类和对象上


image-20220127141441212

类和this

image-20220206095118501

由于已经学习过Java中的类与对象,类似的部分就不再写进博客,只记录下不同之处和精华之处

[toc]

类的引入

Java中的类有Class来表示

C++中的类怎么引入呢?

首先回顾C语言如何引入数据结构中的栈

函数方法 – 数据和方法是分离的,重点关注的是过程(函数方法)

struct Stack
{
	STDataType* a;
	int size;
	int capacity;
};
// 函数方法 -- 数据和方法是分离的,重点关注的是过程-》函数方法
void StackPush(struct Stack* ps, STDataType x);

C++的类则是创建新的类型,和Java一样还是由成员变量,或者叫字段和成员函数

C++中的struct兼容C中的结构体,也把struct升级成了类

struct Stack
{
	void Init(int initSize = 4)//缺省参数
	{
		a = (STDataType*)malloc(sizeof(STDataType) * initSize);
		size = 0;
		capacity = initSize;
	}

	void Push(STDataType x)
	{
		// ...
		a[size] = x;
		size++;
	}
	STDataType* a;
	int size;
	int capacity;
};

需要使用的时候就可以直接

Stack st;
st.Init();
st.Push(1);
st.Push(2);
st.Push(3);

不过C++定义类的时候更喜欢用class,class和struct其实定义的都是类

类的定义

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

class为定义类的关键字,ClassName为类的名字,{}中为类的主体,注意类定义结束时后面分号。
类中的元素称为类的成员:类中的数据称为类的属性或者成员变量; 类中的函数称为类的方法或者成员函数。

两种类定义方法

方法一

image-20220126201328890

一般,由于成员属性都是在声明,而如果成员函数也应该只声明不定义,这样就比较符合规范,所以我们最好把定义的部分放到cpp文件中,把声明文件放到头文件中,然后若要cpp文件中定义这些类的函数的话,就把于加上去就可以了,比如下面

Stack.h

struct Stack
class Stack
{
	void Pop(){}
	void Init(int initSize = 4);
	void Push(STDataType x);
	// ...
	STDataType* a; // 成员变量在这里是声明
	int size;
	int capacity;
};

Stack.cpp

void Stack::Init(int initSize)
{
	a = (STDataType*)malloc(sizeof(STDataType) * initSize);
	size = 0;
	capacity = initSize;
}

void Stack::Push(STDataType x)
{
	// ...
	a[size] = x;
	size++;
}

方法二

还有一种方法就是把声明和定义全部放在类体中,需要注意:成员函数如果__在类中定义__,编译器可能会将其__当成内联函数__处理。(超过十行太多就不自动展开成内联函数了)

image-20220126201316137

类的成员变量命名

int _year; // 年
int _month; // 月
int _day; // 日

在成员变量前加一个_,一般表示成员变量,不是表示private变量,有些公司就会加一个m,具体看公司要求

类的访问限定符

java 提供四种访问控制修饰符号,用于控制方法和属性(成员变量)的访问权限(范围):
🌿 公开级别:用public 修饰,对外公开
🌿 受保护级别:用protected 修饰,对子类和同一个包中的类公开
🌿 默认级别:没有修饰符号,向同一个包的类公开.
🌿 私有级别:用private 修饰,只有类本身可以访问,不对外公开
image-20220126202053335

而C++中这些限制符有不同的含义

🌿 public修饰的成员在类外可以直接被访问
🌿 protected和private修饰的成员在类外不能直接被访问(此处protected和private是类似的)
🌿 访问权限作用域从该访问限定符出现的位置开始直到下一个访问限定符出现时为止
🌿 class的默认访问权限为private,struct为public(因为struct要兼容C)

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

image-20220126201849869

C++中struct和class的区别是什么?

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

封装

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

