C++的第一道门坎:类与对象(一)

csdn推荐

在C++中,有一个关键字为class,我们可以使用这个关键字来声明一个类,类内可以声明成员函数和成员变量,其语法结构为:

class 类名
{
	//类内的成员函数和成员变量
};

现在我们声明一个名为duanku的类:

class duanku
{
	int a;//成员变量
	int Add(int a, int b);//成员函数
};

2.2类的成员的两种定义方式

在定义类时,我们可以将类的定义与声明在同一文件书写,也可以在不同文件内书写。

2.2.1单文件定义

定义一个类,自然需要实现类体内的函数,我们可以将函数在类内定义,如下所示:

class duanku
{
	int a;//成员变量
	int Add(int a, int b)//成员函数
	{
		return a + b;
	}
};

这里需要大家注意的是:成员函数若在类内定义,则可能会被编译器当作内联函数处理。

2.2.2多文件定义

一个类也可以分为两个文件来实现,我们可以将成员函数的定义放在cpp文件内部。

但这儿需要大家注意的一点是:你定义的函数是谁的函数呢?是只在这个文件内部用的,还是实现了哪个地方的声明呢?因此,我们需要加域作用限定符,让编译器知道我们实现的是哪个函数。

//.h文件
class duanku
{
	int a;//成员变量
	int Add(int a, int b);//成员函数
};
//.cpp文件
int duanku::Add(int a, int b)
{
	return a + b;
}

PS:在日常使用时,我们可以将定义和声明都放在类中。但是在实际的工程中,更倾向于将声明和定义分离。

3.类的访问限定符与封装 3.1访问限定符

在C++类中有三种访问限定符:public、private、protect

它们的作用如下

3.2封装

封装:用类将对象的属性(数据)与操作数据的方法结合在一块,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户使用。

通俗的说,封装其实是一种管理,是为了方便让用户更方便的使用类的一种方法。

比如说:对于一台电脑而言,我们提供给用户的是一系列的USB接口,我们可以通过它们和计算机进行一系列的交互,也可以自由更改每个USB接口的接什么,但是我们无法更改电脑的硬件元件。

这里,我们可以这样理解:

每个USB接口都是一个函数,我们可以自由更改它们,它们的访问限定为public。

而硬件元件则是类中的私有成员,不让外界访问,它们的访问限定为private。

4.类对象 4.1类对象的实例化

在类中定义的成员变量实际上是一种声明,而我们利用类名字定义的对象就是类对象的实例化。

即,用类的类型创造对象的过程,称为类的实例化。

1.类是对对象进行描述的,是一个模型一样的东西,限定了类中有哪些成员,定义出一个类并不会分配实际的内存空间来存储它。

2.一个类可以实例化出多个对象,实例化出的对象才占用实际的内存空间,存储的是类的成员变量。

我们不可以直接利用类来进行定义,如下图所示

正确的定义方法如下:

	Date a;
	a._year = 3;

4.2类对象的存储

我们已经知道了类对象的创建方式,那么具体类中的成员变量与成员函数又是如何存储的呢?

4.2.1 存储方式1

每次创建对象时,都开辟一个空间存储类中的成员变量与成员函数。

但是这样存储就会出现一个问题,成员函数都是用的一个函数,我们每次都创建一个岂不是会有很大的开销吗?一个就能行的事儿为什么要用很多个呢?

于是,我们的计算机中,大多数都不是这么存储对象的。

4.2.2 存储方式2

我们首先将成员函数放在外面,之后我们每次创建对象时,都开辟一个空间存储类成员变量与成员函数的地址,需要成员函数的时候直接按照地址去找。

4.2.3存储方式3

每次创建对象时,都开辟一个空间存储类成员变量。而成员函数提前单独存储在另外一个区域(公共代码段)。

之后我们没去用到成员函数时,都去那个区域里面拿出来用。

一般的编译器采用的都是存储模式2或存储模式3,具体使用的是哪种存储模式,我们可以通过计算类大小来进行判断。

4.3类对象的大小 4.3.1普通类对象的大小

类型对象的大小也遵循结构体的内存对齐规则:

