【C++初阶】extern C,引用,内联函数,auto和指针空值

x33g5p2x  于2021-10-14 转载在 C/C++  
字(4.9k)|赞(0)|评价(0)|浏览(556)

1.extern “C”

我们知道,在C代码当中可以调用C语言部分,但是在C语言编写的代码当中无法直接调用纯C语言编写的内容,如何解决这一问题呢?
在C模块前加上extern "C"后,会指示编译器这部分代码按C语言的进行编译,而不是C的,这样C语言便可以调用这部分C++编写的模块。

比如:tcmalloc是google用C++实现的一个项目,他提供tcmallc()tcfree()
两个接口来使用,但如果是C项目就没办法直接使用,这时候就可以使用extern “C”来解决。

实例

  1. //引入extern "C" 是告诉编译器其修饰下的函数的命名修饰规则按照C语言下的命名规则进行
  2. //即此时的函数add在转化到符号表中后为_Add 而非 _Z3Addii
  3. extern "C" int add(int a, int b)
  4. {
  5. return a + b;
  6. }
  7. int main()
  8. {
  9. int ret=add(1, 2);
  10. return 0;
  11. }

2.引用

2.1 引用的概念

引用不同于指针,引用并不会定义新的变量,而是给原来的变量起一个“别名”,就好像华为公司,又被称为“菊厂”、“沸腾厂”等,而这些称呼指的对象都是华为公司,本质是一样的。因此编译器并不会为引用变量单独开辟一块内存空间,该变量与它引用的变量共用同一块内存空间。
引用的符号为&,其使用方法是 类型 + & + 引用变量名称(对象名) = 引用实体; (引用实体的类型必须和&前的类型保持一致)

示例代码:

  1. int main()
  2. {
  3. // 一定要注意这里跟C取地址用了一个符号 &
  4. // 但是他们之前没有关联,各个各用处
  5. int a = 10;
  6. int& b = a;
  7. int& c = a;
  8. int& d = b;
  9. c = 20;
  10. d = 30;
  11. int& e;
  12. return 0;
  13. }

观察到初始值均为10

当执行完 c=20之后,均为20

当执行完 d=30之后,均为30

2.2 引用的特性

1.引用在定义同时必须要初始化。比如在取别名的时候就需要有这个被取别名的对象。
2.一个引用实体可以有多个引用。
3.一旦引用变量成为了一个实体的引用,就不能再成为其他实体的引用。

示例代码:

  1. int main()
  2. {
  3. int a = 10;
  4. int d = 100;
  5. int& b = a;
  6. //一旦写了引用,就必须有完整的实体,不能写成 int& b; 这是不允许的,即第一条特性
  7. int& c = a;
  8. //a变量被引用了两次,也就是第二条特性意思
  9. c = d;
  10. //前面c已经成了a的别名,那么c就永远只能是a的别名,只不过这里C的值变成了100(同样ab也变成100). 第三 条特性意思
  11. return 0;
  12. }

来一道测试题看看,分别画出分割线之前和之后各个变量的图示

  1. int x = 0,y = 1;
  2. int* p1 = &x;
  3. int* p2 = &y;
  4. int*& p3 = p1;
  5. /————————————————分割线——————————————/
  6. *p3 = 10;
  7. p3 = p2;

分割线之前的:

分割线之后的:P3、P1均指向了y

2.3 常引用

常数无法直接引用,引用前需要加上const构成常引用,如下:

  1. const int a = 10;
  2. int& ra = a; //该句代码编译出错,因为a为常量
  3. const int& ra = a; //成功引用
  4. int& b = 100; //引用失败,因为100是常数,无法int引用
  5. const int& rb = 100; //成功引用

总结:可以缩小读写权限,但不能放大读写权限.

2.4 引用的使用场景

(1)作参数

根据以上特性,引用在作参数过程中,引用能发挥哪些作用呢?

1.可以减少传参拷贝(引用作用)
2.可以保护形参不被修改,既可以接收变量,又可以接收常量(常量引用作用).

