ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

【转载】阿秀的求职笔记:基础语法篇

2021-11-29 15:34:01  阅读:444  来源: 互联网

标签:初始化 const 变量 求职 C++ 语法 int 阿秀 指针


本专栏内容均为复习笔试面试阶段学习的文章,部分加上了自己的注释,文章注明转载!!
为了自己更好理解+查看方便+督促自己每天学习
【争取日更25条】

重要的事情说三遍
欢迎大家去 阿秀的求职笔记 学习!
欢迎大家去 阿秀的求职笔记 学习!
欢迎大家去 阿秀的求职笔记 学习!

目录

基础语法

1、在main执行之前和之后执行的代码可能是什么?

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

  • 设置栈指针
  • 初始化静态static变量和global全局变量,即.data段的内容
  • 将未初始化部分的全局变量赋初值:数值型shortintlong等为0boolFALSE,指针为NULL等等,即.bss段的内容
  • 全局对象初始化,在main之前调用构造函数,这是可能会执行前的一些代码
  • 将main函数的参数argcargv等传递给main函数,然后才真正运行main函数
  • __attribute__((constructor))

main函数执行之后

  • 全局对象的析构函数会在main函数之后执行;
  • 可以用 atexit 注册一个函数,它会在main 之后执行;
  • __attribute__((destructor))

bss,data,text,rodata,堆,栈

img

数据段包含了bss段和data段

bss段

未初始化的全局变量和未初始化的局部静态变量保存在 .bss段中

data段

已初始化的全局变量和局部静态变量保存在 .data段中

代码段包含了init段、text段和rodata段

init段

程序初始化入口代码,在main() 之前运行。

text段

通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读,某些架构也允许代码段为可写,即允许修改程序。在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。

rodata段

rodata的意义同样明显,ro代表read only,即只读数据(const),存放的是只读数据,比如字符串常量,全局const变量 和#define定义的常量。例如: char*p=“123456”, "123456"就存放在rodata段中。

2、结构体内存对齐问题?

  • 结构体内成员按照声明顺序存储,第一个成员地址和整个结构体地址相同。
  • 未特殊说明时,按结构体中size最大的成员对齐(若有double成员,按8字节对齐。)

c++11以后引入两个关键字 alignasalignof。其中alignof可以计算出类型的对齐方式,alignas可以指定结构体的对齐方式。

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

注:

typedefsize
typedef signed charint8_t
typedef short intint16_t
typedef intint32_t
typedef long int(64位平台)int64_t
typedef long long int(32位)int64_t
typedef unsigned charuint8_t1字节
typedef unsigned short intuint16_t2字节
typedef unsigned intuint32_t4字节
typedef unsigned long int (64位)uint64_t8字节
typedef unsigned long long int(32位)uint64_t8字节
// 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  4 + 4
std::cout << alignof(Info2) << std::endl;  // 4Copy to clipboardErrorCopied

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;  // 4Copy to clipboardErrorCopied

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

  • 如果想使用单字节对齐的方式,使用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;  // 6Copy to clipboardErrorCopied
    
  • 确定结构体中每个元素大小可以通过下面这种方法:

    #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;  // 1Copy to clipboardErrorCopied
    

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

3、指针和引用的区别

  • 指针是一个变量,存储的是一个地址,引用跟原来的变量实质上是同一个东西,是原变量的别名

    (int a=100; int *p=&a;)

  • 指针可以有多级,引用只有一级

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

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

  • sizeof指针得到的是本指针的大小,sizeof引用得到的是引用所指向变量的大小

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

  • 引用本质是一个指针,同样会占4字节内存;指针是具体变量,需要占用存储空间(,具体情况还要具体分析)。

  • 引用在声明时必须初始化为另一变量,一旦出现必须为typename refname &varname形式;指针声明和定义可以分开,可以先只声明指针变量而不初始化,等用到时再指向具体变量。

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

  • 不存在指向空值的引用,必须有具体实体;但是存在指向空值的指针。

参考代码:

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; 自动转换为指针和自动解引用.

4、在传递函数参数时,什么时候该使用指针,什么时候该使用引用呢?

  • 需要返回函数内局部变量的内存的时候用指针。使用指针传参需要开辟内存,用完要记得释放指针,不然会内存泄漏。而返回局部变量的引用是没有意义的
  • 对栈空间大小比较敏感(比如递归)的时候使用引用。使用引用传递不需要创建临时变量,开销要更小
  • 类对象作为参数传递的时候使用引用,这是C++类对象传递的标准方式