1.类的第一个成员对齐到结构体变量起始位置偏移量为0的地址处

2.其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。

对齐数=编译器默认的一个对齐数与该成员变量大小的较小值。

–VS2022中的默认对齐数是8

–Liunx中gcc没有默认对齐数,对齐数就是成员自身的大小

3.类总大小为所有成员对齐数中的最大对齐数的整数倍

4.如果嵌套了结构体,嵌套的结构体成员对齐到自己的成员的最大对齐数处,结构体的整体大小就是所有成员的最大对齐数(含嵌套结构体成员)的整数倍。

当然,类大小的计算我们可以也借助sizeof计算。

#include
using namespace std;
class Date
{
//可以被直接访问
public:
	void Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
//不能被直接访问
private:
	int _year;
	int _month;
	int _day;
};
int main()
{
	Date d;
	cout << sizeof(d) << endl;
	return 0;
}

由此可见,我们这里算出来的12是三个int类型的总和,而我们的成员函数则被放到了公共代码段中。 若是使用的存储模式2的话,则应该是12+成员函数地址的大小(32位系统是4,64位系统是8)。

4.3.2空类的计算

当类中只有成员函数,或者什么都没有时,类的大小是多少呢?

class A
{
public:
	void func()
	{
		;
	}
};
class B
{
};
int main()
{
	A a;
	B b;
	cout << sizeof(a)<<endl << sizeof(b);
	return 0;
}

这里我们发现空类的大小为1,现在我们可以思考这么一个问题,为什么空类的大小是1而不是0呢?这是因为我们在进行空类的实例化时必须要有空间表示我们在这儿存储了一个对象。这时编译器就会默认给一个字节大小来标记这个对象。

5.this指针 5.1this指针的由来

#include
using namespace std;
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 d1, d2;
	d1.Init(2022, 1, 11);
	d2.Init(2022, 1, 12);
	d1.Print();
	d2.Print();
	return 0;
}

在这里我先给大家抛出这么一个问题:为什么d1调用Init函数时,该函数知道是给d1对象调用Init,而不是给d2对象调用Init呢?

在C++中通过引入this指针解决该问题,即:C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数。通过不同的对象地址来分辨不同的对象,只不过所有的操作对用户是透明的的,即用户不需要来传递,编译器自动完成。实际代码如下:

void Init(Date* this, int year, int month, int day)
{
	this->_year = year;
	this->_month = month;
	this->_day = day;
}
d1.Init(&d1,2022, 1, 11);//实际传参

5.2this指针的特点 this指针额类型为const,即我们无法给this指针赋值this指针的本质是“成员函数”的形参,当对象调用成员函数时,会将对象的地址当作实参传递给形参。this指针只能在成员函数中使用this指针是“成员函数”第一个隐藏起来的指针形参,一般由编译器放在通过eax寄存器自动传递,不需要用户传递。 5.3两个问题 5.3.1问题1

// 1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class duanku
{
public:
	void Print()
	{
		cout << "Print()" <Print();
	(*p).Print();
	return 0;
}

这段代码是可以正常运行的,虽然p是一个空指针,但是由于我们的成员函数放在公共代码段中,因此我们还是可以找到这个函数的,不会因为对空指针解引用而找不到它。

5.3.2问题2

// 1.下面程序编译运行结果是? A、编译报错 B、运行崩溃 C、正常运行
class duanku
{
public:
	void Print()
	{
		cout << _a <Print();
	(*p).Print();
	return 0;
}

这里就会让程序崩溃,因为函数中访问了_a,nullptr->_a程序就崩溃了。

6.成员变量的命名

大家在使用类时,可能会出现以下这么一个问题:

这时为了解决问题这类问题,我们在定义成员变量时会对其进行特定地修饰。比如说_变量名或者变量名_。不同公司,不同的程序员可能都有一套自己的命名规则,这里我们采用第一种。

由此我们可以将上述代码改写为这样:

class Date
{
	void Init(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
	int _year;
	int _month;
	int _day;
};

文章来源:https://blog.csdn.net/duanku111/article/details/139231207



微信扫描下方的二维码阅读本文

© 版权声明
THE END
喜欢就支持一下吧
点赞12 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容