C++基础续

举报
卖寂寞的小男孩 发表于 2022/10/29 15:20:51 2022/10/29
【摘要】 本文主要介绍C++基础学习。

@[TOC]

引用

概念

引用是给已经存在的变量取一个别名,它和它引用的变量公用一块空间。就像李逵和黑旋风一样,是同一个人。
引用的符号是&,和取地址符号是一样的。

int a = 10;
	int& ra = a;
	printf("%d %p\n", a, &a);
	printf("%d %p\n", ra, &ra);

这段代码定义了一个ra作为已存在变量a的引用。
它们的值和地址时一样的。
在这里插入图片描述

引用的特性

引用在定义时必须初始化

int a=10;
int &b;//错误,没有初始化
int &b=a;//正确,进行了初始化

引用类型必须和引用对象是同种类型的

int a=10;
int& ra=a;//正确,同种类型
double& rra=a;//错误,不同种类型

引用和原变量的变化是同时的

由于引用和原变量的地址时相同的,它们本质上是同一个变量,所以两者的变化是同时变化:

int a = 10;
	int& ra = a;
	ra = 20;
	printf("%d", a);

改变的是a的引用ra的值,但是a的值也发生了变化。
在这里插入图片描述

一个变量可以有多个引用

int a = 10;
	int& ra = a;
	ra = 20;
	int& b = ra;
	int& c = b;
	c = 90;
	printf("%d", a);

这里ra是a的引用,b是ra的引用,c是b的引用,它们都占用同一块内存空间,所以他们都是a的引用,改变其中一个值,所有变量的值都会发生改变。
在这里插入图片描述

注意事项1

注意和赋值语句区分

int a = 10;
	int& ra = a;
	int c = 20;
	c = ra;//赋值语句,改变c不改变a的值
	c = 15;
	printf("%d", a);

在这里插入图片描述

注意事项2

小号不能是别人的大号,会报错

int a = 10;
	int b = 20;
	int& b = a;

这段代码就是错误代码,a的小号不能取名为b。因为b已经是一个存在的变量了。

常引用

常引用即为带const修饰的引用。

权限放大是不允许的

const int a=10;
int& ra=a;

a为常量,而ra是变量,这样定义ra是不被允许的。

权限缩小是允许的

int a=20;
const int& ra=a;

这样写相当于把一个变量a变成了常量,是被允许的。这样写的目的是,只能通过修改a的值来修改ra的值。而不能直接对ra进行赋值操作。
通常如果涉及到常引用,变量与引用是保持一致的。

常引用的注意事项

来看这样一段代码:

    int c = d;//可以
	int& c = d;//不行
	const int& c = d;//可以

第一行是被允许的,会发生整型截断。
第二行是不可以的,因为当进行隐式类型转换的时候,会创建临时变量去接收d的值,然后对临时变量进行截断,然后再将临时变量赋值给c。临时变量是右值,此时c应该为临时变量的别名,类型应为const int所以这样写不行。
第三行将c的类型改为与临时变量相同的类型const int,就可以编译通过。

补充:表达式运算也会出现临时变量
所以int& ret=x1+x2是错误的,应该改写为const int& ret=x1+x2。

结论

1.const Type& 可以接收各种类型的对象。
2.使用引用传参,如果函数中不改变引用的值,建议使用const引用。

引用的应用

作参数

引用作参数可以达到指针作参数的效果,可以对比一下这两段代码:

void Swap(int* a, int* b)
{
	int tmp = *a;
	*a = *b;
	*b = tmp;
}
int main()
{
	int a = 10;
	int b = 20;
	Swap(&a, &b);
	cout << a << " " << b << endl;
	return 0;
}

我们都知道,在进行交换两个数时,向Swap函数中传递的一定是两个数的地址,不能传递数本身。这是由于形参是实参的拷贝,改变形参并不能改变实参。
但是如果使用引用来作为参数,就可以达到修改实参的目的,因为引用与原变量占用同一块空间。

void Swap(int& a, int& b)
{
	int tmp = a;
	a = b;
	b = tmp;
}
int main()
{
	int a = 10;
	int b = 20;
	Swap(a, b);
	cout << a << " " << b << endl;
	return 0;
}

这里的形参实际上就是实参,改变形参,实参也会跟着改变。
在这里插入图片描述
可以使用引用传参的方法来避免使用指针。
注意传值与传引用会构成函数重载,但是在使用时会报错,这是因为调用不明确。举一个栗子:

