震惊!一男子深夜燥热难耐,竟然偷偷起身打开电脑并开始 学习c++入门基础(下)

1. 引用

1.1基本概念

引用,就是给已经存在的变量取别名

编译器不会为引用变量开辟新的空间,而是和被引用的变量公用一块内存空间。

使用方式: 类型& 引用别名= 被引用对象;

C++为了避免产生过多的的运算符,于是用的是和取地址相同的符号&,使用时需要注意区分。

#include<iostream>
using namespace std;

int main()
{
	int a = 0;
	int& b = a;
	int& c = b;
	c++;
	cout << &a << endl;
	cout << &b << endl;
	cout << &c<< endl;
	cout << a << endl;
	cout << c << endl;
}

 cb45cc5b8941447aadf09cbf6eead2eb.png

可以看到引用变量的地址都是一样的,并且改变了引用对象的值就改变了被引用对象的值。

1.2 注意

我们要区分开三个概念:

        1.引用是给变量取别名
        2.typedef是给类型取别名
        3.define是常量替换

1.3 特性

• 引⽤在定义时必须初始化

• ⼀个变量可以有多个引⽤

• 引⽤⼀旦引⽤⼀个实体,再不能引⽤其他实体

#include<iostream>
using namespace std;
int main()
{
	int a = 10;
	// 编译报错:“ra”: 必须初始化引⽤
	//int& ra;
	int& b = a;
	int c = 20;
	// 这⾥并⾮让b引⽤c,因为C++引⽤不能改变指向,
	// 这⾥是⼀个赋值
	b = c;
	cout << &a << endl;
	cout << &b << endl;
	cout << &c << endl;
	cout << a << endl;
	cout << b << endl;
	cout << c << endl;
	return 0;
}

ed08365620e049b199e75b7df5d39d6b.png

 1.4 引用的使用

• 引用主要用于引用传参和引用做返回值中减少拷贝提高效率,在改变引用对象的同时改变被引用对象。

• 引⽤传参跟指针传参功能是类似的,引⽤传参相对更⽅便⼀些。

引⽤和指针在实践中相辅相成,功能有重叠性,但是各有特点,互相不可替代。

以下代码段是一个引用替代指针传参的例子

rx,ry形参是实参x,y的别名,所以改变rx,ry就是改变了x,y。

void swap(int& rx, int& ry)
{
	int temp = rx;
	rx = ry;
	ry = temp;
}
int main()
{
	int x = 0, y = 1;
	cout << x << " " << y << endl;
	swap(x, y);
	cout << x << " " << y << endl;
	return 0;
}

1.5 const引用

对象的访问权限在引用过程中可以缩小,但是不能扩大。
const引⽤可以引⽤普通对象,也可以引⽤⼀个const对象,但是必须⽤const引⽤。

int main()
{
	const int a = 10;
	// 编译报错:error C2440: “初始化”: ⽆法从“const int”转换为“int &”
	// 这⾥的引⽤是对a访问权限的放⼤
	//int& ra = a;

	// 这样才可以
	const int& ra = a;

	// 编译报错:error C3892: “ra”: 不能给常量赋值
	//ra++;

	// 这⾥的引⽤是对b访问权限的缩⼩
	int b = 20;
	const int& rb = b;
	// 编译报错:error C3892: “rb”: 不能给常量赋值
	//rb++;

	cout << b<< " " << rb << endl;
	b++;
	cout << b << " " << rb << endl;
	return 0;
}

我们发现rb++会产生报错,因为rb这个引用变量是被const修饰的,因此他的权限小不能改变自生的值,然而我们b变量并没有被const修饰,因此我们可以进行b++的操作,又因为rb为b的引用变量,因此rb的值也因此改变。

 编译器需要⼀个空间暂存表达式的求值结果时临时创建的⼀个未命名的对象,C++中把这个未命名对象叫做临时对象。

需要注意的是类似 int& rb = a*3这样⼀些场景下a*3的和结果保存在⼀个临时对象中

double d = 12.34; int& rd = d也是类似,在类型转换中会产⽣临时对象存储中间值,也就是说,rb和rd引⽤的都是临时对象,在类型转换的传参时,是先将d的整数部分传给临时变量,再将临时变量的值传给我们要赋值的对象的。

⽽C++规定临时对象具有常性,所以这⾥就触发了权限放⼤,必须要⽤常引⽤才可以

int main()
{
	int a = 10;
	const int& ra = 30;

	// 编译报错: “初始化”: ⽆法从“int”转换为“int &”
	// int& rb = a * 3;
	const int& rb = a * 3;
	double d = 12.34;
	
	// 编译报错:“初始化”: ⽆法从“double”转换为“int &”
	// int& rd = d;
	const int& rd = d;

	cout << d << " " << rd << endl;
	return 0;
}

