C++初阶extern C,引用,内联函数,auto和指针空值
Posted 林慢慢i
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++初阶extern C,引用,内联函数,auto和指针空值相关的知识,希望对你有一定的参考价值。
前言:紧跟上一篇文章,补完C++入门初期的零散知识点,本章知识点包括extern “C”、引用、内联函数、auto等内容!
1.extern “C”
我们知道,在C++代码当中可以调用C语言部分,但是在C语言编写的代码当中无法直接调用纯C++语言编写的内容,如何解决这一问题呢?
在C++模块前加上extern "C"后,会指示编译器这部分代码按C语言的进行编译,而不是C++的,这样C语言便可以调用这部分C++编写的模块。
比如:
tcmalloc
是google用C++实现的一个项目,他提供tcmallc()
和tcfree()
两个接口来使用,但如果是C项目就没办法直接使用,这时候就可以使用extern “C”
来解决。
实例
//引入extern "C" 是告诉编译器其修饰下的函数的命名修饰规则按照C语言下的命名规则进行
//即此时的函数add在转化到符号表中后为_Add 而非 _Z3Addii
extern "C" int add(int a, int b)
{
return a + b;
}
int main()
{
int ret=add(1, 2);
return 0;
}
2.引用
2.1 引用的概念
引用不同于指针,引用并不会定义新的变量,而是给原来的变量起一个“别名”,就好像华为公司,又被称为“菊厂”、“沸腾厂”等,而这些称呼指的对象都是华为公司,本质是一样的。因此编译器并不会为引用变量单独开辟一块内存空间,该变量与它引用的变量共用同一块内存空间。
引用的符号为&,其使用方法是 类型 + & + 引用变量名称(对象名) = 引用实体; (引用实体的类型必须和
&
前的类型保持一致)
示例代码:
int main()
{
// 一定要注意这里跟C取地址用了一个符号 &
// 但是他们之前没有关联,各个各用处
int a = 10;
int& b = a;
int& c = a;
int& d = b;
c = 20;
d = 30;
int& e;
return 0;
}
观察到初始值均为10
当执行完 c=20之后,均为20
当执行完 d=30之后,均为30
2.2 引用的特性
1.引用在定义同时必须要初始化。比如在取别名的时候就需要有这个被取别名的对象。
2.一个引用实体可以有多个引用。
3.一旦引用变量成为了一个实体的引用,就不能再成为其他实体的引用。
示例代码:
int main()
{
int a = 10;
int d = 100;
int& b = a;
//一旦写了引用,就必须有完整的实体,不能写成 int& b; 这是不允许的,即第一条特性
int& c = a;
//a变量被引用了两次,也就是第二条特性意思
c = d;
//前面c已经成了a的别名,那么c就永远只能是a的别名,只不过这里C的值变成了100(同样ab也变成100). 第三 条特性意思
return 0;
}
来一道测试题看看,分别画出分割线之前和之后各个变量的图示
int x = 0,y = 1;
int* p1 = &x;
int* p2 = &y;
int*& p3 = p1;
/————————————————分割线——————————————/
*p3 = 10;
p3 = p2;
分割线之前的:
分割线之后的:P3、P1均指向了y
2.3 常引用
常数无法直接引用,引用前需要加上const构成常引用,如下:
const int a = 10;
int& ra = a; //该句代码编译出错,因为a为常量
const int& ra = a; //成功引用
int& b = 100; //引用失败,因为100是常数,无法int引用
const int& rb = 100; //成功引用
总结:可以缩小读写权限,但不能放大读写权限.
2.4 引用的使用场景
(1)作参数
根据以上特性,引用在作参数过程中,引用能发挥哪些作用呢?
1.可以减少传参拷贝(引用作用)
2.可以保护形参不被修改,既可以接收变量,又可以接收常量(常量引用作用).
代码1:减少传参拷贝
struct node //某个结构体,假设他很大
{
int val;
struct node* next;
};
//某函数定义如下: 如果其参数设置为引用,将不需要通过函数传递方式中的值传递(拷贝),造成空间消耗巨大.
void modify(struct node& node0)
{
//此处省略相关操作....
}
代码2:保护形参不被修改,既可以接收变量,又可以接收常量
int add(const int& a,const int& b)
{
return a-b; //比如加法函数,如果手误,码码错代码,修改了a或b的值,编译器会自动提示.
}
int main()
{
int a = 10;
int b = 20;
cout<<"变量作为实参"<<add(a,b)<<endl;
cout<<"常量作为实参"<<add(10,20)<<endl; //必须是常量引用,否则将无法接收实参.
return 0;
}
(2)作返回值
首先来看看下面这段代码是否正确:
int Add(int x, int y)
{
int z = x + y;
return z;
}
int main()
{
int a = 10;
int b = 20;
int& ra = Add(a, b);
cout << ra << endl;
}
编译器对上述程序会报错!这是啥原因?
函数调用是会建立栈帧的,而栈帧在函数调用结束后会销毁并将这块栈帧还给操作系统,那么函数中创建的变量也会被销毁。
但是为什么函数的返回值还能被接收呢?
这是因为系统会创建一个临时变量,函数返回值会赋给这个临时变量,同时这个临时变量又会赋给要赋给的变量;而这种临时变量具有常性,只能赋给了一个类型为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声明的变量必须由编译器在编译时期推导而得.
int main()
{
int a = 10;
auto b = a; //编译器会自行推导数据类型
auto c = 'c'; //编译器会自行推导数据类型
auto d = 3.0; //编译器会自行推导数据类型
cout << typeid(b).name() << endl;//显示变量类型
cout << typeid(c).name() << endl;
cout << typeid(d).name() << endl;
return 0;
}
4.2 auto的使用细则
4.2.1 auto与指针和引用结合起来使用
用auto声明
指针类型
时,auto和auto * 没有任何区别,但用auto声明引用类型时则必须加&
int main()
{
int a = 10;
auto b = &a;
auto* c = &a;
auto& d = a;
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
cout << typeid(d).name() << endl;
return 0;
}
4.2.2 在同一行定义多个变量
当在同一行声明多个变量时,这些变量必须是
相同的类型
,否则编译器将会报错
,因为编译器实际只对第一个类型进行推导,然后用推导出来的类型定义其他变量
int main()
{
auto a = 10, b = 20;
auto c = 3, d = 4.0;//编译错误,c和d的初始表达式类型不同
return 0;
}
4.3 auto不能推导的场景
4.3.1 auto不能作为函数的参数,不能直接用来声明数组.
void TestAuto(auto c)//错误
{
int a[] = {1,2,3};
auto b[] = {4,5,6};
}
4.3.2 auto声明的变量不能作为函数的形参类型
这是因为在编译阶段编译器无法推导形参的类型。
int Count(auto n)//错误
{
return 5;
}
int main()
{
int n = 0;
Count(n);
rrturn 0;
}
5.指针空值nullptr
在良好的C/C++编程习惯中,声明一个变量时最好给该变量一个合适的初始值,否则可能会出现不可预料的错误,比如未初始化的指针。如果一个指针没有合法的指向,我们基本都是按照如下方式对其进行初始化:
void TestPtr()
{
int* p1 = NULL;
int* p2 = 0;
}
实际上,NULL是一个宏,在C语言的头文件(stddef.h)中我们可以看到:
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
可以看到,在C++中,NULL被定义为字面常量0,其他情况下,NULL被无指针类型(void * )的常量0,无论使用何种定义,在使用NULL作为空指针时,总会不可避免的出现一些问题。
比如下例程序,大家现在猜猜输出结果会是啥?
void f(int x)
{
cout<<"f(int)"<<endl;
}
void f(int* x)
{
cout<<"f(int*)"<<endl;
}
int main()
{
f(0);
f(NULL);
f((int*)NULL);
return 0;
}
我们传参NULL
时候,本意是想调用第二个函数,但是编译器却认为我们想要调用第一个函数,这就是在C语言中使用NULL的缺陷,因此,C++提出了nullptr
代替NULL
。
在C++98中,编译器默认将NULL看成一个整型常量,在函数重载的作用下,编译器无法通过NULL来调用参数为指针类型的Test函数,这会产生歧义。如果要将其按照指针方式来使用,必须对其进行强转(void * )0。
需要注意:
1.nullptr在C++11中是作为新关键字引入的,因此在使用其表示空指针时,无需包含头文件。
2.在C++11中,sizeof(nullptr)与sizeof((void* )0)大小相同。
3.为了提高代码的健壮性,后续代码中表示指针空值时最好使用nullptr。
6.习题(选择题,别慌!)
练习题1
解析:
练习题2:
解析:
练习题3:
解析:
练习题4:
解析:
练习题5:
解析:
练习题6:
解析:
练习题7:
解析:
练习题8:
解析:
练习题9:
解析:
C++的extern “C”、引用、内联函数、auto内容到此介绍结束了,感谢您的阅读!!!如果内容对你有帮助的话,记得给我三连(点赞、收藏、关注)——做个手有余香的人。
以上是关于C++初阶extern C,引用,内联函数,auto和指针空值的主要内容,如果未能解决你的问题,请参考以下文章
C++ 初阶 新语言入门介绍:命名空间,(全/半)缺省函数,函数重载,引用,内联,auto
C++ 初阶 新语言入门介绍:命名空间,(全/半)缺省函数,函数重载,引用,内联,auto