C++基础语法1-20(八股篇)

一. 在main函数执行之前和之后的代码可能是什么?

main函数之前,主要是初始化系统相关资源:

        1. 设置栈指针

        2. 初始化静态static变量和global全局变量,即.data段的内容。

        3. 将未初始化的部分全局变量赋初值:数值型short、int、long等设置为0,bool为FALSE,指针为NULL等,即.bss段的内容。

        4. 全局对象初始化,在main之前调用构造函数,这是可能会执行前的一些代码

        5. 将main函数的参数argc,argv等传递给main函数,然后才真正运行main函数

        6. __attribute__((constructor))  ,这是 GCC 编译器的一个特性,它允许你定义一个函数,在程序开始执行之前,也就是 main 函数被调用之前,这个函数会被自动调用。

main函数执行之后:

        1. 全局对象的析构函数。

        2. 可以再atexit注册一个函数,它会在main之后执行。atexit 是 C 标准库中的一个函数,它用于注册一个函数,该函数会在程序正常终止时被调用,即在 main 函数执行完毕后,但在程序实际退出之前。这通常用于执行清理代码,比如关闭文件、释放内存、断开网络连接等。

        3. __attribute__((destructor))

附上资料:(来源:《深入理解计算机系统》)

这里左边褐色和蓝色是在.data或者.bss段上,红色的是在.text段。

windows磁盘上有一个xxx.exe文件,但是CPU是不能运行的,先把这个文件加载到内存当中,这个内存不是物理内存。

用户空间:

空指针: 平时空指针指向的就是这个区域

指令在运行的时候放在.text段(代码段)

.rodata :只读数据段

char * p = "hello world"; *p = 'a'; //错误的

const char* p = "hello world"; //正确的

.data :数据段,存放初始化的,而且初始化不为0的

