c++基础面试笔记

说说 static关键字的作用

  1. 定义全局静态变量和局部静态变量:在变量前面加上static关键字。初始化的静态变量会在数据段分配内存,未初始化的静态变量会在BSS段分配内存。直到程序结束,静态变量始终会维持前值。只不过全局静态变量和局部静态变量的作用域不一样;

  2. 定义静态函数:在函数返回类型前加上static关键字,函数即被定义为静态函数。静态函数只能在本源文件中使用;

  3. 在变量类型前加上static关键字,变量即被定义为静态变量。静态变量只能在本源文件中使用;

    //示例
    static int a;
    static void func();
  4. 在c++中,static关键字可以用于定义类中的静态成员变量:使用静态数据成员,它既可以被当成全局变量那样去存储,但又被隐藏在类的内部。类中的static静态数据成员拥有一块单独的存储区,而不管创建了多少个该类的对象。所有这些对象的静态数据成员都共享这一块静态存储空间。

  5. 在c++中,static关键字可以用于定义类中的静态成员函数:与静态成员变量类似,类里面同样可以定义静态成员函数。只需要在函数前加上关键字static即可。如静态成员函数也是类的一部分,而不是对象的一部分。所有这些对象的静态数据成员都共享这一块静态存储空间。

答案解析

当调用一个对象的非静态成员函数时,系统会把该对象的起始地址赋给成员函数的this指针。而静态成员函数不属于任何一个对象,因此C++规定静态成员函数没有this指针(划重点,面试题常考)。既然它没有指向某一对象,也就无法对一个对象中的非静态成员进行访问。

宏定义

          使用宏,需要引入头文件:#include <climits>

          定义符号常量表示类型的限制

           #define P1 3.14

          预处理器会将代码中的宏名替换成指定的字符串(const定义的常量生效于编译阶段)

          优点:提高程序的可维护性,一改全改

          define定义的常量,运行时是直接的操作数,并不会存放在内存中。

          #define P1 3.14

          #define M(a,b) a*b

          main函数中输出cout << P1;        3.14

                                    cout <<M(2+3,4+5);           2+3*4+5=19

指针

   在32位平台下,无论指针的类型是什么,sizeof(指针名)都是4,在64位平台下,无论指针的类型是什么,sizeof(指针名)都是8。

(1)初始化:

// 数组
int a[5] = { 0 };
char b[] = "Hello";    // 按字符串初始化,大小为6
char c[] = { 'H','e','l','l','o','\0' };    // 按字符初始化
int* arr = new int[10];    // 动态创建一维数组

// 指针
// 指向对象的指针
int* p = new int(0);
delete p;
// 指向数组的指针
int* p1 = new int[10];
delete[] p1;
// 指向类的指针:
string* p2 = new string;
delete p2;
// 指向指针的指针(二级指针)
int** pp = &p;
**pp = 10;

(2)指针操作:

   数组名的指针操作

int a[3][4];  
int (*p)[4];  //该语句是定义一个数组指针,指向含4个元素的一维数组
p = a;        //将该二维数组的首地址赋给p,也就是a[0]或&a[0][0]
p++;          //该语句执行过后,也就是p=p+1;p跨过行a[0][]指向了行a[1][]
              //所以数组指针也称指向一维数组的指针,亦称行指针。
//访问数组中第i行j列的一个元素,有几种操作方式:
//*(p[i]+j)、*(*(p+i)+j)、(*(p+i))[j]、p[i][j]。其中,优先级:()>[]>*。
//这几种操作方式都是合法的。

   (3)指针变量的数据操作:

char *str = "hello,douya!";
str[2] = 'a';
*(str+2) = 'b';
//这两种操作方式都是合法的。

函数指针的应用场景:回调(callback)。我们调用别人提供的 API函数(Application Programming Interface,应用程序编程接口),称为Call;如果别人的库里面调用我们的函数,就叫Callback。

说说内联函数和宏函数的区别

区别:

  1. 宏定义不是函数,但是使用起来像函数。预处理器用复制宏代码的方式代替函数的调用,省去了函数压栈退栈过程,提高了效率;而内联函数本质上是一个函数,内联函数一般用于函数体的代码比较简单的函数,不能包含复杂的控制语句,while、switch,并且内联函数本身不能直接调用自身。
  2. 宏函数是在预编译的时候把所有的宏名用宏体来替换,简单的说就是字符串替换 ;而内联函数则是在编译的时候进行代码插入,编译器会在每处调用内联函数的地方直接把内联函数的内容展开,这样可以省去函数的调用的开销,提高效率
  3. 宏定义是没有类型检查的,无论对还是错都是直接替换;而内联函数在编译的时候会进行类型的检查,内联函数满足函数的性质,比如有返回值、参数列表等
  4. inline函数一般用于比较小的,频繁调用的函数,这样可以减少函数调用带来的开销。只需要在函数返回类型前加上关键字inline,即可将函数指定为inline函数。
  5. 内联是以代码膨胀(复制)为代价,仅仅省去了函数调用的开销,从而提高函数的执行效率。如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率 的收获会很少。另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。

                                                                                                                                                                                 i++ 不能作为左值,而++i 可以:

1
2
3
4
5
inti = 0;
int*p1 = &(++i);//正确
int*p2 = &(i++);//错误
++i = 1//正确
i++ = 1//错误

 说说new和malloc的区别,各自底层实现原理。

  1. new是操作符,而malloc是函数。
  2. new在调用的时候先分配内存,在调用构造函数,释放的时候调用析构函数;而malloc没有构造函数和析构函数。
  3. malloc需要给定申请内存的大小,返回的指针需要强转;new会调用构造函数,不用指定内存的大小,返回指针不用强转。
  4. new可以被重载;malloc不行
  5. new分配内存更直接和安全。
  6. new发生错误抛出异常,malloc返回null

答案解析

malloc底层实现:当开辟的空间小于 128K 时,调用 brk()函数;当开辟的空间大于 128K 时,调用mmap()。malloc采用的是内存池的管理方式,以减少内存碎片。先申请大块内存作为堆区,然后将堆区分为多个内存块。当用户申请内存时,直接从堆区分配一块合适的空闲快。采用隐式链表将所有空闲块,每一个空闲块记录了一个未分配的、连续的内存地址。

new底层实现:关键字new在调用构造函数的时候实际上进行了如下的几个步骤:

  1. 创建一个新的对象
  2. 将构造函数的作用域赋值给这个新的对象(因此this指向了这个新的对象)
  3. 执行构造函数中的代码(为这个新对象添加属性)
  4. 返回新对象

 说说使用指针需要注意什么

    1. 定义指针时,先初始化为NULL。
    2. 用malloc或new申请内存之后,应该立即检查指针值是否为NULL。防止使用指针值为NULL的内存。
    3. 不要忘记为数组和动态内存赋初值。防止将未被初始化的内存作为右值使用。
    4. 避免数字或指针的下标越界,特别要当心发生“多1”或者“少1”操作
    5. 动态内存的申请与释放必须配对,防止内存泄漏
    6. 用free或delete释放了内存之后,立即将指针设置为NULL,防止“野指针”
posted @ 2021-09-14 18:33  春香  阅读(47)  评论(0编辑  收藏  举报