5、堆和栈的区别

  • 申请方式不同。
    • 栈由系统自动分配。
  • 堆是自己申请和释放的。
  • 申请大小限制不同。
    • 栈顶和栈底是之前预设好的,栈是向栈底扩展,大小固定,可以通过ulimit -a查看,由ulimit -s修改。
    • 堆向高地址扩展,是不连续的内存区域,大小可以灵活调整。
  • 申请效率不同。
    • 栈由系统分配,速度快,不会有碎片。
    • 堆由程序员分配,速度慢,且会有碎片。

栈空间默认是4M, 堆区一般是 1G - 4G

管理方式堆中资源由程序员控制(容易产生memory leak)栈资源由编译器自动管理,无需手工控制
内存管理机制系统有一个记录空闲内存地址的链表,当系统收到程序申请时,遍历该链表,寻找第一个空间大于申请空间的堆结点,删除空闲结点链表中的该结点,并将该结点空间分配给程序(大多数系统会在这块内存空间首地址记录本次分配的大小,这样delete才能正确释放本内存空间,另外系统会将多余的部分重新放入空闲链表中)只要栈的剩余空间大于所申请空间,系统为程序提供内存,否则报异常提示栈溢出。(这一块理解一下链表和队列的区别,不连续空间和连续空间的区别,应该就比较好理解这两种机制的区别了)
空间大小堆是不连续的内存区域(因为系统是用链表来存储空闲内存地址,自然不是连续的),堆大小受限于计算机系统中有效的虚拟内存(32bit 系统理论上是4G),所以堆的空间比较灵活,比较大栈是一块连续的内存区域,大小是操作系统预定好的,windows下栈大小是2M(也有是1M,在 编译时确定,VC中可设置)
碎片问题对于堆,频繁的new/delete会造成大量碎片,使程序效率降低对于栈,它是有点类似于数据结构上的一个先进后出的栈,进出一一对应,不会产生碎片。(看到这里我突然明白了为什么面试官在问我堆和栈的区别之前先问了我栈和队列的区别)
生长方向堆向上,向高地址方向增长。栈向下,向低地址方向增长。
分配方式堆都是动态分配(没有静态分配的堆)栈有静态分配和动态分配,静态分配由编译器完成(如局部变量分配),动态分配由alloca函数分配,但栈的动态分配的资源由编译器进行释放,无需程序员实现。
分配效率堆由C/C++函数库提供,机制很复杂。所以堆的效率比栈低很多。栈是其系统提供的数据结构,计算机在底层对栈提供支持,分配专门 寄存器存放栈地址,栈操作有专门指令。

形象的比喻

栈就像我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自由度小。

堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由度大。

6、你觉得堆快一点还是栈快一点?

毫无疑问是栈快一点。

因为操作系统会在底层对栈提供支持,会分配专门的寄存器存放栈的地址,栈的入栈出栈操作也十分简单,并且有专门的指令执行,所以栈的效率比较高也比较快。

而堆的操作是由C/C++函数库提供的,在分配堆内存的时候需要一定的算法寻找合适大小的内存。并且获取堆的内容需要两次访问,第一次访问指针,第二次根据指针保存的地址访问内存,因此堆比较慢。

7、区别以下指针类型?

int *p[10]
int (*p)[10]
int *p(int)
int (*p)(int)
  • int *p[10]表示指针数组,强调数组概念,是一个数组变量,数组大小为10,数组内每个元素都是指向int类型的指针变量。
  • int (*p)[10]表示数组指针,强调是指针,只有一个变量,是指针类型,不过指向的是一个int类型的数组,这个数组大小是10。
  • int *p(int)是函数声明,函数名是p,参数是int类型的,返回值是int *类型的。
  • int (*p)(int)是函数指针,强调是指针,该指针指向的函数具有int类型参数,并且返回值是int类型的。

本人记忆点:

  • p没有小括号的从左至右读:* p[10]:指针数组

  • p有小括号的* p最后读:(* p)[10]:数组指针,(* p)(int):函数指针

8、new / delete 与 malloc / free的异同

(笔试碰到过)

相同点

  • 都可用于内存的动态申请和释放