.bss : 数据段,存放未初始化的,而且初始化为0的(如int data; //全局变量,未初始化但是打印出来为0)

.heap:堆区(从上往下增长)

加载动态共享库(windows:*.dll linux: *so)

stack:栈空间(从下往上增长)

命令行和环境变量(如 :./a.out 192.168.1.110 90)

内核空间:

ZONE_DMA(16M)

ZONE_NORMAL(800M,进程控制块等内核)

ZONE_HIGHMEM(做地址映射用的)

二. 结构体内存对齐问题?

        1. 结构体内成员按照声明顺序存储,第一个成员地址和整个结构体地址是相同的。

        2. 未特殊说明时,按结构体中size最大的成员对齐(若有double成员,按8字节对齐)。

扩展:C++11中引入了alignasalignof

                alignas:指定结构体的对齐方式。

                alignof:计算出类型的对齐方式。

但是alignas在某些情况下是不能使用的,具体例子:

// alignas 生效的情况

struct Info {
  uint8_t a;
  uint16_t b;
  uint8_t c;
};

std::cout << sizeof(Info) << std::endl;   // 6  2 + 2 + 2
std::cout << alignof(Info) << std::endl;  // 2

struct alignas(4) Info2 {
  uint8_t a;
  uint16_t b;
  uint8_t c;
};

std::cout << sizeof(Info2) << std::endl;   // 8  2 + 2 + 2 + 2
std::cout << alignof(Info2) << std::endl;  // 4

alignas将内存对齐调整为4字节。故sizeof(Info2)的值变为8。

// alignas 失效的情况

struct Info {
  uint8_t a;
  uint32_t b;
  uint8_t c;
};

std::cout << sizeof(Info) << std::endl;   // 12  4 + 4 + 4
std::cout << alignof(Info) << std::endl;  // 4

struct alignas(2) Info2 {
  uint8_t a;
  uint32_t b;
  uint8_t c;
};

std::cout << sizeof(Info2) << std::endl;   // 12  4 + 4 + 4
std::cout << alignof(Info2) << std::endl;  // 4

alignas小于自然对齐的最小单位,则被忽略。

        3. 如果想使用单字节对齐的方式,使用alignas是无效的。应该使用#pragma pack(push, 1)或者使用__attribute__((packed))

#if defined(__GNUC__) || defined(__GNUG__)
  #define ONEBYTE_ALIGN __attribute__((packed))
#elif defined(_MSC_VER)
  #define ONEBYTE_ALIGN
  #pragma pack(push,1)
#endif

struct Info {
  uint8_t a;
  uint32_t b;
  uint8_t c;
} ONEBYTE_ALIGN;

#if defined(__GNUC__) || defined(__GNUG__)
  #undef ONEBYTE_ALIGN
#elif defined(_MSC_VER)
  #pragma pack(pop)
  #undef ONEBYTE_ALIGN
#endif

std::cout << sizeof(Info) << std::endl;   // 6 1 + 4 + 1
std::cout << alignof(Info) << std::endl;  // 1

        4. 确定结构体中每个元素大小可以通过下面这种方法:

#if defined(__GNUC__) || defined(__GNUG__)
  #define ONEBYTE_ALIGN __attribute__((packed))
#elif defined(_MSC_VER)
  #define ONEBYTE_ALIGN
  #pragma pack(push,1)
#endif

/**
* 0 1   3     6   8 9            15
* +-+---+-----+---+-+-------------+
* | |   |     |   | |             |
* |a| b |  c  | d |e|     pad     |
* | |   |     |   | |             |
* +-+---+-----+---+-+-------------+
*/
struct Info {
  uint16_t a : 1;
  uint16_t b : 2;
  uint16_t c : 3;
  uint16_t d : 2;
  uint16_t e : 1;
  uint16_t pad : 7;
} ONEBYTE_ALIGN;

#if defined(__GNUC__) || defined(__GNUG__)
  #undef ONEBYTE_ALIGN
#elif defined(_MSC_VER)
  #pragma pack(pop)
  #undef ONEBYTE_ALIGN
#endif

std::cout << sizeof(Info) << std::endl;   // 2
std::cout << alignof(Info) << std::endl;  // 1

这种方式是alignas处理不了的。

三. 指针和引用的区别

        1. 指针是一个变量,存储的是一个地址,指向内存的一个存储单元; 引用是原变量的别名,跟原来的变量实质上是同一个东西。

        2. 指针可以为空,引用不能为NULL且在定义式必须初始化

        3.指针可以有多级,引用只有一级。

        4. 指针在初始化可以改变指向,而引用在初始化之后不可以再改变。

        5. 把指针作为参数传递的时候,也是将实参的一个拷贝传递给形参,两者指向的地址相同,但是不是同一个变量。在函数中改变这个变量的指向不影响实参,而引用却可以。

        6.sizeof两者,指针是本指针的大小,引用是引用所指向变量的大小。

        7.引用一旦初始化之后就不可以再改变(变量可以被引用为多次,但是引用只能作为一个变量引用);指针变量可以重新指向别的变量。

        8. 指针的声明和定义可以分开定义,引用在声明是必须初始化为另一变量。

在汇编层面,一些编译器将引用当成指针操作,因此引用会占用空间。是否占用空间,应该结合编译器分析。

引用的本质

引用是指针常量的伪装。

引用是编译器提供的一个有用且安全的工具,去除了指针的一些缺点,禁止了部分不安全的操作。

变量是什么变量就是一个在程序执行过程中可以改变的量。

换一个角度,变量是一块内存区域的名字,它代表这块内存区域,当我们对变量进行修改的时候,会引起内存区域中内容的改变。

在计算机看来,内存区域根本就不存在什么名字,它仅有的标志就是的地址,因此我们若想修改一块内存区域的内容,只有知道他的地址能实现。

所谓的变量只不过是编译器给我们进行的一种抽象,让我们不必去了解更多的细节,降低我们的思维跨度而已。

程序员拥有引用,但编译器仅拥有指针(地址)

引用的底层机制实际上是和指针一样的。不要相信有别名,不要认为引用可以节省一个指针的空间,因为这一切不会发生,编译器还是会把引用解释为指针

引用和指针本质上没有区别。

void test(int *p)
{
  int a=1;
  p=&a;
  cout<<p<<" "<<*p<<endl;
}

int main(void)
{
    int *p=NULL;
    test(p);
    if(p==NULL)
    cout<<"指针p为NULL"<<endl;
    return 0;
}
//运行结果为:
//0x22ff44 1
//指针p为NULL


void testPTR(int* p) {
	int a = 12;
	p = &a;

}

void testREFF(int& p) {
	int a = 12;
	p = a;

}
void main()
{
	int a = 10;
	int* b = &a;
	testPTR(b);//改变指针指向,但是没改变指针的所指的内容
	cout << a << endl;// 10
	cout << *b << endl;// 10

	a = 10;
	testREFF(a);
	cout << a << endl;//12
}

在编辑器看来,int a = 10;int &b = a,等价于int * const b = &a;而 b = 20,等价于 *b = 20;自动转换为指针和自动解引用 。

四. 在传递函数参数的时候,使用指针和引用的场景

        1. 需要返回函数内部局部变量的内存的时候用指针。使用指针传参需要开辟内存,用完要记得释放指针,不然会内存泄漏。而返回局部变量的引用是没有意义的(出函数作用域就释放了)。

        2. 对栈空间大小比较敏感(如递归)的时候使用引用。使用引用传递不需要创建临时变量,开销要更小。

        3. 类对象作为参数传递的时候使用引用,这是C++类对象传递的标准方式。

五. 堆和栈的区别

        1. 申请方式不同。

                栈由系统自动分配。

                堆是自己申请和释放的。

        2.申请的大小限制不同。

                栈顶和栈底是之前预设好的,栈是向栈底扩展,大小固定,可以通过ulimit -a 查看,由ulimit -s修改。

                堆向高地址扩展,是不连续的内存区域,大小可以灵活调整。

        3. 申请效率不同。

                栈由系统分配,速度快,不会由碎片。

                堆由程序员分配,速度慢,且会有碎片。

六. 堆快还是栈快?

七. 区别以下指针类型

int *p[10]
int (*p)[10]
int *p(int)
int (*p)(int)

        1. int *p[10]表示指针数组,指数组,是一个数组变量,大小为10,数组内每个元素都是指向int类型的指针变量。

        2. int (*p)[10]表示数组指针,指指针,只有一个变量,是指针类型,不过指向的是一个int类型的数组,这个数组大小是10。

        3. int *p(int) 是函数声明,函数名是p,参数是int类型,返回值是int*类型的。

        4.int (*p)(int)是函数指针,是指针,该指针指向的函数具有int类型参数,并且返回值是int类型的。

八. new/delete与malloc/free的异同

        相同点:

                都是从堆上申请空间,并且需要用户手动释放。

        不同点:

                1.前者是C++操作符,后者是C/C++语言标准库函数。

                2.new自动计算要分配的空间大小,malloc需要手工计算。

                3.new是类型安全的,malloc不是,例如:

int *p = new float[2]; //编译错误
int *p = (int*)malloc(2 * sizeof(double));//编译无错误

                4. new调用operator new的标准库函数分配足够空间并调用相关对象的构造函数,delete对指针所指对象运行适当的析构函数;然后通过调用名为operator delete的标准库函数释放该对象所用内存。后者均没有相关调用。

                5.后者需要库文件支持,前者不用。

                6.new是封装了malloc,直接free不会报错,但是这只是释放内存,而不会析构对象。

九. new和delete是如何实现的

  • new 和 delete是用户进行动态内存申请和释放的操作符operator new 和operator delete是系统提供的全局函数,new在底层调用operator new全局函数来申请空间delete在底层通过operator delete全局函数来释放空间。

operator new

  • operator delete

十. malloc和new的区别

十一. 既然有了malloc/free,C++中为什么还需要new/delete呢?直接用malloc/free不好吗?

主要是看多了啥

十二. 被free回收的内存是立即返回给操作系统的吗?

十三. 宏定义和函数有何区别

十四. 宏定义和typedef区别

十五. 变量声明和定义区别

十六. strlen和sizeof区别

        1. sizeof是运算符,并不是函数,结果在编辑时得到而非运行中获得;strlen是字符处理的库函数。

         2. sizeof参数可以是任何数据的类型或者数据(sizeof参数不退化);strlen的参数只能是字符指针且结尾是 '\0' 的字符串。

        3. 因为sizeof值在编译时确定,所以不能用来得到动态分配(运行时分配)存储空间的大小。

  int main(int argc, char const *argv[]){
      
      const char* str = "name";

      sizeof(str); // 取的是指针str的长度,是8
      strlen(str); // 取的是这个字符串的长度,不包含结尾的 \0。大小是4
      return 0;
  }

注:

        指针占用大小为8字节(64位)。

        指针占用大小为4字节(32位)。

十七. 常量指针和指针常量区别

常量指针

语法const 数据类型 *变量名;                     

不能通过解引用的方法修改内存地址中值(用原始的变量名是可以修改的)。

注意:

  1. 指向的变量(对象)可以改变(之前是指向变量a的,后来可以改为指向变量b)。
  2. 一般用于修饰函数的形参,表示不希望在函数里修改内存地址中的值。
  3. 如果用于形参,虽然指向的对象可以改变,但这么做没有任何意义。
  4. 如果形参的值不需要改变,建议加上const修饰,程序可读性更好。

2)指针常量

语法: 数据类型 * const 变量名;                      

指向的变量(对象)不可改变。

注意:

  1. 在定义的同时必须初始化,否则没有意义。
  2. 可以通过解引用的方法修改内存地址中值。
  3. C++编译器把指针常量做了一些特别的处理,改头换面之后,有一个新的名字,叫引用。

3)常指针常量

语法:const 数据类型 * const 变量名;          

指向的变量(对象)不可改变,不能通过解引用的方法修改内存地址中值。

常引用。

常量指针:指针指向可以改,指针指向的值不可以更改。

指针常量:指针指向不可以改,指针指向的值可以更改。

常指针常量:指针指向不可以改,指针指向的值不可以更改。

记忆秘诀:*表示指针,指针在前先读指针;指针在前指针就不允许改变。

常量指针:const 数据类型 *变量名;        

指针常量:数据类型 * const 变量名;          

十八. a和 &a有什么区别

十九. C和Python的区别

二十. C++和C语言的区别

  • 26
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值