174-C++重要知识点5

本文详细介绍了C++中的引用,包括引用数组的声明与使用,以及右值引用的概念和作用。同时,讲解了模板的实例化与模板推演规则,以及模板类型和模板函数的设计。此外,还探讨了左值、右值和纯右值的区别,以及函数返回值类型对对象生存期的影响。文章最后讨论了C++中运算符重载的规则和注意事项。
摘要由CSDN通过智能技术生成

1.引用的本身就是一个指针

2.数组的两个要素:类型和大小
引用数组的时候,必须要有类型和大小,如:

int ar[10] = {12,23,34,45,56,67,78,89,90,100};
sizeof(ar);
int (*p) [10] = &ar;
int (&br) [10] = ar;

int (*p) [10] = &ar;表示指针变量p保存的是数组ar的地址,什么样的数组呢?是一个类型为int,大小为10的数组
int (&br) [10] = ar;表示br是数组ar的引用,什么样的数组呢?是一个类型为int,大小为10的数组

3.模板

Print_Ar是一个模板函数,Type是类型概念,int N是非类型概念

template<typename Type,int N>
void Print_Ar(Type(&br) [N])
{
	for(int i = 0;i<N;i++)
	{
		cout << br[i] << " ";
	}
	cout << endl;
}
int main()
{
	int ar[] = {12,23,34,45,56,67,78};
	double dx[] = {1.2,2.3,3.4,4.5,5.6,6.7,7.8};	

	Print_Ar(ar);
	Print_Ar(dx);
	return 0;
}

编译过程中需要对模板进行实例化,要进行模板推演规则
Print_Ar(ar);是要引用一个数组,就必须要知道数组属性,即类型和大小

typedef int Type;
void Print_Ar<int,7>(Type(&br) [7])
{
	for(int i = 0;i<7;i++)
	{
		cout << br[i] << " ";
	}
	cout << endl;
}

这个N不是一个变量,而是一个潜在的常量,在编译的时候会替换成数组的大小7

可以把非类型想成宏,但是不能把类型想成宏,类型是一种重命名规则,非类型是替换规则

4.模板可以设计成模板类型和模板函数

模板类型:

template<class T>
class Test
{
	T data[10];
};

模板函数:

template <typename Type,int N>
void Print_Ar(Type(&br)[N])
{
	for(int i = 0;i<N;i++)
	{
		cout << br[i] << " ";
	}
	cout << endl;
}

5.什么是左值?
凡是可以寻址的值就叫做左值,就是可以取地址的值

左值lvalue,xvalue,右值rvalue,纯右值prvalue

int a = 10; 中a就是左值,可以取地址&a,10是右值,也是纯右值,因为10不能寻址

const int b = 20; 中b是一个左值,20是右值

const int& a = 10;常引用是万能引用,它可以引用一切

int &&b = 10;注意他不是引用的引用,没有引用的引用这一概念,int&& 称为右值引用

6.右值引用

int fun()
{
	int a = 10;
	return a;
}
int main()
{
	int x = fun();//yes
	int& b = fun();//error
	const int& c = fun();//yes
	int&& d = fun();//yes
	
	return 0;
}

int x = fun();返回的时候会把a放入一个临时量中,当返回到主函数的时候,再把临时量的值赋给x

const int& c = fun();返回的时候会直接把a的值放入主函数的栈帧中,c去引用这个空间,这个将亡值不能改变

int&& d = fun();本来是把a的值放入一个临时空间中,赋给d,等到函数结束的时候,将亡值就消失了,右值引用的话会把a的值放到主函数的栈帧空间中,d去引用这个将亡值,这个将亡值可以改,右值引用把将亡值的生存期拉长了,之前生存期是一瞬间,只在函数的返回过程中存在,一旦返回过程结束了,就不存在了,右值引用的生存期和名字的生存期一样长

7.C++为什么要使用右值引用呢?

C++对于内置类型有自己的处理规则,对自定义类型也有自己的处理规则,内置类型的处理规则和自定义类型的处理规则是完全不一样的概念,右值引用的引入主要是处理自定义类型的处理规则

C++有一种类型是介于内置类型和自定义类型,我们称为PUD类型,也就是第三种类型,就是拿结构体设计的类型

8.class和struct的区别

class和struct都可以设计类型,但是也是有区别的
语法上:class默认是私有的private,struct默认是公有的public,class继承默认是private继承,struct继承默认是public继承

class设计的是一个类型,最终实例化成的是对象,对象由两部分构成,数据和方法的集合

struct设计的时候要么只包含数据,就是当作一个数据的集合来看待,没有构造函数,析构函数,当作为数据的集合时,和内置类型是一致的,就是说处理方式和内置类型的处理方式是一样的;要么只包含方法,就是当作一个接口(interface)来看待

9.函数返回的过程中产生的将亡值叫做右值

10.赋值语句返回引用的原因是,可以实现连续赋值

11.函数以值返回

程序先构造objx(Object::Object:010FFA70)和objy(Object::Object:010FFA64),然后调用fun函数,用objx去初始化obj,然后把val的值加10,然后调用构造函数构造obja(Object::Object:010FF93C),然后return的时候会构造一个将亡值对象(因为是以值返回,会构建在调用方函数的栈帧空间中,Copy Create:010FF998),这是obja的生存期到了,析构obja(Object::~ Object:010FF93C),返回到主函数以后,调用赋值运算符重载函数(010FFA64 == 010FF998),然后输出objy的值,return之后,objx和objy的生存期到了,先析构objy(Object::~ Object:010FFA64)再析构objx(Object::~Object:010FFA70先构造的先析构)
在这里插入图片描述
12.函数以引用返回