void Swap(int& a, int& b)
{
	int tmp = a;
	a = b;
	b = tmp;
}
void Swap(int a, int b)
{
	int tmp = a;
	a = b;
	b = tmp;
}
int main()
{
	int a = 10;
	int b = 20;
	Swap(a, b);
	cout << a << " " << b << endl;
	return 0;
}

在这里插入图片描述

作返回值

返回值的拷贝

在学习引用作为返回值之前,我们需要了解返回值是怎么来的。

int Add(int a, int b)
{
	return a + b;
}
int main()
{
	int a = 10;
	int b = 20;
	printf("%d", Add(a, b));
}

这是一段很简单的程序,我们知道形参是实参的拷贝(不使用引用的情况下),其实返回值也有自己的拷贝,返回的并不是a+b的值本身,而是a+b的值的拷贝。
这是因为如果返回的是a+b值本身的话,那么在打印Add(a,b)的值之前,Add(a,b)已经执行结束了,这意味着形参a和b已经被销毁了,其实无法返回a+b的值了已经。
那么返回值的拷贝存放在哪里呢?
1.当返回值比较小时,一般存放在寄存器中。
2.当返回值较大时,一般存储在调用带有返回值的函数的栈帧中。

引用作返回值

int& Add(int a, int b)
{
	int c= a + b;
	return c;
}
int main()
{
	int a = 10;
	int b = 20;
	int& ret = Add(a, b);
	printf("%d", ret);
}

这样写是不正确的,虽然编译器不会报错,但是其实返回值c并没有拷贝新的空间,返回的就是c本身,然而c在函数结束时已经销毁了,如果在打印之前调用一个函数,将原来放置c的空间进行覆盖,打印的就不是30了。
在这里插入图片描述
因此如果函数返回时,出了函数作用域,如果返回对象还未还给系统,则可以使用引用返回,如果已经还给系统了,则必须传值返回。

struct A { int a[10]; };
A a;
A& Test1() { return a; };
int main()
{
	A b = Test1();
}

比如a定义在全局中,当函数结束时也不会发生销毁。这时就可以使用引用作为返回值。

引用的价值

提高效率

当引用作为参数时,函数直接对原变量进行修改,不需要重新开辟空间。
当引用作为返回值时,也不会另外开辟空间,所以引用提高了程序运行效率。

作为形参可以影响实参

void Swap(int& a, int& b)
{
	int tmp = a;
	a = b;
	b = tmp;
}
int main()
{
	int a = 10;
	int b = 20;
	Swap(a, b);
	cout << a << " " << b << endl;
	return 0;
}

这段代码中就是没有使用指针而改变了实参的例子。

可以修改返回变量

意思是使得函数变得可读可写。

int& At(int i)
{
	static int a[10];
	return a[i];
}
int main()
{
	int i;
	for (i = 0; i < 10; i++)
	{
		At(i) = i;
	}
	for (i = 0; i < 10; i++)
	{
		cout << At(i) << endl;
	}
	return 0;
}

在这里插入图片描述
注意这一段代码,就是为返回值赋值,从而改变全局数组a中的值。
如果该函数不使用引用作为返回值,是会发生报错的。
这是因为,当引用作为返回值的时候,这段代码相当于a[i]=i,可以正常赋值。
但是如果是值作为返回值时,返回时会创建临时变量,临时变量是右值,是不可以修改的,所以会报错。

引用的底层

int a = 1;
	int& b = a;
	int* p = &a;

其中b是a的引用,p存放a的地址,但是这两段代码体现在汇编上是完全相同的。
在这里插入图片描述

在语法层:指针和引用是完全不同的概念,指针是开空间,存储变量的地址,引用不开空间,仅仅对变量取别名。
在底层:引用是用指针实现的。

内联函数

概念

以inline修饰的函数称为内联函数,编译时C++编译器会在内联函数的地方展开,没有函数压栈的开销,内联函数提升程序运行的效率。

int Add(int a, int b)
{
	int c = a + b;
	return c;
}
int main()
{
	int a = 10, b = 20;
	int c = Add(a, b);
	cout << c;
}

我们正常调用一个函数的时候,会在主函数之外再开辟一个栈帧,在其中新建一个变量去接收传入的参数。在反汇编中会体现出调用函数的过程:
在这里插入图片描述

