初探类和对象 C++

1. 类的引入

在C语言的结构体中,只能定义变量,但是在C++中给结构体进化成了类,它保留了在C中所有的用法,而且还可以再在结构体中定义函数,并且不用加struct定义变量,可以直接写类名

struct Test
{
	int a = 10;
	int b = 20;

	int Add(int a, int b)
	{
		return a + b;
	}
};

int main()
{
	Test test;
	cout << test.Add(test.a, test.b) << endl;
	return 0;
}

在这里插入图片描述

2.类的定义

但是在C++中更喜欢用class关键字来定义类

class className
{
	//类体
};//分号别忘

类定义的两种方式

  1. 声明和定义全部放在类体中,注:如果以这种方式定义的话,类体中的函数默认是内联函数。
class Test
{
	int a = 10;
	int b = 20;

	/*inline*/int Add(int a, int b)
	{
		return a + b;
	}
};
  1. 声明和定义分离,(声明在.h,定义在.cpp),类体中的函数需要加上类名::

在这里插入图片描述

  1. 前面两种方式可以混用。
class Test
{
private:
	int a = 10;
	int b = 20;
public:
	int Add(int a, int b);
	void fun()
	{
		cout << "Hello World" << endl;
	}
};

一般情况下,我们最好使用第三种定义方法,我们将代码少的成员函数定义和声明放在一起,代码量多的成员函数定义和声明分离。

成员变量名命名规则建议:
我们经常要初始化类的成员变量,会遇到下面的情况

class Test
{
private:
	int year;
	int month;
	int day;
public:
	void Init(int year, int month, int day)
	{
	//哪个是形参?哪个是成员变量名?
	//这里是局部优先规则,都是形参
		year = year;
		month = month;
		day = day;
	}
};

所以我们建议在成员变量名前面加上一个_

class Test
{
private:
	int _year;
	int _month;
	int _day;
public:
	void Init(int year, int month, int day)
	{
	//哪个是形参?哪个是成员变量名?
	//这里是局部优先规则,都是形参
		_year = year;
		_month = month;
		_day = day;
	}
};

3.类的访问限定符及封装

3.1 访问限定符

C++实现封装的方式:用类将对象的属性与方法结合在一块,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户使用。
在这里插入图片描述
说明:

  1. public修饰的成员变量,类外可以直接访问。
  2. protected 和 private修饰的成员变量,类外不能访问。
  3. 修饰限定的作用范围是从定义开始到下一个修饰限定符的出现
  4. class定义的类和sturuct定义的类的区别是,class默认的访问限定符是private,struct默认的访问限定符是public。

注意:访问限定符只在编译时有用,当数据映射到内存后,没有任何访问限定符上的区别

3.2 封装

封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。

在C++语言中实现封装,可以通过类将数据以及操作数据的方法进行有机结合,通过访问权限来隐藏对象内部实现细节,控制哪些方法可以在类外部直接被使用。

4. 类的作用域

一个{}就代表一个域,类定义了一个域,类的所有成员都在该域里面,当类外定义成员的时候需要使用::来指明是哪个作用域,就像我们上面所说的,当成员函数的定义和声明分离的时候就用到了::

5. 类的实例化

函数的声明和定义很好区分,但是变量的定义和声明如何区分呢?那就是看有没有给这个变量分配空间。

class Test
{
public:
	int year;
	int month;
	int day;
};
int main()
{
//这里的year只是声明,没有空间,所以这种写法是错误的
	Test::year = 1;
	return 0;
}

总结:

  1. 定义一个类,并没有分配一个空间来存储它,而类的实例话就是给类分配一个空间,创造出一个对象。
  2. 一个类可以实例化很多的对象,就像一张图纸可以构造出很多的房子。但这些对象的基本属性和行为特征都相似。
class Test
{
public:
	int year;
	int month;
	int day;
};
int main()
{
	Test test1;
	Test test2;
	return 0;
}

6. 类的对象模型