1.6 引用和指针的关系

C++中指针和引⽤在实践中他们相辅相成,功能有重叠性,但是各有⾃⼰的特点,互相不可替代。

• 语法概念上引⽤是⼀个变量的取别名不开空间,指针是存储⼀个变量地址,要开空间

• 引⽤在定义时必须初始化,指针建议初始化,但是语法上不是必须的

• 引⽤在初始化时引⽤⼀个对象后,就不能再引⽤其他对象;⽽指针可以在不断地改变指向对象。

• 引⽤可以直接访问指向对象,指针需要解引⽤才是访问指向对象。

• sizeof中含义不同,引⽤结果为引⽤类型的⼤⼩,但指针始终是地址空间所占字节个数(32位平台下占4个字节,64位下是8byte)

• 指针很容易出现空指针和野指针的问题,引⽤很少出现,引⽤使⽤起来相对更安全⼀些

2. inline

⽤inline修饰的函数叫做内联函数,编译时C++编译器会在调⽤的地⽅展开内联函数,这样调⽤内联函数就需要建⽴栈帧了,就可以提⾼效率。

inline对于编译器⽽⾔只是⼀个建议,也就是说,你加了inline编译器也可以选择在调⽤的地⽅不展开(这是一种防御机制,防止代码膨胀导致内存增大),不同编译器关于inline什么情况展开各不相同,因为C++标准没有规定这个。inline适⽤于频繁调⽤的短⼩函数,对于递归函数,代码相对多⼀些的函数,加上inline也会被编译器忽略。

• C语⾔实现宏函数也会在预处理时替换展开,但是宏函数实现很复杂很容易出错的,且不⽅便调试,C++设计了inline⽬的就是替代C的宏函数

• vs编译器 debug版本下⾯默认是不展开inline的,这样⽅便调试,debug版本想展开需要设置⼀下以下两个地⽅。

• inline不建议声明和定义分离到两个⽂件,分离会导致链接错误。因为inline被展开,就没有函数地址,链接时会出现报错,所以建议直接就放在头文件里面定义声明。

 这里我们可以通过汇编观察程序是否展开,有call Add语句就是没有展开,没有就是展开了

inline int Add(int x, int y)
{
	int ret = x + y;
	ret += 1;
	ret += 1;
	ret += 1;
	return ret;
} 
int main()
{	int ret = Add(1, 2);
	cout << Add(1, 2) * 5 << endl;
	return 0;
}

 实现⼀个ADD宏函数的常⻅问题,以下三种方式都是错误的
#define ADD(int a, int b) return a + b;
#define ADD(a, b) a + b;
#define ADD(a, b) (a + b)

为什么不能加分号?
        define宏的本质是原模原样的替换,编译器在替换完cout << ADD(1, 2) << endl;会
        分号也替换过去,则会出现报错
为什么要加外⾯的括号?
       避免替换 ADD(1, 2) * 5 的语句时变成 1+ 2 * 5,这样顺序就错乱了
 为什么要加⾥⾯的括号?
        避免ADD(x & y, x | y)的语句变成了 x & y + x | y,因为加号的优先级是更高的,所以会导致运算顺序错乱。

下面是正确的宏定义 

#include<iostream>
using namespace std;


// 正确的宏实现
#define ADD(a, b) ((a) + (b))

int main()
{
	int ret = ADD(1, 2);
	cout << ADD(1, 2) << endl;
	cout << ADD(1, 2) * 5 << endl;
	int x = 1, y = 2;
	ADD(x & y, x | y); 
	return 0;
}

3. nullptr

 NULL的本质是⼀个宏,在传统的C头⽂件(stddef.h)中,可以看到如下代码

#ifndef NULL
    #ifdef __cplusplus
        #define NULL 0
    #else
        #define NULL ((void *)0)
    #endif
#endif

由此我们可以发现

• C++中NULL可能被定义为字⾯常量0,或者C中被定义为⽆类型指针(void*)的常量。不论采取何种定义,在使⽤空值的指针时,都不可避免的会遇到⼀些⿇烦,本想通过f(NULL)调⽤指针版本的f(int*)函数,但是由于NULL被定义成0,调⽤了f(int x),因此与程序的初衷相悖。f((void*)NULL);调⽤会报错。

• C++11中引⼊nullptr,nullptr是⼀个特殊的关键字,nullptr是⼀种特殊类型的字⾯量,它可以转换成任意其他类型的指针类型。使⽤nullptr定义空指针可以避免类型转换的问题,因为nullptr只能被隐式地转换为指针类型,⽽不能被转换为整数类型

简单的来说我们可以将nullptr当作C++中的空指针使用,这样可以避免出现以下的带啊吗问题

957dbcbaf2b647aba194adf5557d2f35.png

  • 22
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值