C++基础知识(二)

本文介绍了C++中的引用概念,强调其作为变量别名的特性,以及引用和指针的区别。讨论了传值与传引用的效率差异,指出内联函数在优化代码性能上的作用。同时,提到了`auto`关键字的使用,包括与指针和引用结合的情况,以及基于范围的for循环在遍历集合时的便利性。
摘要由CSDN通过智能技术生成

引用

(一)引用概念

引用不是定义一个新的变量,而是给已存在的变量取了一个别名,编译器不会给引用变量开辟一块新的内存空间,而是引用变量和原变量共用同一块内存空间
引用形式:
类型& 引用变量名=引用实体

int main() {
	int a = 10;
	int& p = a;    //定义引用类型
	cout << &p << endl;
	cout << &a << endl;

	//指针取别名
	int* b = &a;
	int*& p1 = b;
	cout << &b << endl;
	cout << &p1 << endl;
	return 0;
}

注意:引用类型必须和引用实体是同一个类型。
在这里插入图片描述
引用类型p1和a是不同类型,所以编译器会报错。

(二)引用的特性

1.引用在定义时必须初始化。
在这里插入图片描述
2.一个变量可以有多个引用。(多个引用和变量都共用同一块空间)

int main() {
	int a = 10;
	int& p = a; 
	int& p1 = a;
	return 0;
}

3.一个引用一旦引用一个实体就不能再引用其他实体。
在这里插入图片描述

(三)常引用

在这里插入图片描述
加上const就是常量,权限可以缩小,但是不能放大。

(四)引用使用

1.做参数

int Add(int& x, int& y) {
	return x + y;
}

引用做参数,对形参的更改就是对外部实参的更改。

2.做返回值

首先了解传值返回:

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

传值返回函数调用时,返回值为函数结果的临时拷贝。函数返回值在调用完之后不会随栈帧销毁而销毁,返回值在栈帧销毁之前被保存在某个寄存器。
传引用返回:
在这里插入图片描述
在这里插入图片描述
上面使用ret接受,int&作为返回地址, 返回值c就是ret的别名,下面使用int& ret接受,int&作为返回值,但是为什么上面结果正确,下面结果错误在哪呢?
上面调用Add函数将c的别名返回后给ret接受,Add函数运行结束后,对应的栈空间就被回收了,但是内存还在,还没有被置为随机值,如果栈帧销毁了,返回就是随机值,ret的结果是未定义的,这种引用返回不正确的。
下面调用ret的值可能是随机值也可能不是,取决于这块空间有没有被重新覆盖和使用。cout函数栈帧的创建覆盖了Add函数的函数栈帧,所以ret的值变成随机值了。
所以正确的使用方式是静态变量,全局变量,上一层的栈帧或者malloc等等

int& Add(int x, int y) {
	static int c = x + y;
	return c;
}
int main() {
	int& ret = Add(2, 3);
	cout << "Add(2,3)的值为:" << ret << endl;
	return 0;
}

在这里插入图片描述

注意:如果函数返回时,出了函数作用域,如果返回对象还在(还没还给系统),则可以引用返回,如果已经还给系统了,则必须使用传值返回。

(五)传值和传引用效率比较

以值作为参数或者返回值类型时,在传参和函数返回,函数不会直接传递实参或者将变量本身返回,而是传递实参或者返回变量的一份临时拷贝,所以用值作为参数或者返回值类型,效率会非常低下。

1. 以值或引用作为函数参数比较

#include <time.h>
struct A { int a[10000]; };

void TestFunc1(A a) {}
void TestFunc2(A& a) {}

void TestRefAndValue()
{
	A a;
	// 以值作为函数参数
	size_t begin1 = clock();
	for (size_t i = 0; i < 10000; ++i)
		TestFunc1(a);
	size_t end1 = clock();
	// 以引用作为函数参数
	size_t begin2 = clock();
	for (size_t i = 0; i < 10000; ++i)
		TestFunc2(a);
	size_t end2 = clock();
	// 分别计算两个函数运行结束后的时间
	cout << "TestFunc1(A)-time:" << end1 - begin1 << endl;
	cout << "TestFunc2(A&)-time:" << end2 - begin2 << endl;
}
int main() {
	TestRefAndValue();
	return 0;
}

在这里插入图片描述

2. 值和引用作为返回值类型的比较

#include <time.h>
struct A { int a[10000]; };
//全局变量A
A a;
// 值返回
A TestFunc1() { return a; }
// 引用返回
A& TestFunc2() { return a; }
void TestReturnByRefOrValue()
{
	// 以值作为函数的返回值类型
	size_t begin1 = clock();
	for (size_t i = 0; i < 100000; ++i)
		TestFunc1();
	size_t end1 = clock();
	// 以引用作为函数的返回值类型
	size_t begin2 = clock();
	for (size_t i = 0; i < 100000; ++i)
		TestFunc2();
	size_t end2 = clock();
	// 计算两个函数运算完成之后的时间
	cout << "以值作为返回值"<<"TestFunc1 time:" << end1 - begin1 << endl;
	cout << "以引用作为返回值"<< "TestFunc2 time:" << end2 - begin2 << endl;
}

