c++入门——基础知识点(2)

1、类对象模型

(1)如何计算类对象大小
类中既可以有成员变量,又可以有成员函数,那么一个类的对象中包含了什么?如何计算一个类大小呢?

  • 一个类对象包含了什么:类的成员变量类的成员函数
  • 要知道如何计算类的大小,先来要知道一个类对象是如何存储的。有两种对象存储方式:对象中包含类的各个成员只保存成员变量,成员函数存放在公共的代码段
    方式一: 对象中包含类的各个成员
    在这里插入图片描述
    注意: 每个对象中成员变量是不同的,但是调用的是同一份函数,按照这种方式存储,当一个类创建多个对象时,那就导致相同的代码被保存多次,浪费空间。
    方式二:只保存成员变量,成员函数存放在公共的代码段
    在这里插入图片描述
    注意: 这种方法同上面那种方法想比较,一个类中成员变量是不同的,但是调用的是同一份成员函数,那我们可以换一种思路,将成员函数单独存放在一块空间之中也就是:公共代码区,只需要在类实例化对象时只需要存储成员变量即可,这样就节省空间同时提高效率
    综上我们就可以得出一个结论:一个类的大小就是该类中“成员变量”之和,当然也要进行内存对齐[详解],注意空类的大小,空类比较特殊,编译器给空类一个字节来唯一标识这个类。
#include<iostream>
#include<windows.h>
using namespace std;
class A
{
	public:
		void PrintA()//成员函数
		{
			cout<<_a<<endl;
		}
	private:
		char _a;//成员变量
};

int main()
{
	cout<<sizeof(A)<<endl;//此时输出1,因为成员变量_a是char类型,占一个字节;
	system("pause");
}
2、this指针

(1)先来看一个简单的日期类

class Date
{
public:
	void Display()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}

	void setDate(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;//年
	int _month;//月
	int _day;//日
};

int main()
{
	Date d1, d2;
	d1.setDate(1990, 1, 1);
	d2.setDate(2018, 7, 1);
	d1.Display();
	d2.Display();
	system("pause");
	return 0;
}

可以看出Date类中有两个成员函数,应该是显示日期的Display和设置日期的setDate,函数体重也没有关于不同对象的区分,那么当s1调用setDate函数时,函数是如何知道应该设置s1对象,而不是设置s2对象呢?
所以在c++中,引入了this指针来解决该问题,就业就是:c++编译器个每一个成员函数增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数中所有成员变量的操作,都是通过该指针去访问。只不过所有的操作对用户来说都是透明的,意思就是用户不用刻意的去传递这个参数,编译器会自动完成。
(2)this指针的特性

  • this指针的类型:类类型 const*
  • 只能在成员函数的内部使用
  • this指针本质上就是一个成员函数的形参,是对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针
  • this指针是成员函数第一个隐含的指针形参,一般情况有编译器通过ecx寄存器自动传递,不需要用户传递
    在这里插入图片描述
    有两问题:(1):this指针能为空吗?(2):this指针存储在哪里?
    (1)this可以为空,当我们在调用函数的时候,如果函数内部并不需要使用到this,也就是不需要通过this指向当前对象并对其进行操作时才可以为空(当我们在其中什么都不放或者在里面随便打印一个字符串),如果调用的函数需要指向当前对象,并进行操作,则会发生错误(空指针引用)就跟C中一样不能进行空指针的引用
    (2)其实编译器在生成程序时加入了获取对象首地址的相关代码。并把获取的首地址存放在了寄存器ECX中(VC++编译器是放在ECX中,其它编译器有可能不同)。也就是成员函数的其它参数正常都是存放在栈中。而this指针参数则是存放在寄存器中。类的静态成员函数因为没有this指针这个参数,所以类的静态成员函数也就无法调用类的非静态成员变量。
class Date
{
public:
	void Display()
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}

	void setDate(int year = 1990,int month = 1,int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	void Print()
	{
		printf("hello this!!!\n");
	}
private:
	int _year;//年
	int _month;//月
	int _day;//日
};

int main()
{
	Date* p = NULL;
	p->Print();//输出 hello this!!!
	p->Display();//程序崩了
	system("pause");
	return 0;
}
3、引用