封装本质上是一种管理:我们如何管理兵马俑呢?比如如果什么都不管,兵马俑就被随意破坏了。那么我们
首先建了一座房子把兵马俑给封装起来。但是我们目的全封装起来,不让别人看。所以我们开放了售票通
道,可以买票突破封装在合理的监管机制下进去参观。类也是一样,我们使用类数据和方法都封装到一下。
不想给别人看到的,我们使用protected/private把成员封装起来。开放一些共有的成员函数对成员合理的访
问。所以封装本质是一种管理。

这里的封装和Java的本质差不多

 //在类设计中,一般情况,想给别人的(成员函数)访问定义成共有
 //                      不想给别人访问(成员变量)的就定义成私有或保护
struct Stack
class Stack
{
	void Pop()
{}
public:
	void Init(int initSize = 4);
	void Push(STDataType x);

	// ...

private:
	STDataType* a; // 成员变量在这里是声明
	int size;
	int capacity;
};

封装好还是不分装好?

image-20220126203131881

封装更好更规范

类的作用域

类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员,需要使用 :: 作用域解析符指明成员属于哪个类域。

class Person
{
public:
	void PrintPersonInfo();
private:
	char _name[20];
	char _gender[3];
	int _age;
};
// 这里需要指定PrintPersonInfo是属于Person这个类域
void Person::PrintPersonInfo()
{
	cout << _name << " "_gender << " " << _age << endl;
}

类的实例化

image-20220126203620631

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

🍁 类只是一个模型一样的东西,限定了类有哪些成员,定义出一个类并没有分配实际的内存空间来存储它
🍁 一个类可以实例化出多个对象,实例化出的对象 占用实际的物理空间,存储类成员变量
🍁 做个比方。类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图,只设计出需要什么东西,但是并没有实体的建筑存在,同样类也只是一个设计,实例化出的对象才能实际存储数据,占用物理空间

类对象模型

计算类对象大小

对于成员属性是很好计算的,有内存对齐,但是方法如何计算,这里比较特殊,这些方法函数被放在了一个公共的代码段中,可以多次调用,不用多次创建

image-20220126204117929

那么现在看类的大小其实就和结构体一样了,参考之前的内存对齐

下面看两个例子总结特殊情况

class A1 {
public:
	void f1() {}
private:
	int _a;
};

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

int main()
{
	cout << sizeof(A1) << endl;
	A1 aa;
	aa.f1();
	// 他们的大小是1,为什么呢? 大小是1,给1个byte不是为了存储数据,是占位,表示对象存在过
	A2 a2;
	cout << sizeof(a2) << endl;
	A3 a3;
	cout << sizeof(a3) << endl;

	return 0;
}

这段代码中的A3和A2都是没有成员属性的类,他们的大小都不是0而是1

image-20220126204703459

就是为了他们的大小是1,为什么呢? 大小是1,给1个byte不是为了存储数据,是占位,表示对象存在过,为了区分不同的对象,毕竟,不给空间不能表示存在过

结构体内存对齐规则

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

【面试题】

  1. 结构体怎么对齐? 为什么要进行内存对齐

在计算机系统中,规定:每个地址单元都会对应一个字节(8个bit),但是,在c语言中,除了有一个字节(8个bit)的char,也有两个字节(16个bit)的short,也有四个字节(32个bit)的long(在不同的编译器下可能不同)。对于16位或者32位的处理器,即就是大于8位的处理器,由于寄存器的宽度大于一个字节,那么就存在如何将一个多字节的变量的数据如何存放的问题——所以,就有了大小端之分。

  1. 如何让结构体按照指定的对齐参数进行对齐

参考shang’mian

  1. 什么是大小端?如何测试某台机器是大端还是小端,有没有遇到过要考虑大小端的场景
int cheak_sys()
{
	int a = 1;
	return (*(char*)&a);
}
int cheak_sys()
{
	union Un
	{
		char c;
		int i;
	}u;
	u.i = 1;
	return u.c;
}
int main()
{
	int ret = cheak_sys();
	if (1 == ret)
		printf("小端\n");
	else
		printf("大端\n");
	//如果返回1,表示小端
	//如果返回0,表示大端
	return 0;
}

this

this指针

Java中的this