代码1:减少传参拷贝

  1. struct node //某个结构体,假设他很大
  2. {
  3. int val;
  4. struct node* next;
  5. };
  6. //某函数定义如下: 如果其参数设置为引用,将不需要通过函数传递方式中的值传递(拷贝),造成空间消耗巨大.
  7. void modify(struct node& node0)
  8. {
  9. //此处省略相关操作....
  10. }

代码2:保护形参不被修改,既可以接收变量,又可以接收常量

  1. int add(const int& a,const int& b)
  2. {
  3. return a-b; //比如加法函数,如果手误,码码错代码,修改了a或b的值,编译器会自动提示.
  4. }
  5. int main()
  6. {
  7. int a = 10;
  8. int b = 20;
  9. cout<<"变量作为实参"<<add(a,b)<<endl;
  10. cout<<"常量作为实参"<<add(10,20)<<endl; //必须是常量引用,否则将无法接收实参.
  11. return 0;
  12. }

(2)作返回值

首先来看看下面这段代码是否正确:

  1. int Add(int x, int y)
  2. {
  3. int z = x + y;
  4. return z;
  5. }
  6. int main()
  7. {
  8. int a = 10;
  9. int b = 20;
  10. int& ra = Add(a, b);
  11. cout << ra << endl;
  12. }

编译器对上述程序会报错!这是啥原因?

函数调用是会建立栈帧的,而栈帧在函数调用结束后会销毁并将这块栈帧还给操作系统,那么函数中创建的变量也会被销毁。

但是为什么函数的返回值还能被接收呢?

这是因为系统会创建一个临时变量,函数返回值会赋给这个临时变量,同时这个临时变量又会赋给要赋给的变量;而这种临时变量具有常性只能赋给了一个类型为const int 的临时变量,而ra作为int类型的引用,显然是无法接收这个具有常性的临时变量

注意:当引用作为函数返回值时,被引用的对象其作用域必须是有效范围,所以返回一个对局部变量的引用是不合法的,应该是返回值为全局变量或则static修饰的变量.

引用作为引用实体的别名,没有独立空间,与实体共用同一块空间,但是在底层实现上,引用和指针的实现方式是一样的(可以通过编译器反汇编观察到)。

2.5 那么引用与指针有哪些区别?

(1)引用需要初始化,而指针没有要求。
(2)引用一旦作为一个引用实体的引用,就不能再作为其他实体的引用,但指针可以修改其所指向的对象的。
(3)引用没有独立空间,而指针有,但是引用的效率也会更高(毕竟少开辟了一大块内存空间)。
(4)对于sizeof,引用变量的大小与类型有关,指针变量的大小与类型无关。
(5)对于自加,引用加一是数值上加一,而指针加一是跳过一个类型的大小。
(6)访问实体的方式不同,指针是通过解引用访问,而引用是编译器自己处理。
(7)引用使用起来相对于指针更安全。

3.内联函数

3.1 概念

一种通过inline修饰的函数,C++编译器进行编译时可以直接在函数调用的地方进行展开,减少了多余的函数栈帧开销,提高了程序运行效率

3.2 三个特性

一、 inline 是一种以空间换时间的做法,省去调用函数的开销。所以代码很长或者有循环/递归的函数不适宜使用作为内联函数。
二、inline 对于编译器而言只是一个建议,编译器会自动优化,如果定义为inline 的函数体内有循环/递归等等,编译器优化时会忽略掉内联。
三、inline 不建议声明和定义分离,分离会导致链接错误。因为inline 被展开,就没有函数地址了,链接就会找不到。

4.auto关键字

4.1 概念

一个新的类型指示符,auto声明的变量必须由编译器在编译时期推导而得.

  1. int main()
  2. {
  3. int a = 10;
  4. auto b = a; //编译器会自行推导数据类型
  5. auto c = 'c'; //编译器会自行推导数据类型
  6. auto d = 3.0; //编译器会自行推导数据类型
  7. cout << typeid(b).name() << endl;//显示变量类型
  8. cout << typeid(c).name() << endl;
  9. cout << typeid(d).name() << endl;
  10. return 0;
  11. }