(1)概念:引用不是新定义一个变量,而是给已经存在的变量取一个别名,编译器不会为了引用变量开辟空间,它和它的引用的变量共用同一块内存空间
类型& 应用变量名(对象名) = 引用实体

void Print()
{
	int a = 10;
	int& ra = a;//定义了引用
	
	printf("%p\n",&a);//输出010FF8E4
	printf("%p\n",&ra);//输出010FF8E4	
}

注意:引用类型必须和引用实体同种类型
(2)引用特性

  • 应用在定义是必须初始化
  • 一个变量可以有多个引用,(就像一个人可以有多个外号)
  • 引用一旦引用了一个实体,就不能再引用其他实体(专一性)
void test()
{
	int a = 10;
	//int& ra;  //语句会报错,没有初始化
	//体现一个变量可以有多个引用
	int& ra = a;
	int& rra = a;
	const int b = 10;
	//int& rb = b;//编译时会报错,因为b为常量
	const int& rb = b;
	//const double& rb = b; //会报错类型不符合
}

(3)使用场景:(1)做参数 (2)做返回值
做参数:

void Swap(int& left, int& right)
{   
	int temp = left;   
	left = right;   
	right = temp;
}

做返回值

int& TestRefReturn(int& a)
{   
	a += 10;  
	return a;
}
int& Add(int a, int b)
{    
	int c = a + b;    
	return c;
}
int main()
{    
	int& ret = Add(1, 2);    
	Add(3, 4); 
	cout << "Add(1, 2) is :"<< ret <<endl; //输出为7  
	return 0;
}

注意:如果函数返回值,离开函数作用域后,其栈上的空间已经还给了系统,因此不能用栈上的空间作为引用类型返回。所以以引用类型返回,返回值的生命周期必须不受函数的限制(返回值的生命周期必须比函数的生命周期长)
(4)传值和传引用的效率比较
传值和传引用作为参数时和返回值:

struct A
{
	int a[100000];
};
A a;
A test1()//传值做返回值
{
	return a;
}

void test2(A a)//传值作为参数
{
}

A& test3()//传引用作为返回值
{
	return a;
}

void test4(A& a)//传引用作为参数
{
}

void test()
{
	size_t begin1 = clock();
	for (size_t i = 0; i < 100000; ++i)
	{
		test1(a);
	}
	size_t end1 = clock();

	size_t begin2 = clock();
	for (size_t i = 0; i < 100000; ++i)
	{
		test3(a);
	}
	size_t end2 = clock();
	
		size_t begin3 = clock();
	for (size_t i = 0; i < 100000; ++i)
	{
		test2(a);
	}
	size_t end3 = clock();
	
	size_t begin4 = clock();
	for (size_t i = 0; i < 100000; ++i)
	{
		test4(a);
	}
	size_t end4 = clock();
	cout << "传值作为返回值test1-time:" << end1 - begin1 << endl;
	cout << "传引用作为返回值test3-time:" << end2 - begin2 << endl;
	cout << "传值作为参数test2-time:" << end3 - begin3 << endl;
	cout << "传引用作为参数test4-time:" << end4 - begin4 << endl;
}

int main()
{
	test();
	system("pause");
	return 0;
}

运行结果:
在这里插入图片描述
结论:传值和传传引用在作为传参以及返回值类型上效率相差很大。
(5)应用和指针的区别
理论上应用就是一个别名,和引用实体共用同一块空间,所以二者的地址都是相同的。

int main()
{
	int a = 10;
	//引用
	int& ra = a;
	ra = 20;
	//指针
	int* pa = &a;
	*pa = 20;
	
	return 0;
}

来看一下反汇编代码:
在这里插入图片描述
引用和指针的不同点:

  • 引用在定义时必须初始化,指针没有要求
  • 引用在引用一个实体后,就不能再引用其他实体,指针却可以在任何时候指向任何一个同一个类型的实体
  • 引用没有NULL,指针由NULL
  • 在sizeof中含义不同:引用结果为引用类型大小,指针始终都是地址空间所占字节个数(32为下大小为4字节)
  • 引用自加就是引用实体增加1,而指针向后偏移一个类型大小
  • 有多级指针,没有多级引用
  • 访问方式不同:指针要解引用,引用编译器自己处理
  • 引用比指针用起来安全

如有见解,请不吝赐教,感激

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值