目录
前言
C++入门笔记,引用与内联
一、引用
先看一个阿里巴巴的笔试题:
关于引用以下说法错误的是( E )
A.引用必须初始化,指针不必
B.引用初始化以后不能被改变,指针可以改变所指的对象
C.不存在指向空值的引用,但是存在指向空值的指针
D.一个引用可以看作是某个变量的一个“别名”
E.引用传值,指针传地址
F.函数参数可以声明为引用或指针类型
A.引用必须初始化,必须在定义引用时明确引用的是哪个变量或者对象,否则语法错误,指针不初 始化时值为随机指向
B.引用一旦定义时初始化指定,就不能再修改,指针可以改变指向
C.引用必须初始化,不能出现空引用,指针可以赋值为空
D.简单粗暴的引用理解可以理解为被引用变量或对象的"别名"
E.引用表面好像是传值,其本质也是传地址,只是这个工作有编译器来做,所以错误
F.函数调用为了提高效率,常使用引用或指针作为函数参数传递变量或对象
1.引用的概念
引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间。注意:不会额外开辟空间,内存空间是共用的。
(指针需要开辟空间,引用不需要开辟空间)
类型& 引用变量名(对象名) = 引用实体;
a的别名ra:
void Test()
{
int a = 10;
int& ra = a;//<====定义引用类型
printf("%p\n", &a);
printf("%p\n", &ra);
}
注意:引用类型必须和引用实体是同种类型的
2.引用特性
1). 引用在定义时必须初始化
2). 一个变量可以有多个引用
void Test()
{
int a = 10;
// int& ra; // 该条语句编译时会出错
int& ra = a;
int& rra = a;
printf("%p %p %p\n", &a, &ra, &rra);
}
3). 引用一旦引用一个实体,再不能引用其他实体
b是a的别名,c是b的别名,那么c也是a的别名
b和c都是a的别名
多次初始化是错误的:
4).取别名的原则:对原引用的变量权限只能缩小不能放大
int main()
{
int a = 10;
int& b = a;
//取别名的原则:对原引用的变量权限只能缩小不能放大
const int x = 20;
int& y = x;//放大
const int& y = x;//不变
int c = 30;
const int& d = c;//缩小
}
3.常引用
void Test()
{
const int a = 10;
//int& ra = a; // 该语句编译时会出错,a为常量
const int& ra = a;
// int& b = 10; // 该语句编译时会出错,b为常量
const int& b = 10;
double d = 12.34;
//int& rd = d; // 该语句编译时会出错,类型不同
const int& rd = d;
}
常数也可以有别名,但涉及到别名权限的问题,即
对原引用的变量权限只能缩小不能放大。
int main()
{
int a = 10;
int& b = a;
//int& c = 20;
const int& c = 20;//常数也可以是别名,但需要加上const
double d = 2.2;
int f = d;//从d赋值给f,从double到int,会创建一个临时变量,d会将整数部分四个字节给f
//int& e = d;//而临时变量具有常性,是只读的,根据引用不可放大权限的原则,需加上const
const int& e = d;
return 0;
}
临时变量具有常性,需要加上const
注意:int ret = count();//错误,是因为,首先创建了main的栈帧,里有ret,ret从count的栈帧中找到返回值n,但是n出了栈帧就销毁了,所以编译器会创建临时变量,该临时变量可能在寄存器中,但如果数据大,也可能出现在main的栈帧中,提前开辟好空间。
int count()
{
int n = 0;
n++;
return n;//n拷贝给临时变量再拷贝给ret
}
int main()
{
const int& ret = count();//临时变量具有常性,所以需要加上const
return 0;
}
4.经典面试题
以下代码输出的结果?//7
int& Add(int a, int b)
{
int c = 0;
c = a + b;
return c;
}
int main()
{
int& ret = Add(1, 2);
Add(3, 4);
//第一次先传值,得到7
cout << ret << endl;
cout << &ret << endl;
cout << ret << endl;
cout << &ret << endl;
//取值地址一致,说明栈帧销毁,空间还在,就好像上课完教室还在
return 0;
}
在这个Add函数中,变量c是在函数体中刚刚创建的,我们将c的引用返回后,函数生命结束,c这块空间是要被释放掉的,这里的释放是指我们要将这块空间归还给操作系统,这块空间在被操作系统重新分配利用之前它里面的值确实不会变,但这并不代表我们有权利去访问它。就好比我们出门在外要住酒店,住宿结束后要进行归还。但酒店房间依然还在,只不过我们不能再进了(你有本事能进去也行,但是就造成了非法内存访问);
至于为什么ret第一次的还是7,见下图解释即可:
但是对于以上代码进行一定的修改就可能会导致输出的不是7,因为可能会覆盖到Add所在的空间,那么c变成随机值
综上,就能解释下述笔试题:
为什么值不同?发现2在1的前一句就是值相同,但是在下面就不同
int& count()
{
int n = 0;
n++;
return n;
}
int main()
{
int& ret = count();
cout << ret << endl;
cout << &ret << endl;//1
cout << ret << endl;//2
return 0;
}
因为 第二次调用,压栈导致覆盖了第一次的值,出现随机值
5.引用与指针的不同点
1. 引用概念上定义一个变量的别名,指针存储一个变量地址。
2. 引用在定义时必须初始化,指针没有要求
3. 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型
实体
4. 没有NULL引用,但有NULL指针
5. 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占
4个字节)
6. 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
7. 有多级指针,但是没有多级引用
8. 引用比指针使用起来相对更安全
9. 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
指针通过某个指针变量指向一个对象后,对它所指向的变量间接操作。程序中使用指针,程序的可读性差;而引用本身就是目标变量的别名,对引用的操作就是对目标变量的操作
二、内联
以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率。 解决宏函数看不明白,容易写错的问题,下图这种情况下就很适合使用内联函数。
但是要注意:内联的定义和声明不能分开。
当编译器发现某段代码在调用一个内联函数时,它不是去调用该函数,而是将该函数的代码,整段插入到当前位置。这样做的好处是省去了调用的过程,加快程序运行速度。(函数的调用过程,由于参数入栈等操作,所以总要多占用一些时间)。这样做的不好处:由于每当代码调用到内联函数,就需要在调用处直接插入一段该函数的代码,所以程序的体积将增大。
拿生活现象比喻,就像电视坏了,通过电话找修理工来,你会嫌慢,于是干脆在家里养了一个修理工。这样当然是快了,不过,修理工住在你家可就要占地儿了。内联函数并不是必须的,它只是为了提高速度而进行的一种修饰。要修饰一个函数为内联型,使用如下格式:
inline 函数的声明或定义
简单一句话,在函数声明或定义前加一个 inline 修饰符。
inline int max(int a, int b)
{
return (a>b)? a : b;
}
内联函数的本质是,节省时间但是消耗空间。
使用内联函数时应注意以下几个问题:
(1) 在一个文件中定义的内联函数不能在另一个文件中使用。它们通常放在头文件中共享。
(2) 内联函数应该简洁,只有几个语句,如果语句较多,不适合于定义为内联函数。
(3) 内联函数体中,不能有循环语句、if语句或switch语句,否则,函数定义时即使有inline关键字,编译器也会把该函数作为非内联函数处理。
(4) 内联函数要在函数被调用之前声明。关键字inline 必须与函数定义体放在一起才能使函数成为内联,仅将inline 放在函数声明前面不起任何作用。
inline仅做为一种“请求”,特定的情况下,编译器将不理会inline关键字,而强制让函数成为普通函数。出现这种情况,编译器会给出警告消息。
在你调用一个内联函数之前,这个函数一定要在之前有声明或已定义为inline,如果在前面声明为普通函数,而在调用代码后面才定义为一个inline函数,程序可以通过编译,但该函数没有实现inline。比如下面代码片段:
//函数一开始没有被声明为inline:
void foo();
//然后就有代码调用它:
foo();
//在调用后才有定义函数为inline:
inline void foo()
{
......
}
代码是的foo()函数最终没有实现inline;
2.1 关于c++的inline关键字,以下说法正确的是( C)
A.使用inline关键字的函数会被编译器在调用处展开
B.头文件中可以包含inline函数的声明
C.可以在同一个项目的不同源文件内定义函数名相同但实现不同的inline函数
D.递归函数也都可以成为inline函数
A.不一定,因为inline只是一种建议,需要看此函数是否能够成为内联函数
B. inline函数不支持声明和定义分离开,因为编译器一旦将一个函数作为内联函数处理,就会在调用位置展开,即该函数是没有地址的,也不能在其他源文件中调用,故一般都是直接在源文件中定义内联函数的
C.inline函数会在调用的地方展开,所以符号表中不会有inline函数的符号名,不存在链接冲突。
D.比较长的函数,递归函数就算定义为inline,也会被编译器忽略,故错误
内联能提高函数的执行效率,为什么不把所有的函数都定义成内联函数?
如果所有的函数都是内联函数,还用得着“内联”这个关键字吗?内联是以代码膨胀(复制)为代价,仅仅省去了函数调用的开销,从而提高函数的执行效率。如果执行函数体内代码的时间,相比于函数调用的开销较大,那么效率的收获会很少。另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。
原文链接:https://blog.csdn.net/seadplus/article/details/7785773
int Add(int x, int y)
{
int z = x + y;
return z;
}
int main()
{
Add(1, 2);
Add(1, 2);
Add(1, 2);
Add(1, 2);
Add(1, 2);
Add(1, 2);
Add(1, 2);
Add(1, 2);
Add(1, 2);
return 0;
}
宏的优缺点?
优点:
1.增强代码的复用性。2.提高性能。
缺点:
1.不方便调试宏。(因为预编译阶段进行了替换)2.导致代码可读性差,可维护性差,容易误用。3.没有类型安全的检查 。
C++有哪些技术替代宏?
1. 常量定义 换用const enum
2. 短小函数定义 换用内联函数