举例说明1.引用和指针的权限放大和缩小问题(放大不行缩小可以)代码语言javascriptAI代码解释#includeiostream using namespace std; int main() { const int a 0; //权限放大(不能) //int b a; int c 0; //权限缩小能 const int d c; return 0; }代码语言javascriptAI代码解释//权限的放大和缩小只存在于const指针和const引用 //我们再来看看指针 #includeiostream using namespace std; int main() { const int a 0; const int* p1 a; //int p2 p1; //这个也属于权限的放大得写成下面这样 const int* p2 p1; //但是权限缩小还是可以的 int c 0; int* p3 c; const int* p4 p3; return 0; }2.const可以引用常量,作为函数参数时如果不是为了让形参的改变可以影响实参,是可以const修饰引用的这样传参的时候选择更多 代码语言javascriptAI代码解释#includeiostream using namespace std; int main() { int i 0; double d i;//这个是可以通过编译的涉及隐式类型转换因为int和double本质上都是关于数据类型大小的。 //像整型和指针就只能用强制类型转换,如下 int p (int)i; //但是我们再来看看引用里面的使用 int j 1; //double rd j;//不行 const double rd j;//这个就可以了 //为什么呢?--我们先不急再看一个例子 //int rp (int)j;//不行 const int rp (int)j;//可以 //-------------------------具体原因分析(配合图片)------------------------------------ //这是因为在引用里面,转换的过程中会产生一个临时对象保存中间值。 //所以实际上rbrp引用的都是中间值在C里这个临时对象是具有常性的(即被const修饰) //因此我们这里如果直接转换的话就会出现权限放大的错误我们必须使用常引用(即const修饰) return 0; }图示如下指针和引用的关系(面试考点)C中指针和引用就像两个性格迥异的亲兄弟指针是哥哥引用是弟弟在实践中他们相辅相成相得益彰。功能有重叠性但是也有各自的特点互相不可替代语法概念上引用是一个变量的取别名不开空间指针是存储⼀个变量地址要开空间。(我们一般尽量取谈语法层底层只在一些特殊场景下用来辅助了解)引用在定义时必须初始化指针建议初始化但是语法上不是必须的。引用在初始化时引用⼀个对象后就不能再引用其他对象而指针可以在不断地改变指向对象。指针很容易出现空指针和野指针的问题引用很少出现引用使用起来相对更安全⼀些。引用可以直接访问指向对象指针需要解引用才是访问指向对象。sizeof中含义不同引用结果为引用类型的大小但指针始终是地址空间所占字节个数(32位平台下占4个字节64位下是8byte)二.inline内联函数关键点用inline修饰的函数叫做内联函数编译时C编译器会在调用的地方展开内联函数这样调用内联函数就不需要建立栈帧了可以提高效率。inline对于编译器而言只是一个建议也就是说你加了inline编译器也可以选择在调用的地方不展开不同编译器关于inline什么情况展开各不相同因为C标准没有规定这个。inline适用于频繁调用的短小函数对于递归函数代码相对多⼀些的函数加上inline也会被编译器忽略。C语言实现宏函数也会在预处理时替换展开但是宏函数实现很复杂很容易出错的且不方便调试C设计了inline目的就是替代C的宏函数。vs编译器 debug版本下面默认是不展开inline的这样方便调试debug版本想展开需要设置⼀下两个地方(后面的举例说明中会有)。inline不建议声明和定义分离到两个文件分离会导致链接错误。因为inline被展开就没有函数地址链接时会出现报错。举例说明先来看看之前C语言中宏函数里面的一些坑吧以ADD函数为例错误一代码语言javascriptAI代码解释#define ADD(int x,int y) return xy;这个错误很离谱我们要牢记宏是一种替换机制这里直接写成了一个函数很明显是错误的错误二代码语言javascriptAI代码解释//宏是一种替换机制 //#define ADD(int x,int y) return xy; //错误写法二 //#define ADD(a,b) ab; //宏定义不要带分号 //我们把分号去掉但是还是有问题的 #define ADD(a,b) ab using namespace std; int main() { int ret1 ADD(1, 2); //展开之后:int ret1 1 2;;,会出现两个分号这里还不会报错我们再来看看下面的 //int ret2 ADD(1, 2) * 3;//这里就出问题了 //我们就算不带分号上面这个ret2最后的值也是错的 int ret2 ADD(1, 2) * 3;//我们想要得到的是9但是我们打印出来是7 cout ret2 endl; //因为展开之后:1 2 * 3 7//这里的优先级被影响了 return 0; }宏定义时不要加分号还需要加上来保持优先级错误三代码语言javascriptAI代码解释#define ADD(a,b) (ab) #includeiostream using namespace std; int main() { //这样写ret2打印出来的结果是我们想要的9 int ret2 ADD(1, 2) * 3; cout ret2 endl; //但是这种写法还是存在一些问题的 int x 0, y 1; ADD(x | y, x y); //展开会变成:(x | y x y) //号的优先级高于 |和 所以这里相当于x|(yx)y return 0; }带上了外面的括号ret2的问题解决了。但是在一些场景下还是有问题正确写法代码语言javascriptAI代码解释//正确写法: #define ADD(a,b) ( (a) (b) ) #includeiostream using namespace std; int main() { //这样写ret2打印出来的结果是我们想要的9 int ret2 ADD(1, 2) * 3; cout ret2 \n; //这种写法也没问题了 int x 0, y 1; ADD(x | y, x y); //展开会变成:( (x | y) (x y) ),符合我们的目的 return 0; }Tips: 宏函数这么复杂容易写出问题还不能调试。 那我们为什么还要用它呢它的优势在于什么呢优点:高频调用小函数时写成宏函数可以提高效率预处理阶段宏会替换提高效率不建立栈帧我们在C中使用inline内联函数代替宏函数该怎么写代码语言javascriptAI代码解释inline int ADD(int x, int y) { return x y; }和函数的写法差不多但是是不一样的。它编译是直接展开的跟宏一样,不会创建栈帧空间因为默认debug版本下为了方便调试inline也不展开。我们需要完成两设置代码代码语言javascriptAI代码解释#includeiostream using namespace std; //转反汇编看发现还是有call还是创建了栈帧这是为什么 inline int ADD(int a, int b) { return a b; } //因为默认debug版本下为了方便调试inline也不展开。 //我们需要设置一下--这里大家可以自己测试看看最号好用低版本的vs int main() { int ret2 ADD(1, 2) * 3; cout ret2 \n;//打印出来也是9完全没有问题 return 0; }设置步骤右键单击解决方案资源管理器中的项目选择“属性”。在弹出的属性对话框中找到“C/C”选项卡点击“常规”。在“调试信息格式”下拉菜单中选择“程序数据库(/Zi)”。接着点击“C/C”下的“优化”选项。在“内联函数的扩展”下拉菜单中选择“只适用于_inline(/Ob1)”。inline只是一个建议展开还是创建空间由编译器说的算递归和代码多的函数可能就不会展开代码语言javascriptAI代码解释#includeiostream using namespace std; inline int ADD(int a, int b) { a * 2; a * 2; a * 2; a * 2; a * 2; a * 2; a * 2; a * 2; a * 2; a * 2; //5个的时候还是可以展开的10个就不行了 return a b; } int main() { int ret2 ADD(1, 2) * 3; cout ret2 \n; return 0; }图示如下思考为什么只是建议呢如果完全交给程序员可能会出现代码指令恶性膨胀的问题导致可执行程序(安装包)过大这是特别不好的。所以编译器会自己把握这个展开还是不展开有其自己的逻辑和判断。inline不建议声明和定义放离到两个文件分离会导致链接错误。因为inline被展开就没有函数地址链接时会出错(注意看注释)拿顺序表为例我直接给正确改法了然后它的.cpp文件和.h文件这里是截图的SeqList.h(内联函数直接在.h文件中实现就可以了)SeqList.cpp(在.cpp文件中不需要再实现内联函数了可以看出我这里注释掉了)test.cpp代码语言javascriptAI代码解释#includeSeqList.h int main() { SL s; //我实现用的引用所以不用传地址 SLInit(s); // call 地址 return 0; }三.nullptrNULL实际是⼀个宏在传统的C头文件(stddef.h)中可以看到如下代码关键点C中NULL可能被定义为字面常量0或者C中被定义为无类型指针(void*)的常量。不论采取何种定义在使用空值的指针时都不可避免的会遇到⼀些麻烦本想通过f(NULL)调⽤指针版本的f(int*)函数但是由于NULL被定义成0调用了f(int x)因此与程序的初衷相悖。f((void*)NULL);调用会报错。C11中引入nullptrnullptr是⼀个特殊的关键字nullptr是⼀种特殊类型的字面量它可以转换成任意其他类型的指针类型。使用nullptr定义空指针可以避免类型转换的问题因为nullptr只能被隐式地转换为指针类型而不能被转换为整数类型。举例说明代码语言javascriptAI代码解释#includeiostream using namespace std; void f(int x) { cout f(int x) endl; } void f(int* ptr) { cout f(int* ptr) endl; } int main() { f(0); f(NULL); //f((void*)0);--有个图片 //用上面的都会执行出来函数1而不会是函数2 f(nullptr);//但是用nullptr就很清晰了可以很好处理这个问题 int* p1 NULL; char* p2 NULL; //以后我们在C里面置为空都这样写 int* p3 nullptr; char* p4 nullptr; return 0; }