C++好难(2):类和对象(上篇)

本文介绍了C++中的面向过程和面向对象编程思想,通过洗衣服的例子进行对比。接着讲解了类的引入,类的定义包括声明和定义的两种方式,以及类的访问限定符和封装的概念。此外,还讨论了类的作用域、实例化和对象模型,特别是对象大小的计算和内存对齐规则。最后提到了this指针及其在成员函数中的作用。
摘要由CSDN通过智能技术生成

okay,从这里开始,就进入c++比较难的部分了~啊啊啊!!! (¯﹃¯ԅ)

坚持坚持啦 ~ ᵎ(•̀㉨•́)و ̑̑ 


【本章目标】

1.面向过程和面向对象初步认识
2.类的引入
3.类的定义
4.类的访问限定符及封装
5.类的作用域
6.类的实例化
7.类的对象大小的计算
8.类成员函数的this指针


目录

【本章目标】

1.什么是面向过程?什么是面向对象?

2.类的引入

3.类的定义:

3.1定义类的两种方法:

(1)声明和定义全部放在类里面

(2)声明放在 .h 文件里面,类的定义放在 .cpp 文件里面

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

4.1访问限定符

4.2封装

5.类的作用域

6.类的实体化

7.类对象模型

7.1如何计算类对象的大小

7.2结构体内存对齐规则

8. this 指针

8.1  this 指针的引出

8.2  this 指针的特点

8.3看看下面程序编译的运行结果是什么?

1)理解一

2)理解二


1.什么是面向过程?什么是面向对象?

我们都知道:

  • C语言,是面向过程的,关注的是过程,分析出求解问题的每一步,通过函数调用来逐步解决
  • C++,是面向对象的,关注的是对象,讲一件事拆分为不同的对象,靠对象之间的交互完成

我们用一个具体的时间来理解,就比如洗衣服这件事

1)用面向过程的方法:(C语言)

  1. 打开洗衣机
  2. 放入衣服和洗衣液
  3. 洗衣机开始洗衣服
  4. 等待洗完,拿出衣服晾干

2)用面向对象的方法:(C++)

我能会讲这件事分为两个对象:人 和 洗衣机,在对每一个对象进行分析,它需要做哪些事情

人做三件事:

  1. 打开洗衣机
  2. 放入衣服和洗衣液
  3. 拿出衣服晾干

洗衣机只需要做一件事:洗衣服即可

总结:

我们能看出来,面向过程和面向对象是两种思考问题的方式,处理问题的角度不一样

面对过程的思维方式,更加关注事情的每一步。能更加高效、直接

面对对象的思维方式,更加关注事情的参与者、分类。对每一个类有不同的规划,这样在这件事的解决方面能更好的管理和维护

以上即时两种思维的区别

2.类的引入

C语言结构体中只能定义变量,在C++中,结构体内不仅可以定义变量,也可以定义函数

比如: 之前在数据结构初阶中,用C语言方式实现的栈,结构体中只能定义变量;现在以C++方式实现, 会发现struct中也可以定义函数。

struct Student    //class Struct 是C++中常用的使用方法
{
	void Init(const char* name, const char* gender, int age)
	{
		strcpy(_name, name);
		strcpy(_gender, gender);
		_age = age;
	}

	void Print()
	{
		cout << _name << " " << _gender << " " << _age << endl;
	}

	// 这里变量的声明并不是必须加_,可以根据自己的喜好或者之后公司的规定去加
	// 习惯加这个,用来标识成员变量
	char _name[20];
	char _gender[3];
	int _age;
};

int main()
{
	struct Student s1; // C语言的用法,需要加上struct
	Student s2; // C++中,可以直接用Student

	s1.Init("摸鱼", "外星人", 666);
	s1.Print();

	return 0;
}

在结构体的定义中,C++更习惯于用 class 来替代 struct 

3.类的定义:

语法:

class classname
{
	//类体:由成员变量和成员函数组成
	//...
};	//注意后面的分号

其中:

  • class  是定义类的关键字
  • classname  是类的名称
  • {  };   中的内容是主体,注意结束时的分号

类中的元素:称为类的成员

类中的变量:称为成员变量

类中的函数:称为成员函数

3.1定义类的两种方法:

(1)声明和定义全部放在类里面

需要注意:成员函数如果在类中定义,编译器可能会将其当成内联函数处理。

class Data
{
	//类里的内容由成员函数和成员变量组成
public:
	Data(int year, int month, int day)
	{
		_year = year;
		_month = month;
		_day = day;
	}
    void Print()
	{
		cout << _year << " " << _month << " " << _day << endl;
	}
private:
	int _year = 0;
	int _month = 0;
	int _day = 0;
};

(2)声明放在 .h 文件里面,类的定义放在 .cpp 文件里面

 我们一般都更希望,采用第二种方式来写

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

4.1访问限定符

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

访问限定符的说明:

  1. public  修饰的成员在类外可以直接被访问
  2. protected  和  private  修饰的成员在类外不能直接被访问
    (此处protectedprivate是类似的,他们的不用以后会讲)
  3. 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
  4. 如果后面没有访问限定符,作用域就到 } 即类结束
  5. class的默认访问权限为private,struct为public(因为struct要兼容C)

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

C++ 需要兼容 C 语言,所以 C++ 中 struct 可以当成结构体去使用。
另外 C++ 中 struct 还可以用来定义类,和 class 是定义类是一样的;
区别是 struct 的成员默认访问方式是 public,class 是的成员默认访问方式是 private;