而使用内联函数的好处就在于,程序不需要重新开辟新的栈帧,而会在内联函数处,将内联函数展开。
在debug版本下,需要对编译器进行一定的设置才能使用内联函数。
在这里插入图片描述
首先右击创建的项目,选择属性。
在这里插入图片描述
首先点击常规,修改调试信息格式为程序数据库。
在这里插入图片描述
然后在C/C++优化中选择内联函数扩展->只适用于_inline,然后确定即可。
设置之后就可以使用内联函数了。

inline int Add(int a, int b)
{
	int c = a + b;
	return c;
}
int main()
{
	int a = 10, b = 20;
	int c = Add(a, b);
	cout << c;
}

此时不会调用Add这个函数,而是直接进行运算。
在这里插入图片描述
内联函数的使用和C语言中的宏替换很像。

特性

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

总结:短小和频繁调用的函数建议定义为内联函数。

auto关键字

C语言中的auto

在早期的C语言中,定义变量是需要加auto修饰的。

auto int a=0;

这表示的是a是一个自动存储类型,在函数结束之后自动销毁,其实并没有什么意义,因为就算不加auto的话,在函数栈帧结束之后,变量也会自动销毁。
所以在C++11中,赋予了auto全新的含义。

auto的含义

auto表示的意思是自动推导类型。

    int a = 0;
	auto b = a;//自动推导出b为int
	auto c = 10;//自动推导出c为int
	auto d = 1.1;//自动推导出d为double

对于类型很长的变量,可以直接auto该变量,这就是auto的应用。
我们可以打印它们的类型俩验证一下。
在这里插入图片描述
注意使用auto定义变量的时候,必须进行初始化。

auto的注意事项

auto与指针

int a=0;
auto p1=&a;
auto* p2=&a;

规定此时p1和p2的类型是一样的,都是int*类型。

auto一行定义多个变量

在auto一行定义多个变量时,要保证这些变量类型是一致的。

auto a=10,b=20;
auto c=10,b=2.3;

第一行是正确的,第二行是错误的,编译器会报错。

auto不能使用的场景

auto不能作为函数的参数

void Test(auto a);

这样定义是不被允许的。

auto不能声明数组

int a[]={1,2,3};//正确
auto b[]={4,5,6};//错误

语法糖:基于范围for循环

C++提供了一种更简单遍历数组的方法:

int arr0[] = { 1,2,3,4,5,6,7,8,9,0 };
	for (auto e : arr0)
	{
		cout << e <<endl;
	}

这段代码的含义是,自动依次取数组中的每个元素,依次赋值给e,自动判断结束。

在这里插入图片描述
如果想要修改数组中的元素,就需要使用到引用了,因为改变e不能改变数组中的值。

for (auto& e : arr0)
	{
		e++;
	}

在这里插入图片描述
注意在使用范围for的时候,只能传入数组名,不能对形参数组进行操作。因为此时范围是不确定的。
即这样书写时不正确的,尽量在主函数中使用。

void Test(int* arr)
{
	for (auto e : arr)
	{
		cout << e;
	}
}
int main()
{
	int arr0[] = { 1,2,3,4,5,6,7,8,9,0 };
	Test(arr0);
}

指针空值nullptr

在C++中,NULL作为一个宏存在,它的值被定义为0,在一些情况下并不适用,比如下面这段代码:

void f(int)
{
	cout << "f(int)" << endl;
}
void f(int*)
{
	cout << "f(int*)" << endl;
}
int main()
{
	f(NULL);
	f(nullptr);
}

在这段代码中,当传入参数为NULL时,我们认为传入的是一个空指针,应该进入第二个重载函数,但其实进入的是第一个。
只有传入nullptr的时候才会传入第二个函数。
在这里插入图片描述

总结

C++基础是一些C++中琐碎的知识点,了解了这些,才能够更好地进行C++的类与对象的学习,在学C语言数据结构的时候,经常会看到有些书有引用的用法,引用在大部分情况是可以代替指针的,C++对C语言简化了很多东西,无论是输入输出,缺省参数,还是基于范围for循环中,C++使得编程变得更加灵活和方便。
著名俄国学者斯坦尼夫拉夫斯基曾经说过:一个三连的人,一定是一个高尚的人,一个纯粹的人,一个有道德的人,一个脱离了低级趣味的人,一个有益于人民的人(salute)。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。