不同点

  • 前者是C++运算符,后者是C/C++语言标准库函数
  • new自动计算要分配的空间大小,malloc需要手工计算
  • new是类型安全的,malloc不是。例如:
int *p = new float[2]; //编译错误
int *p = (int*)malloc(2 * sizeof(double));//编译无错误
  • new调用名为operator new的标准库函数分配足够空间并调用相关对象的构造函数,delete对指针所指对象运行适当的析构函数;然后通过调用名为operator delete的标准库函数释放该对象所用内存。后者均没有相关调用
  • 后者需要库文件支持,前者不用
  • new是封装了malloc,直接free不会报错,但是这只是释放内存,而不会析构对象

9、new和delete是如何实现的?

  • new的实现过程是:首先调用名为operator new的标准库函数,分配足够大的原始为类型化的内存,以保存指定类型的一个对象;接下来运行该类型的一个构造函数,用指定初始化构造对象;最后返回指向新分配并构造后的的对象的指针
  • delete的实现过程:对指针指向的对象运行适当的析构函数;然后通过调用名为operator delete的标准库函数释放该对象所用内存

流程:

operator new分配内存->构造函数->析构->operator delete释放内存

10、malloc和new的区别?

  • malloc和free是标准库函数,支持覆盖;new和delete是运算符,不重载。
  • malloc仅仅分配内存空间,free仅仅回收空间,不具备调用构造函数和析构函数功能,用malloc分配空间存储类的对象存在风险;new和delete除了分配回收功能外,还会调用构造函数和析构函数。
  • malloc和free返回的是void类型指针(必须进行类型转换),new和delete返回的是具体类型指针。

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

  • malloc/free和new/delete都是用来申请内存和回收内存的。
  • 在对非基本数据类型的对象使用的时候,对象创建的时候还需要执行构造函数,销毁的时候要执行析构函数。而malloc/free是库函数,是已经编译的代码,所以不能把构造函数和析构函数的功能强加给malloc/free,所以new/delete是必不可少的。

12、被free回收的内存是立即返还给操作系统吗?

不是的,被free回收的内存会首先被ptmalloc使用双链表保存起来,当用户下一次申请内存的时候,会尝试从这些内存中寻找合适的返回。这样就避免了频繁的系统调用,占用过多的系统资源。同时ptmalloc也会尝试对小块内存进行合并,避免过多的内存碎片。

13、宏定义和函数有何区别?

  • 宏在编译时完成替换,之后被替换的文本参与编译,相当于直接插入了代码,运行时不存在函数调用,执行起来更快;函数调用在运行时需要跳转到具体调用函数。
  • 宏定义属于在结构中插入代码,没有返回值;函数调用具有返回值。
  • 宏定义参数没有类型,不进行类型检查;函数参数具有类型,需要检查类型。
  • 宏定义不要在最后加分号。

14、宏定义和typedef区别?

  • 宏主要用于定义常量及书写复杂的内容;typedef主要用于定义类型别名。
  • 宏替换发生在编译阶段之前(预编译时),属于文本插入替换;typedef是编译的一部分。
  • 宏不检查类型;typedef会检查数据类型。
  • 宏不是语句,不在在最后加分号;typedef是语句,要加分号标识结束。
  • 注意对指针的操作,typedef char * p_char和#define p_char char *区别巨大。

注:

  • typedef char* Pchar;

    Pchar p1, p2;

    则p1和p2都是char*类型的指针变量。

  • #define Pchar char*

    Pchar p3, p4;

    由于#define只是进行字符串替换,因此第二句相当于 char* p3, p4;

    即p3是char* 型变量,而p4则是char 型变量。

15、变量声明和定义区别?

  • 声明仅仅是把变量的声明的位置及类型提供给编译器,并不分配内存空间;定义要在定义的地方为其分配存储空间。
  • 相同变量可以在多处声明(外部变量extern),但只能在一处定义。

16、strlen和sizeof区别?