6.1如何计算类对象的大小

类里既有成员变量又有成员函数,空间是如何分配的?类对象的大小如何计算?

class Test
{
public:
	int year;
	int month;
	int day;
};
int main()
{
	Test test1;
	cout << sizeof(Test) << endl;
	return 0;
}

在这里插入图片描述
通过内存对齐的方式我们可以计算到这个结果,但如果有成员函数呢?

class Test
{
public:
	int year;
	int month;
	int day;
	void fun()
	{
		cout << year << endl;
	}
};
int main()
{
	Test test1;
	cout << sizeof(Test) << endl;
	return 0;
}

在这里插入图片描述
可以看出大小一样,所以成员函数不在类对象里,计算类对象的大小的时候只需要考虑成员变量和内存对齐规则,不需要考虑成员函数。

6.2 类对象的存储方式

对于类对象的存储方式我们有三种猜测

  1. 对象中包含所有的成员(变量和函数) 这种方式有很大的缺陷,每个对象的成员变量不同,但是调用同一个函数,如果按此种方式存储,那每个对象都保存多次相同的代码段,浪费空间。
  2. 对象中包含成员变量和成员函数地址的指针 函数代码只有一份,对象中保存函数地址。
  3. 对象中只包含成员变量,成员函数在公共代码段。

那么这三种方式,计算机是按照哪种方式存储的呢?根据6.1我们知道,计算机是按照第三种方式存储的,但是空类(没有成员变量)比较特殊,编译器给了空类一个字节大小来标识自己。

class Test
{
};
int main()
{
	Test test1;
	cout << sizeof(Test) << endl;
	return 0;
}

在这里插入图片描述

6.3 结构体内存对齐规则

  1. 第一个成员在与结构体偏移量为0的地址处。
  2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。 注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。 VS中默认的对齐数为8
  3. 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
  4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整 体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍

7. this指针

7.1 this指针的引出

我们先看一段代码

class Date
{
public:
	void Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print()
	{
		cout << _year <<"-"<< _month<<"-" << _day<< endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date date1;
	Date date2;
	date1.Init(2022, 10, 20);
	date2.Init(2023, 10, 20);
	date1.Print();
	date2.Print();
	return 0;
}

在这里插入图片描述
根据上面讲的类对象的存储方式,可知我们调用的公共代码段的同一函数,函数中没有关于不同对象的区分,当date1调用函数,该函数是如何区分是打印date1的数据还是date2的数据?

C++通过引入this指针来解决了这个问题,就是C++编译器给非静态函数都默认隐藏的增加了一个指针,指向调用该函数的对象。但是这些对用户都是透明的,不需要用户自己来完成,编译器自动完成。

//编译器处理后的代码
class Date
{
public:
	void Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print(Data* const this)
	{
		cout << this->_year <<"-"<< this->_month<<"-" << this->_day<< endl;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main()
{
	Date date1;
	Date date2;
	date1.Init(2022, 10, 20);
	date2.Init(2023, 10, 20);
	date1.Print(&date1);
	date2.Print(&date2);
	return 0;
}

7.2 this指针的特性

  1. 该指针的类型*const类型,即不能给this指针赋值。
  2. 只能在成员函数内部使用
  3. this是形参,并不存储在类中。
  4. this指针是“成员函数”第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传 递,不需要用户传递

经典面试题

// 1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{
public:
	void Print()
	{
		cout << "Print()" << endl;
	}
private:
	int _a;
};
int main()
{
	A* p = nullptr;
	p->Print();
	return 0;
}
//解析:该代码最终是正常运行的,因为this指针虽然是空的,但是在Print()函数中没有调用


// 1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class A
{
public:
	void PrintA()
	{
		cout << _a << endl;
	}
private:
	int _a;
};
int main()
{
	A* p = nullptr;
	p->PrintA();
	return 0;
}
//解析:该代码最终是运行崩溃,因为空指针访问

所以this指针是可以为空的。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值