C/C++重点八股文

1.C/C++关键字

1.1 static(静态)变量

在C中,关键字static是静态变量:

  • 静态变量只会初始化一次,然后在这函数被调用过程中值不变。
  • 在文件内定义静态变量(函数外),作用域是当前文件,该变量可以被文件内所有函数访问,不能被其他文件函数访问。为本地的全局变量,只初始化一次。

在C++中,类内数据成员可以定义为static

  • 对于非静态数据成员,每个对象有一个副本。而静态数据成员是类的成员,只存在一个副本,被所有对象共享。
  • 静态成员变量没有实例化对象也可以使用,“类名:静态成员变量”
  • 静态成员变量初始化在类外,但是private和protected修饰的静态成员不能类外访问。
class Stu
{
public:
	static int age;
private:
	static int height;
};
//初始化静态成员变量
int Stu::age = 19;
int Stu::height = 180;

int main()
{
	cout<<Stu::age<<endl;//输出19;
	cout<<Stu::height<<endl;//错误的,私有无法访问。
	Stu s;
	cout<<s::age<<endl;//输出19;
	cout<<s::height<<endl;//错误的,私有无法访问。
	return 0;
}
  • 在类中,static修饰的函数是静态成员函数。静态成员函数一样属于类,不属于对象,被对象共享。静态成员函数没有this指针,不能访问非静态的函数和变量,只能访问静态的。

与全局变量相比,静态数据成员的优势:

  • 全局变量作用域是整个工程,而static作用域是当前文件避免命名冲突
  • 静态数据成员可以是private成员,而全局变量不能,实现信息隐藏

为什么静态成员变量不能在类内初始化?

因为类的声明可能会在多处引用,每次引用都会初始化一次,分配一次空间。这和静态变量只能初始化一次,只有一个副本冲突,因此静态成员变量只能类外初始化

为什么static静态变量只能初始化一次?

所有变量都只初始化一次。但是静态变量在全局区(静态区),而自动变量在栈区。静态变量生命周期和程序一样,只创建初始化一次就一直存在,不会销毁。而自动变量生命周期和函数一样,函数调用就进行创建初始化,函数结束就销毁,所以每一次调用函数就初始化一次。

在头文件中定义静态变量是否可行?

不可行,在头文件中定义的一个static变量,对于包含该头文件的所有源文件,实质上在每个源文件内定义了一个同名的static变量。造成资源浪费,可能引起bug

1.2 const的作用

常量类型也称为const类型,使用const修饰变量或者对象

在C中,const的作用为:

  • 定义变量(局部或者全局)为常量
const int a = 10; //常量定义时,必须初始化
  • 修饰函数的参数,函数体内不能修改这个参数的值
  • 修饰函数的返回值
    • const修饰的返回值类型为指针,返回的指针不能被修改,而且只能符给被const修饰的指针
      const char* GetString()
      {
      	//...
      }
      
      int main()
      {
      	char *str = GetString();//错误,str没被const修饰
      	const char *str = GetString();//正确
      }
      
      
    • const修饰的返回值类型为引用,那么函数调用表达式不能做左值(函数不能被赋值)
      const int & add(int &a , int &b)
      {
      	//..
      }
      int main()
      {
      	add(a,b) = 4;//错误,const修饰add的返回引用,不能做左值
      }
      
    • const修饰的返回值类型为普通变量,由于返回是普通临时变量,const修饰没意义。

在c++中,const还有作用为:

  • const修饰类内的数据成员。表示这个数据成员在某个对象的生命周期是常量,不同对象的值可以不一样,因此const成员函数不能在类内初始化。
  • const修饰类内的成员函数。那么这个函数就不能修改对象的成员变量

const的优点?

  1. 进行类型检查,使编译器对处理内容有更多了解。

  2. 避免意义模糊的数字出现,类似宏定义,方便对参数进行修改。

  3. 保护被修饰的内容,防止被意外修改

  4. 为函数重载提供参考

    class A
    {
    	void f(int i){...} //非const对象调用
    	void f(int i) const {...}//const对象调用
    }
    

5.节省内存
6.提高程序效率(编译器不为普通const常量分配存储空间,而保存在符号表中。称为一个编译期间的常量,没有存储和读内存的操作)