(第一次线下面试的时候就把sizeof说错了,char str[10]={ ‘h’,‘e’,‘l’,‘1’,‘o’ },sizeof(str)=10!!!)

  • sizeof是运算符,并不是函数,结果在编译时得到而非运行中获得;strlen是字符处理的库函数。
  • sizeof参数可以是任何数据的类型或者数据(sizeof参数不退化);strlen的参数只能是字符指针且结尾是’\0’的字符串。
  • 因为sizeof值在编译时确定,所以不能用来得到动态分配(运行时分配)存储空间的大小。
  int main(int argc, char const *argv[]){

      const char* str = "name";

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

注:

32位环境下:sizeof() 和strlen() 碰见字符串

首先,字符串数组初始化的方式只有两种
1、逐个字符初始化字符数组:

char str[10]={ 'h','e','l','1','o' };

注意:如果花括号中提供的字符个数大于数组长度,则按语法错误处理;若小于数组长度,则其余的元素自动定为’\0’ 。
2、用字符串常量来初始化字符数组:

char str[]={"hello"};

也可以省略花括号

char str[]="hello";

接下来,就是 sizeof 和 stelen 的区别

sizeof("hello")=6;

char str1[] = { 'h','e','l' ,'\0'};
sizeof(str1)=4;

char str2[10] = { 'h','e','l' };
sizeof(str2)=10;

char str3[] = { 'h','e','l','\0','l' };
sizeof(str3)=5;
printf("%d   ", strlen("hello"));//5

char str1[] = { 'h','e','l' ,'\0'};
printf("%d   ", strlen(str1));//3

char str2[10] = { 'h','e','l' };
printf("%d   ", strlen(str2));p //3

char str3[] = { 'h','e','l','\0','l' };
printf("%d   ", strlen(str3));//3

strlen读到\0为返回长度,而且不把\0计入字符串长度
sizeof是计算数组分配的空间大小,把\0计入字符串长度

16.2、(补充题)一个指针占多少字节?

在16题中有提到sizeof(str)的值为8,是在64位的编译环境下的,指针的占用大小为8字节;

而在32位环境下,指针占用大小为4字节。

一个指针占内存的大小跟编译环境有关,而与机器的位数无关。

还有疑问的,可以自行打开Visual Studio编译器自己实验一番。

17、常量指针和指针常量区别?

  • 指针常量是一个指针,读成常量的指针,指向一个只读变量,也就是后面所指明的int const 和 const int,都是一个常量,可以写作int const *p或const int *p。
  • 常量指针是一个不能给改变指向的指针。指针是个常量,必须初始化,一旦初始化完成,它的值(也就是存放在指针中的地址)就不能在改变了,即不能中途改变指向,如int *const p。

18、a和&a有什么区别?

假设数组int a[10]; int (*p)[10] = &a;其中:

  • a是数组名,是数组首元素地址,+1表示地址值加上一个int类型的大小,如果a的值是0x00000001,加1操作后变为0x00000005。*(a + 1) = a[1]。
  • &a是数组的指针,其类型为int (*)[10](就是前面提到的数组指针),其加1时,系统会认为是数组首地址加上整个数组的偏移(10个int型变量),值为数组a尾元素后一个元素的地址。
  • 若(int *)p ,此时输出 *p时,其值为a[0]的值,因为被转为int *类型,解引用时按照int类型大小来读取。

19、C++和Python的区别

包括但不限于:

  • Python是一种脚本语言,是解释执行的,而C++是编译语言,是需要编译后在特定平台运行的。python可以很方便的跨平台,但是效率没有C++高。
  • Python使用缩进来区分不同的代码块,C++使用花括号来区分
  • C++中需要事先定义变量的类型,而Python不需要,Python的基本数据类型只有数字,布尔值,字符串,列表,元组等等
  • Python的库函数比C++的多,调用起来很方便

20、C++和C语言的区别

  • C++中new和delete是对内存分配的运算符,取代了C中的malloc和free。
  • 标准C++中的字符串类取代了标准C函数库头文件中的字符数组处理函数(C中没有字符串类型)。
  • C++中用来做控制态输入输出的iostream类库替代了标准C中的stdio函数库。
  • C++中的try/catch/throw异常处理机制取代了标准C中的setjmp()和longjmp()函数。
  • 在C++中,允许有相同的函数名,不过它们的参数类型不能完全相同,这样这些函数就可以相互区别开来。而这在C语言中是不允许的。也就是C++可以重载,C语言不允许。
  • C++语言中,允许变量定义语句在程序中的任何地方,只要在是使用它之前就可以;而C语言中,必须要在函数开头部分。而且C++不允许重复定义变量,C语言也是做不到这一点的
  • 在C++中,除了值和指针之外,新增了引用。引用型变量是其他变量的一个别名,我们可以认为他们只是名字不相同,其他都是相同的。
  • C++相对与C增加了一些关键字,如:bool、using、dynamic_cast、namespace等等

21、C++与Java的区别

语言特性

  • Java语言给开发人员提供了更为简洁的语法;完全面向对象,由于JVM可以安装到任何的操作系统上,所以说它的可移植性强
  • Java语言中没有指针的概念,引入了真正的数组。不同于C++中利用指针实现的“伪数组”,Java引入了真正的数组,同时将容易造成麻烦的指针从语言中去掉,这将有利于防止在C++程序中常见的因为数组操作越界等指针操作而对系统数据进行非法读写带来的不安全问题
  • C++也可以在其他系统运行,但是需要不同的编码(这一点不如Java,只编写一次代码,到处运行),例如对一个数字,在windows下是大端存储,在unix中则为小端存储。Java程序一般都是生成字节码,在JVM里面运行得到结果
  • Java用接口(Interface)技术取代C++程序中的抽象类。接口与抽象类有同样的功能,但是省却了在实现和维护上的复杂性

垃圾回收

  • C++用析构函数回收垃圾,写C和C++程序时一定要注意内存的申请和释放
  • Java语言不使用指针,内存的分配和回收都是自动进行的,程序员无须考虑内存碎片的问题

应用场景

  • Java在桌面程序上不如C++实用,C++可以直接编译成exe文件,指针是c++的优势,可以直接对内存的操作,但同时具有危险性 。(操作内存的确是一项非常危险的事情,一旦指针指向的位置发生错误,或者误删除了内存中某个地址单元存放的重要数据,后果是可想而知的)
  • Java在Web 应用上具有C++ 无可比拟的优势,具有丰富多样的框架
  • 对于底层程序的编程以及控制方面的编程,C++很灵活,因为有句柄的存在

22、C++中struct和class的区别

相同点

  • 两者都拥有成员函数、公有和私有部分
  • 任何可以使用class完成的工作,同样可以使用struct完成

不同点

  • 两者中如果不对成员不指定公私有,struct默认是公有的,class则默认是私有的
  • class默认是private继承,而struct模式是public继承

引申:C++和C的struct区别

  • C语言中:struct是用户自定义数据类型(UDT);C++中struct是抽象数据类型(ADT),支持成员函数的定义,(C++中的struct能继承,能实现多态)

  • C中struct是没有权限的设置的,且struct中只能是一些变量的集合体,可以封装数据却不可以隐藏数据,而且成员不可以是函数

  • C++中,struct增加了访问权限,且可以和类一样有成员函数,成员默认访问说明符为public(为了与C兼容)

  • struct作为类的一种特例是用来自定义数据结构的。一个结构标记声明后,在C中必须在结构标记前加上struct,才能做结构类型名(除:typedef struct class{};);C++中结构体标记(结构体名)可以直接作为结构体类型名使用,此外结构体struct在C++中被当作类的一种特例

    eg. C: struct Stu stu1,stu2; C++ :Stu stu1,stu2;

23、define宏定义和const的区别

编译阶段

  • define是在编译的预处理阶段起作用,而const是在编译、运行的时候起作用

安全性

  • define只做替换,不做类型检查和计算,也不求解,容易产生错误,一般最好加上一个大括号包含住全部的内容,要不然很容易出错
  • const常量有数据类型,编译器可以对其进行类型安全检查

内存占用

  • define只是将宏名称进行替换,在内存中会产生多分相同的备份。const在程序运行中只有一份备份,且可以执行常量折叠,能将复杂的的表达式计算出结果放入常量表
  • 宏替换发生在编译阶段之前,属于文本插入替换;const作用发生于编译过程中。
  • 宏不检查类型;const会检查数据类型。
  • 宏定义的数据没有分配内存空间,只是插入替换掉;const定义的变量只是值不能改变,但要分配内存空间。

24、C++中const和static的作用

const定义的常量在超出其作用域之后其空间会被释放,而static定义的静态常量在函数执行后不会释放其存储空间。

static

  • 不考虑类的情况

    • 隐藏。所有不加static的全局变量和函数具有全局可见性,可以在其他文件中使用,加了之后只能在该文件所在的编译模块中使用
    • 默认初始化为0,包括未初始化的全局静态变量与局部静态变量,都存在全局未初始化区
    • 静态变量在函数内定义,始终存在,且只进行一次初始化,具有记忆性,其作用范围与局部变量相同,函数退出后仍然存在,但不能使用
  • 考虑类的情况

    • static成员变量:只与类关联,不与类的对象关联。定义时要分配空间,不能在类声明中初始化,必须在类定义体外部初始化,初始化时不需要标示为static;可以被非static成员函数任意访问。

      class foo  
      {  
      public:  
            foo();  
      private:  
            static int i;  
      };  
        
      int foo::i=20;  
      这表明:  
      1、初始化在类体外进行,而前面不加static,以免与一般静态变量或对象相混淆  
      2、初始化时不加该成员的访问权限控制符private、public等  
      3、初始化时使用作用域运算符来表明它所属的类,因此,静态数据成员是类的成员而不是对象的成员。  
      
    • static成员函数:不具有this指针,无法访问类对象的非static成员变量和非static成员函数;不能被声明为const、虚函数和volatile;可以被非static成员函数任意访问

const

  • 不考虑类的情况

    • const常量在定义时必须初始化,之后无法更改
    • const形参可以接收const和非const类型的实参,例如// i 可以是 int 型或者 const int 型void fun(const int& i){ //…}
  • 考虑类的情况

    • const成员变量:不能在类定义外部初始化,只能通过构造函数初始化列表进行初始化,并且必须有构造函数;不同类对其const数据成员的值可以不同,所以不能在类中声明时初始化

      class foo  
      {  
      public:  
            foo():i(100){}  
      private:  
            const int i=100;//error!!!  
      };  
      //或者通过这样的方式来进行初始化  
      foo::foo():i(100)  
      {}  
      
    • const成员函数:const对象不可以调用非const成员函数;非const对象都可以调用;不可以改变非mutable(用该关键字声明的变量可以在const成员函数中被修改)数据的值

注:

  • 类里的static cosnt 和 const static成员初始化

    这两种写法的作用一样,为了便于记忆,在此只说明一种通用的初始化方法:

    class Test  
    {  
    public:  
          static const int mask1;  
          const static int mask2;  
    };  
    const Test::mask1=0xffff;  
    const Test::mask2=0xffff;  
    //它们的初始化没有区别,虽然一个是静态常量一个是常量静态。静态都将存储在全局变量区域,其实最后结果都一样。可能在不同编译器内,不同处理,但最后结果都一样。 
    
  • 完整版

class Test  
{  
public:  
      Test():a(0){}  
      enum {size1=100,size2=200};  
private:  
      const int a;//只能在构造函数初始化列表中初始化  
      static int b;//在类的实现文件中定义并初始化  
      const static int c;//与 static const int c;相同。  
};  
  
int Test::b=0;//static成员变量不能在构造函数初始化列表中初始化,因为它不属于某个对象。  
cosnt int Test::c=0;//注意:给静态成员变量赋值时,不需要加static修饰符。但要加cosnt  

25、C++的顶层const和底层const

概念区分

  • 顶层const:指的是const修饰的变量本身是一个常量,无法修改,指的是指针,就是 * 号的右边
  • 底层const:指的是const修饰的变量所指向的对象是一个常量,指的是所指变量,就是 * 号的左边

举个例子

int a = 10;int* const b1 = &a;        //顶层const,b1本身是一个常量
const int* b2 = &a;       //底层const,b2本身可变,所指的对象是常量
const int b3 = 20;            //顶层const,b3是常量不可变
const int* const b4 = &a;  //前一个const为底层,后一个为顶层,b4不可变
const int& b5 = a;           //用于声明引用变量,都是底层

区分作用

  • 执行对象拷贝时有限制,常量的底层const不能赋值给非常量的底层const
  • 使用命名的强制类型转换函数const_cast时,只能改变运算对象的底层const
const int a;       
int const a;

const int *a;
int *const a;
  • int const a和const int a均表示定义常量类型a。
  • const int *a,其中a为指向int型变量的指针,const在 * 左侧,表示a指向不可变常量。(看成const (*a),对引用加const)
  • int *const a,依旧是指针类型,表示a为指向整型数据的常指针。(看成const(a),对指针const)

标签:初始化,const,变量,求职,C++,语法,int,阿秀,指针
来源: https://blog.csdn.net/Mu_Muxi_/article/details/121596790

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有