4.2封装

什么是封装?

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

通过访问修饰符(如private)来修饰成员变量和成员方法,隐藏你不想让别人知道的代码,只提供接口让别人使用就行

封装的优点:

  • 隐藏代码细节,只提供公共访问
  • 提到代码复用性
  • 提高安全性

5.类的作用域

类定义了一个新的作用域,类的所有成员都在类的作用域中。

在类体外定义成员时,需要使用 : : 作用域操作符指明成员属于哪个类域。

class Student
{
public:
	//显示基本信息
	void Print();

private:
	char _name[20];
	char _gender[3];
	int _age;
};

//这里需要指定Print是属于Student这个类域,所以需要加上::作用域限定符
void Student::Print()
{
	cout << _name << " " << _gender << " " << _age << endl;
}

6.类的实体化

用类类型创建对象的过程,称为类的实例化

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

比如:入学时填写的学生信息表,表格就可以看成是一个类,来描述具体学生信息。

(2)一个类可以实例化出多个对象,实例化出的对象占用实际物理空间,存储成员变量

(3)打个比方:类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图,只设计出需要什么东西,但是并没有实体的建筑存在,同样类也只是一个设计,实例化出的对象才能实际存储数据,占用物理空间

类就像当于一个图纸,根据图纸建造的房屋就是实例化出来的对象

只有实例化出来的对象栈用物理空间(也就是只有房子能住人,图纸不能)

 

7.类对象模型

7.1如何计算类对象的大小

//类中由成员函数和成员变量
class A1
{
public:
	void Print();
private:
	int a;
};

// 类中仅有成员函数
class A2 
{
public:
	void f2() {}
};

//类中仅有成员变量
class A3
{
private:
	int a;
};

// 类中什么都没有---空类
class A4
{};


int main()
{
	cout << sizeof(A1) << endl;

	cout << sizeof(A2) << endl;

	cout << sizeof(A3) << endl;

    cout << sizeof(A4) << endl;

	return 0;
}

如上代码,一个类中既可以有成员变量,又可以有成员函数,

那么一个类的对象中包含了什么?类的大小又是如何计算的呢?

可以看到仅有成员函数和空类都是一个字节,

结论:

  • 空类需要占用一个字节来唯一标识这个类的对象
  • 一个类的大小只取决于这个类的“成员变量”之和
  • 成员函数大小不计算在内
  • 注意计算过程中的结构体内存对其规则

这时候就有人会问了:为啥成员函数不占用类对象的大小嘞?

我们可以这样理解,类对象就是一个别墅用该类所有的对象就是一个别墅小区

每个对象里面的成员变量,就是每个别墅里面的私人财产,如私人的跑车,直升机之类的

成员函数就类似于小区里面的公共场所,如篮球场,超市之类的

类对象在用成员变量时,就是在用自己私有的东西,所以需要算进对象大小里面

而类对象在用成员函数的时候,就相当于去篮球场打球,去超市购物,用的是共有的东西,所以不算进对象大小里面

这个公共区域叫做:代码段

7.2结构体内存对齐规则

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

8. this 指针

8.1  this 指针的引出

如下代码,我们定义一个日期类date

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

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

int main()
{
	Date d1;
	d1.Init(2023, 1, 1);

	Date d2;
	d2.Init(2023, 3, 6);

	d1.Print();
	d2.Print();

	return 0;
}

结果如下:

 对于上述结果,大家有没有考虑过这样一个问题:

用 Date 类创建了 d1 和 d2 两个对象,并进行了初始化赋值,那么当我们调用 Print 函数去打印的时候,Print 函数是如何区分 d1 和 d2 对象的呢?

也就是他怎么知道d1是哪个,d2是哪个的?

这就要引出我们的新概念:this指针

C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有“成员变量”的操作,都是通过该指针去访问。
只不过所有的操作对用户是透明的即用户不需要来传递,编译器自动完成。

 

 8.2  this 指针的特点

  1. this指针的类型:类的类型* const
    被 const 修饰以后,this 指针本身不能被修改,但是 this 指针指向的对象可以被修改,还可以进行初始化。
  2. this 指针只能在 “成员函数” 的内部使用。
     
  3. this 指针本质上其实是一个成员函数的形参,是对象调用成员函数时,将对象地址作为实参传递给 this 形参,所以对象中不存储 this 指针。
     
  4. this 指针是成员函数第一个隐含的指针形参,存放在栈里面,一般情况由编译器通过 ecx 寄存器自动传递,不需要用户传递。

8.3看看下面程序编译的运行结果是什么?

1)理解一

class A
{
public:
	void Show()
	{
		cout << "Show()" << endl;
	}
private:
	int aaa;
};

int main()
{
	A* p = nullptr;
	p->Show();
}

编译结果:

原因:

这里指针 p 是一个类对象的空指针,但当执行 p->Show() 时,程序并不会崩溃,而是正常打印。

因为对象里面存储的只有成员变量,像 Show() 这些成员函数的地址并没有存到对象里面,而是存在公共代码段的。所以这里能够正常运行

2)理解二

class A
{
public:
	void printA()
	{
		cout << _a << endl;
	}
private:
	int aaa;
};

int main()
{
    A* p = nullptr;
	p->printA();

	return 0;
}

编译结果: 

崩溃原因:

当执行  p->printA()   时,这里并不会产生错误,理由理解一中所讲

但是在  printA  函数内部需要打印成员变量aaa,成员变量  aaa 是空的,是访问不到的,所以会报错

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值