什么时候使用const?

  • 修饰一般常量

  • 修饰对象

  • 修饰常指针

    const int *p;
    int const *p;
    int *const p;
    const int *const p;
    
  • 修饰常引用

  • 修饰函数的参数

  • 修饰函数返回值

  • 修饰类的成员函数

  • 修饰另一文件中引用的变量

    extern const int j;
    

const和指针(常量指针、指针常量)

  • 常量指针(const 修饰常量,const在*的左边)

    const int *p = &a; // const修饰int,指针的指向可以修改,但是指针指向的值不能改
    int const *p;//同上
    p = &b;//正确
    *p = 10;//错误
    
  • 指针常量(const修饰指针,const在*的右边)

    int *const p = &a;//const修饰指针,指针的指向不可以改,但是指针指向的值可以改
    *p = 10;//正确
    p = &b;//错误
    
  • const都修饰指针和常量(指针和常量都不能修改)

    const int *const p;
    int const *const p;
    

1.3 switch语句中case结尾是否必须加break

**一般必须在case结尾加break。**因为通过switch确认入口点,一直往下执行,直到遇见break。否则会执行完这个case后执行后面的case,default也会执行。 注,switch(c),c可以是int、long、char等,但是不能是float

1.4 volatile 的作用

volatile 关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改,比如:操作系统、硬件或者其它线程等。

  • 编译器不再进行优化,从而可以提供对特殊地址的稳定访问。
  • 系统总是重新从它所在的内存读取数据,不会利用cache中原有的数值。
  • 用于多线程被多个任务共享的变量,或者并行设备的硬件寄存器

1.5 断言ASSERT()是什么?

**是一个调试程序使用的宏。**定义在<assert.h>中,用于判断是否出现非法数据。括号内的值 为false(0),程序报错,终止运行。

ASSERT(n != 0);// n为0的时候程序报错
k = 10/n;

ASSERT()在Debug中有,在Release中被忽略。 ASSERT()是宏,assert()是ANSCI标准中的函数,但是影响程序性能。

1.6 枚举变量的值计算

#include<stdio.h>
int main()
{
	enum {a,b=5,c,d=4,e};
	printf("%d %d %d %d %d",a,b,c,d,e); 
	return 0;
}

输出为 0 5 6 4 5

1.7 字符串存储方式

  1. 字符串存储在栈中
char str1[] = "abc";
char str2[] = "abc";
  1. 字符串存储在常量区
char *str3 = "abc";
char *str4 = "abc";
  1. 字符串存储在堆中
char *str5 =char*malloc(4);
strcpy(str5,"abc");
char *str6 =char*malloc(4);
strcpy(str6,"abc");
  1. 字符串是否相等
  • str1 != str2 ,str1和str2是两个字符串的首地址。
  • str3 == srt4 , str3和str4是常量的地址,同样字符串在常量区只存在一份。
  • str5 != str6 ,str5 和str6是指向堆的地址。
    在这里插入图片描述

1.8 程序内存分区

内存高地址 栈区
堆区
全局/静态区 (.bss段 .date段)
常量区
内存低地址 代码区

在这里插入图片描述

  1. 栈区(stack)
  • 临时创建的局部变量存放在栈区。

  • 函数调用时,其入口参数存放在栈区。

  • 函数返回时,其返回值存放在栈区。

  • const定义的局部变量存放在栈区。

  1. 堆区(heap)
  • 堆区用于存放程序运行中被动态分布的内存段,可增可减。

  • malloc函数分布的内存,必须用free进行内存释放,否则会造成内存泄漏。

  1. 全局区(静态区)
  • (c语言中)全局区有.bss段和.data段组成,可读可写。
  • C++不分bss和data
  1. .bss段
  • 未初始化的全局变量存放在.bss段。

  • 初始化为0的全局变量和初始化为0的静态变量存放在.bss段。

  • .bss段不占用可执行文件空间,其内容有操作系统初始化。

  1. .data段
  • 已经初始化的全局变量存放在.data段。

  • 静态变量存放在.data段。

  • .data段占用可执行文件空间,其内容有程序初始化。

  • const定义的全局变量存放在.rodata段。

  1. 常量区
  • 字符串存放在常量区。

  • 常量区的内容不可以被修改。

  1. 代码区
  • 程序执行代码(二进制代码文件)存放在代码区。

1.9 *p++ 和 (*p)++ 的区别

  • *p++ 先完成取地址,然后对指针地址进行++,再取值
  • (*p)++,先完成取值,再对值进行++