4.2 auto的使用细则

4.2.1 auto与指针和引用结合起来使用

用auto声明指针类型时,auto和auto / 没有任何区别,但用auto声明引用类型时则必须加&*

  1. int main()
  2. {
  3. int a = 10;
  4. auto b = &a;
  5. auto* c = &a;
  6. auto& d = a;
  7. cout << typeid(b).name() << endl;
  8. cout << typeid(c).name() << endl;
  9. cout << typeid(d).name() << endl;
  10. return 0;
  11. }

4.2.2 在同一行定义多个变量

当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量

  1. int main()
  2. {
  3. auto a = 10, b = 20;
  4. auto c = 3, d = 4.0;//编译错误,c和d的初始表达式类型不同
  5. return 0;
  6. }

4.3 auto不能推导的场景

4.3.1 auto不能作为函数的参数,不能直接用来声明数组.

  1. void TestAuto(auto c)//错误
  2. {
  3. int a[] = {1,2,3};
  4. auto b[] = {456};
  5. }

4.3.2 auto声明的变量不能作为函数的形参类型

这是因为在编译阶段编译器无法推导形参的类型。

  1. int Count(auto n)//错误
  2. {
  3. return 5;
  4. }
  5. int main()
  6. {
  7. int n = 0;
  8. Count(n);
  9. rrturn 0;
  10. }

5.指针空值nullptr

在良好的C/C++编程习惯中,声明一个变量时最好给该变量一个合适的初始值,否则可能会出现不可预料的错误,比如未初始化的指针。如果一个指针没有合法的指向,我们基本都是按照如下方式对其进行初始化:

  1. void TestPtr()
  2. {
  3. int* p1 = NULL;
  4. int* p2 = 0;
  5. }

实际上,NULL是一个宏,在C语言的头文件(stddef.h)中我们可以看到:

  1. #ifndef NULL
  2. #ifdef __cplusplus
  3. #define NULL 0
  4. #else
  5. #define NULL ((void *)0)
  6. #endif
  7. #endif

可以看到,在C++中,NULL被定义为字面常量0,其他情况下,NULL被无指针类型(void / )的常量0,无论使用何种定义,在使用NULL作为空指针时,总会不可避免的出现一些问题*。

比如下例程序,大家现在猜猜输出结果会是啥?

  1. void f(int x)
  2. {
  3. cout<<"f(int)"<<endl;
  4. }
  5. void f(int* x)
  6. {
  7. cout<<"f(int*)"<<endl;
  8. }
  9. int main()
  10. {
  11. f(0);
  12. f(NULL);
  13. f((int*)NULL);
  14. return 0;
  15. }

我们传参NULL时候,本意是想调用第二个函数,但是编译器却认为我们想要调用第一个函数,这就是在C语言中使用NULL的缺陷,因此,C++提出了nullptr代替NULL
在C++98中,编译器默认将NULL看成一个整型常量,在函数重载的作用下,编译器无法通过NULL来调用参数为指针类型的Test函数,这会产生歧义。如果要将其按照指针方式来使用,必须对其进行强转(void /* )0。

需要注意:

1.nullptr在C11中是作为新关键字引入的,因此在使用其表示空指针时,无需包含头文件。
2.在C
11中,sizeof(nullptr)与sizeof((void/* )0)大小相同。
3.为了提高代码的健壮性,后续代码中表示指针空值时最好使用nullptr。

6.习题(选择题,别慌!)

练习题1

解析:

练习题2:

解析:

练习题3:

解析:

练习题4:

解析:

练习题5:

解析:

练习题6:

解析:

练习题7:

解析:

练习题8:

解析:

练习题9:

解析:

C++的extern “C”、引用、内联函数、auto内容到此介绍结束了,感谢您的阅读!!!如果内容对你有帮助的话,记得给我三连(点赞、收藏、关注)——做个手有余香的人。

相关文章