C++引用与内联

目录

前言

一、引用

1.引用的概念

2.引用特性

3.常引用

4.经典面试题

5.引用与指针的不同点​编辑

二、内联

2.1 关于c++的inline关键字,以下说法正确的是( C)      

宏的优缺点?

前言

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. 短小函数定义 换用内联函数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值