Java中也有
构造器中会用到this关键字,和C++不一样,一般是显式的
🍁 this 关键字可以用来访问本类的属性、方法、构造器
🍁 this 用于区分当前类的属性和局部变量
🍁 访问成员方法的语法:this.方法名(参数列表);
🍁 访问构造器语法:this(参数列表); 注意只能在构造器中使用(即只能在构造器中访问另外一个构造器, 必须放在第一条语句)
🍁 this 不能在类定义的外部使用,只能在类定义的方法中使用。

public Person(String name, int age) {
this.name = name;
this.age = age;
}

那C++中呢?

C++中的this

这里我们先试着引入Date类

C++中this指针是隐藏的,但是为了区别和保证语法的合法,我们在命名的时候要保证凡是命名成员变量的时候,尽量命名风格标准化比如下面的例子中的样子

class Date{
public:
	void Init(int year, int month, int day){
		// 成员函数中,成员前面编译器会自动加this->,显示加也可以
		_year = year;
		_month = month;
		_day = day;
        //也可以显示this指针
		this->_year = year;
		this->_month = month;
		this->_day = day;
	}
private:
	// 凡是成员变量,建议命名风格区分一下
	int _year;
	int _month;
	int _day;
	// 其他风格
	//int year_;
	//int m_year;
	//int mYear;
};

这就体现了封装的好处的体现,使用者不能直接改成员属性,只能赋值,而且我可以在初始化函数中保证好,比如说日期合法化,判断day必须要>0什么的

🌸 Date类中有SetDate与Display两个成员函数,函数体中没有关于不同对象的区分,那当s1调用SetDate函数时,该函数是如何知道应该设置s1对象,而不是设置s2对象呢?
🌸 C++中通过引入this指针解决该问题,即:C++编译器给每个“非静态的成员函数“增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有成员变量的操作,都是通过该指针去访问。只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成。

image-20220127132425981

这就很妙,证明一下

int main()
{
	Date d1;
	cout << "d1:" << &d1 << endl;
	d1.Init(2021, 5, 24); // d1.Init(&d1, 2021, 5, 24);

	Date d2;
	cout << "d2:" << &d2 << endl;
	d2.Init(2021, 5, 25); // d2.Init(&d2, 2021, 5, 25)

	return 0;
}

image-20220127133244218

this指针快速入门

🍁 this指针的类型:类类型* const
🍁 只能在“成员函数”的内部使用
🍁 this指针本质上其实是一个成员函数的形参,是对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针。
🍁 this指针是成员函数第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递

image-20220127132248146

两个有趣的问题

🍁 this指针存在哪里?

this一般是存在栈上的,不同的编译器不同,vs是使用ecx寄存器存储

🍁 this指针可以为空吗?

看看下面这道题

class A{
public:
	void PrintA(){
		cout << _a << endl;
	} 
	void Show(){
		cout << "Show()" << endl;
	}
private:
	int _a;
};
int main()
{
	A* p = nullptr;
	p->PrintA(); // 空指针
	p->Show();  // 正常运行
}

首先并没有报错

image-20220127135827513

但是在 p->PrintA(); // 空指针调用的时候是this访问了空指针

可以访问show()函数,但是不可以访问调用了成员属性的函数PrintA(),毕竟这里对this->_a产生了解引用

本章主要讨论了Cpp中类的几个特点,和this指针,下一章会主要谈谈类的6个默认生成的函数

兄弟们干净又卫生

Test

下列有关this指针使用方法的叙述正确的是( )

作业内容

A.保证基类保护成员在子类中可以被访问

B.保证基类私有成员在子类中可以被访问

C.保证基类公有成员在子类中可以被访问

D.保证每个对象拥有自己的数据成员,但共享处理这些数据的代码

D

下面描述错误的是( )

A.this指针是非静态成员函数的隐含形参.

B.每个非静态的成员函数都有一个this指针.

C.this指针是存在对象里面的.

D.this指针可以为空

C


文章作者: Allen
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 Allen !
  目录