int main()
{
	TestReturnByRefOrValue();
	return 0;
}

在这里插入图片描述

(六)引用和指针的区别

1.引用是变量的别名,没有独立空间,和引用实体共用同一个空间。而指针有独立的空间,指向变量地址。
2.引用在定义时必须初始化,而指针没有要求。
3.引用在初始化时引用一个实体后就不能再引用其他实体,而指针可以指向任何同一类型的实体。
4.在sizeof中含义不同,引用结果是引用类型的大小,而指针始终是地址空间所占字节数的个数(32位平台下占4个字节)。
5.没有NULL引用,但有NULL指针。
6.有多级指针,但没有多级引用。
7.引用自加是引用的实体加一,而指针自加是指针向后偏移一个类型的大小。
8.访问实体的方式不同,指针访问需要显示解引用,引用访问编译器自己处理。

内联函数

以inline修饰的函数叫内联函数,编译时C++编译器会在调用内联函数的地方展开,inline是一种以空间换时间的做法, 如果编译器将函数当成内联函数处理,在编译阶段,会用函数体替换函数调用,优点就是少了调用开销,提高了运行效率。缺点就是将目标文件变大。
注意:inline不建议声明和定义分离,分离就会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。

//Func.h
#include<iostream>
using namespace std;
inline void Func(int i);

//Func.cpp
#include"Func.h"
void Func(int i) {
	cout << i << endl;
}

//main.cpp
#include"Func.h"
int main() {
	Func(10);
	return 0;
}

在这里插入图片描述

auto关键字

int main() {
	std::map<std::string, std::string> m{ {"apple","苹果"}
		,{"orange","橙子"},{"pear","梨"} };
	std::map<std::string, std::string>::iterator it = m.begin();
	while (it != m.end()) {
		//
	}
	return 0;

随着程序越来越复杂,程序的类型也越来越复杂,类型难以拼写或者含义不明确导致出错,std::map<std::string, std::string>是一个类型,这个类型太长,容易写错。有时会通过typedef给类型取别名:

typedef std::map<std::string, std::string> Map;
int main() {
	Map m{ {"apple","苹果"},{"orange","橙子"},{"pear","梨"} };
	Map::iterator it = m.begin();
	while (it != m.end()) {
		//
	}
	return 0;
}

但是typedef在编程时常常把表达式的值赋值给变量。
所以使用auto修饰变量,auto不再是一个存储类型的指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须是编译器在编译时推导而得。
auto在定义变量时必须初始化,在编译阶段需要根据初始化表达式来推导auto的实际类型,所以编译器将auto替换为变量的实际类型

int TestAuto() {
	return 1;
}
int main() {
	int a = 10;
	auto b = a;   //int
	auto c = 'a'; //char
	auto d = TestAuto();  //int

	//typeid(b).name()获取变量的实际类型
	cout << typeid(b).name() << endl;
	cout << typeid(c).name() << endl;
	cout << typeid(d).name() << endl;
	return 0;
}

(二)auto的使用

1. auto与指针和引用结合起来使用

用auto声明指针类型时,用auto和auto*没有什么区别,但用auto声明引用类型时则必须加&

int main() {
	int x = 10;
	auto a = &x;  //int*
	auto* b = &x;  //int*
	//auto* b = x;  //编译失败,b限定为指针
	auto& c = x;   //int

	cout << typeid(a).name() << endl;
	cout << typeid(b).name() << endl;
	cout << typeid(c).name() << endl;
	cout << *a << endl;   //10
	cout << *b << endl;   //10
	cout << c << endl;    //10
	return 0;
}

2. 在同一行可以定义多个变量

当在同一行定义多个变量时,这些变量必须是同一个类型,否则编译会报错。
在这里插入图片描述

(三)auto不能使用的场景

1.auto不能作为函数的参数

在这里插入图片描述
编译就会报错,auto不能作为形参类型,因为编译器不能对变量的实际类型进行推导。

2. auto不能用来声明数组

在这里插入图片描述

基于范围for循环

(for循环迭代的范围必须是确定的)
我们在学c时遍历一个数组,按照以下方式遍历:

int main() {
	int a[] = { 1,2,3,4,5 };
	for (int i = 0; i < sizeof(a) / sizeof(a[0]); i++) {
		cout << a[i] << " ";
	}
	return 0;
}

对于一个有范围的集合来说,循环范围是多余的,C++引入基于范围for循环:
for(范围内用于迭代的变量:被迭代的范围)

int main() {
	int a[] = { 1,2,3,4,5 };
	for (auto x : a) {
		cout << x << " ";
	}   //自动依次取数组中数据赋值给x,自动判断结束。x相当于数组的拷贝。
	return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值