1.10 new / delete 与 malloc / free的异同

  • 相同点

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

    • new / delete 是C++运算符,malloc / free是C/C++语言标准库函数

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

    • new是类型安全的,malloc不是。例如:

      int *p = new float[2]; //编译错误
      int *p = (int*)malloc(2 * sizeof(double));//编译无错误
      
    • new调用名为operator new的标准库函数分配足够空间并调用相关对象的构造函数,delete对指针所指对象运行适当的析构函数;然后通过调用名为operator delete的标准库函数释放该对象所用内存。malloc / free均没有相关调用

    • malloc / free需要库文件支持,new / delete不用

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

1.11 exit()和return 的区别

  • return是语言级的,标志调用堆栈的返回。是从当前函数的返回,main()中return的退出程序
  • exit()是函数,强行退出程序,并返回值给系统
  • return实现函数逻辑,函数的输出。exit()只用来退出。

1.12 extern和export的作用

变量的声明有两种情况:

  1. 一种是需要建立存储空间的。例如:int a 在定义的时候就已经建立了存储空间。

  2. 另一种是不需要建立存储空间的。 例如:extern int a 其中变量a是在别的文件中定义的。

  3. 总之就是:把建立空间的声明成为“定义”,把不需要建立存储空间的成为“声明”。

  • extern
    • 普通变量、类。结构体
  • export(C++中新增)
    • 和exturn类似,但是用作模板
    • 使用该关键字可实现模板函数的外部调用
    • 模板实现的时候前面加上export,别的文件包含头文件就可用该模板

1.13 C++中,explicit的作用

  • 隐式转换

    String s1 = "hello";
    //进行隐式转换,等价于
    String s1 = String("hello");
    
  • explicit阻止隐式转换

    class Test1
    {
    publicTest1(int n){ num = n }
    private:
    	int num;
    }
    
    class Test2
    {
    publicexplicit Test2(int n){ num = n }
    private:
    	int num;
    }
    
    int main()
    {
    	Test1 t1 = 1; //正确,隐式转换
    	Test2 t2 = 1;//错误,禁止隐式转换
    	Test2 t2(1); //正确,可与显示调用
    }
    

1.14 C++的异常处理

C++中的异常处理机制主要使用try、throw和catch三个关键字

#include <iostream>
using namespace std;
int main()
{
    double m = 1, n = 0;
    try {
        cout << "before dividing." << endl;
        if (n == 0)
            throw - 1;  //抛出int型异常
        else if (m == 0)
            throw - 1.0;  //拋出 double 型异常
        else
            cout << m / n << endl;
        cout << "after dividing." << endl;
    }
    catch (double d) {
        cout << "catch (double)" << d << endl;
    }
    catch (...) {
        cout << "catch (...)" << endl;
    }
    cout << "finished" << endl;
    return 0;
}
//运行结果
//before dividing.
//catch (...)
//finished

代码中,对两个数进行除法计算,其中除数为0。可以看到以上三个关键字,

  • 程序的执行流程是先执行try包裹的语句块,如果执行过程中没有异常发生,则不会进入任何catch包裹的语句块,如果发生异常,则使用throw进行异常抛出,再由catch进行捕获,
  • throw可以抛出各种数据类型的信息,代码中使用的是数字,也可以自定义异常class。catch根据throw抛出的数据类型进行精确捕获(不会出现类型转换),如果匹配不到就直接报错,可以使用catch(…)的方式捕获任何异常(不推荐)。
  • 当然,如果catch了异常,当前函数如果不进行处理,或者已经处理了想通知上一层的调用者,可以在catch里面再throw异常。

1.15 回调函数

  • 把一段可执行的代码像参数传递那样传给其他代码,而这段代码会在某个时刻被调用执行,这就叫做回调。

  • 如果代码立即被执行就称为同步回调,如果过后再执行,则称之为异步回调。

  • 回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。

  • 主函数和回调函数是在同一层的,而库函数在另外一层。如果库函数对我们不可见,我们修改不了库函数的实现,也就是说不能通过修改库函数让库函数调用普通函数那样实现,那我们就只能通过传入不同的回调函数

  • sort(),中自定义的cmp就是回调函数

2. 内存分配

2.1 C++内存分配

见 1.8

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>