程序先构造objx(Object::Object:008FF864)和objy(Object::Object:008FF858),然后调用fun函数,用objx去初始化obj,然后把val的值加10,然后调用构造函数构造obja(Object::Object:008FF74C),因为以引用返回返回的是obja的地址,并不返回obja对象本身,所以不需要去拷贝构造一个对象,所以return以后直接析构obja(Object:: ~ Object:008FF74C),回到主函数以后,fun函数拿到的其实是一个地址,但是并不是把地址赋给objy,而是把地址指向的对象赋值给objy(008FF858 == 008FF4C),然后输出objy的值,return之后,objx和objy的生存期到了,先析构objy(Object::~ Object:008FF858)再析构objx(Object::~Object:008FF864,先构造的先析构)

但是为什么调用赋值运算符重载函数以后,仍然能打印出来10呢?调用的时候不会对栈帧进行清扫初始化为0xcccc cccc吗?

实际上因为函数内部没有任何变量,他对栈帧清扫的很轻(大概12到16个字节),并没有把obja的空间清扫掉,导致仍然能打印出已死亡对象的值,一旦在函数中定义变量,就会对整个栈帧进行清扫,这时打印出的值就是一个随机值,这就是一个很大的问题

VS2019的特点,如果调用函数的时候,如果函数没有定义局部变量或者局部对象,那么几乎不会对栈帧进行清扫,一旦定义局部变量或者对象,就会对整个栈帧进行清扫,所以无论如何都不允许在函数中将对象以引用返回,只有两种情况可以以引用返回:①对象的生存期不受函数的影响②引用进来的可以引用返回
在这里插入图片描述
13.不允许在构造函数或拷贝构造函数中和其他成员函数中使用memset,memmove函数,如果类型在设计的时候有虚表(virtual)时,虚表是写在对象的最上面4个字节,虚表是在进入构造函数或者拷贝构造函数的函数体之前被构建的,当在函数体内部使用memset(this,&obj,sizeof(object));函数以后,就会把虚表变成0了,这是不允许的

如果类型是一个简单类型,不存在继承关系,没有虚函数还是可以谨慎使用的,如果有虚表以后,有memset函数很容易使程序崩溃

14.如果没有写出赋值运算符重载函数,系统会自动生成一个缺省赋值运算符重载函数,编译器发现如果这是一个简单类型(没有自定义类型,都是基本类型),那么这个函数不会有函数的调用过程,不会形成现场保护,不会调用赋值运算符重载函数,也不会对之前的栈帧进行清扫,如果写出了赋值运算符重载函数,那么就会调用赋值运算符重载函数,会对栈帧进行清扫,里面的数据也会清空

15.没有深拷贝和浅拷贝

16.写出缺省构造函数和缺省=重载函数,存在的缺陷是什么?

class Object
{
	private:
		int num;
		int* ip;
	public:
		Object(int n,int val = 0):num(n)
		{
			ip = (int *)malloc(sizeof(int ) * n);
			for(int i = 0;i<num;i++)
			{
				ip[i] = val;
			}
		}
		~Object()
		{
			free(ip);
			ip = NULL;
		}
		//缺省构造函数
		Object(const Object& obj):num(obj.num),ip(obj.ip) {}
		//缺省=重载
		Object& operator=(const Object& obj)
		{
			if(this != &Obj)
			{
				num = obj.num;
				ip = obj.ip;
			}
			return *this;
		}
}

这样做的缺陷是会造成内存丢失,析构的时候会对同一个空间析构两次,会造成程序崩溃,所以当缺省拷贝和缺省赋值无法满足我们的需求的时候,我们要自己正确的给出来

17.++重载
前置++
返回类型 类名::operator++() {…}

Int& operator++()
{
	value+=1;
	return *this;
}

后置++
返回类型 类名::operator++(int) {…}
这里的int仅用作区分,没有实际意义

Int operator+(int)
{
	Int tmp = *this;
	++*this;
	return tmp;
}

后置++不能以引用返回,因为tmp是一个局部对象,函数结束后,栈帧清退了

前置++比后置++好的原因是:后置++每一次++的时候都需要频繁创建对象和析构对象,效率及其低下,而前置++是不用频繁创建对象和析构对象,所以要尽量的使用前置++

18.C++中不允许重载的运算符
?:
.
*
::
sizeof

//
/*
*/

19.运算符重载有以下几种限制
①不可臆造新的运算符
②不能改变运算符操作数的个数,运算符重载以后不再是运算符了,而是函数,所以优先级、结合性和语法结构都要遵循函数的法则,而不是运算符的法则
③运算符重载不宜使用过多
④重载运算符含义必须清楚,不能有二义性

20.对象 + 对象

Int operator+(const Int& src) const
{
	return Int(value + src.value);
}

21.整型 + 对象
放到类外实现,return it + x;是对象+整型,又会去调用对象+整型的函数

Int operator+(const int x,const Int& it)
{
	return it + x;
}

22.对象 + 整型

Int& operator+(const int x) const
{
	return Int(value + x);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值