C++ 类和对象(一)类、访问限定符、this指针

前言

        C++中的类(class)是实现数据抽象和面向对象程序设计的核心。本文作为类和对象的开篇,将介绍有关类的基础知识,之后会持续更新类和对象的深入内容。

目录

前言

1 类的引入

2 类的定义

3 访问限定符和封装

3.1 访问限定符

3.2 封装

4 类的实例化及类对象的储存

4.1 实例化

4.2 类对象的储存

4.3 类的大小计算

5 this指针

5.1 this指针的引入

5.2 this能否为空指针

5.3 代码规范


1 类的引入

 C++兼容C语言,C语言中的结构(struct)在C++中扩充成了类。

struct Stack
{
    // ...
};

struct Stack st1; // C语言定义结构体变量

Stack st2;       // C++创建类对象

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

struct Stack
{
	// 成员函数
	void Init()
	{
		a = nullptr;
		top = capacity = 0;
	}

	void Push(int x)
	{
		// ...
	}

	// 成员变量
	int* a;
	int top;
	int capacity;
};

Stack st;
st.Init();  // 使用:类对象.成员变量/成员函数
st.Push(1);

2 类的定义

class className

{

        // 类体

};

class为定义类的关键字,className为类的名字,{}中为类的主体。类体中的内容为类的成员:成员变量(类的属性),成员函数(类的方法)。

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

类的定义方式:

        1.声明和定义全部放在类体中。成员函数在类中定义可能会被编译器当作内联函数处理。

        2.类声明放在.h文件中,成员函数定义放在.cpp文件中(成员函数前需加 类名::

3 访问限定符和封装

3.1 访问限定符

访问限定符分为:public(公有),protected(保护)和private(私有)

说明:

  • public修饰的成员在类外可以直接被访问
  • protected和private修饰的成员在类外不能直接被访问
  • 作用域从该访问限定符开始直到下一个访问限定符出现或类结束
  • class的默认访问权限为private,struct为public

3.2 封装

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

// .h文件中声明成员函数
class Stack
{
public:
	void Init();
	void Push(int x);
	int Top();
private:
	int* a;
	int top;
	int capacity;
};

// .cpp文件中定义成员函数
void Stack::Init()
{
	a = nullptr;
	top = capacity = 0;
}
void Stack::Push(int x)
{
	if (top == capacity)
	{
		int newCapacity = (capacity == 0 ? 4 : capacity * 2);
		int* tmp = (int*)realloc(a, sizeof(int) * newCapacity);
		if (tmp == nullptr)
		{
			perror("relloc fail");
			return;
		}
		a = tmp;
		capacity = newCapacity;
	}
	a[top++] = x;
}
int Stack::Top()
{
	return a[top - 1];
}
int main()
{
	Stack st;
	int top = st.Top();
	// st.a[st.Top]  不能访问
	return 0;
}

如上定义栈类(Stack),成员变量top初始为0指向栈顶元素下一个位置,用户可能认为top指向栈顶元素,通过访问成员变量获取栈顶元素(st.a[st.top])出错,因此通过将Stack进行封装,将类的属性设为私有,用类的方法Top()实现获取栈顶元素供用户使用。

4 类的实例化及类对象的储存

4.1 实例化

        用类创建对象的过程称为类的实例化。类就像是一种模型,对对象进行描述,限定了类有哪些成员,定义一个类并没有分配实际的空间来储存它,一个类可以实例化出多个对象,实例化出的对象才占用实际的物理空间。

4.2 类对象的储存

        定义一个类,它的成员变量实际是一种声明,只有实例化出的对象才会定义这些成员变量,因此实例化类对象需要开辟物理空间储存成员变量,那么类的成员函数需要每个对象储存吗,类的成员函数不仅要在类中声明,也要在类中定义,每个对象都可以使用类的成员函数,所以类的成员函数不需要实例化对象储存,事实上它们也不需要类储存,因为它们存放在公共代码区。

bf5289a7691841b6b5d8d379b52091d1.png

4.3 类的大小计算

        那么计算类的大小只需要计算类中的成员变量,其计算方式遵循结构体内存对齐规则。

结构体内存对齐规则

1. 第一个成员在与结构体偏移量为0的地址处。

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

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

VS中默认的对齐数为8

3. 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。

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

如创建三个类,它们分别是空类、只含成员函数的类、只含成员变量的类、含成员变量和成员函数的类,分别计算它们的大小。

// 空类
class A
{

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

	}
};
// 类中仅有成员变量
class C
{
private:
	int a;
	int b;
	int c;
};
// 类中既有成员函数又有成员变量
class D
{
public:
	void Func()
	{

	}
private:
	int a;
	int b;
	int c;
};
int main()
{
	cout << "sizeof(A) " << sizeof(A) << endl;
	cout << "sizeof(B) " << sizeof(B) << endl;
	cout << "sizeof(C) " << sizeof(C) << endl;
	cout << "sizeof(D) " << sizeof(D) << endl;
	return 0;
}

结果:

76712faecaf449e18c93960d6250b259.png

C类含3个整型其大小为12字节,D类和C类的大小相同,可以验证成员函数不需要类储存,A类和B类的大小都为1,是因为编译器给空类(包括只含成员函数的类)一个字节用来标识。

5 this指针

5.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 d1;
	Date d2;
	d1.Init(2022, 7, 20);
	d2.Init(2022, 7, 21);
	return 0;
}

如上定义一个日期类,创建两个对象d1,d2,类中的函数没有关于不同对象的区分,当d1调用Init()函数时,函数是如何将参数传给对象d1的成员变量(_year,_month,_day)而不是对象d2的呢?

C++中引入this指针解决该问题,即:类的每个成员函数有一个隐藏的指针参数this,当对象调用函数时会将对象的地址传给函数的this指针,函数内所有成员变量的操作都通过this指针去访问。this指针的设置和传参由编译器自动完成,用户不能显式写出,在函数体内可以使用。

this指针的特性:

  1. this指针的类型:类类型* const,在成员函数中不能给this指针赋值。
  2. this指针是成员函数的第一个隐含指针形参,对象调用成员函数时将对象地址作为实参传递给this指针,对象中不储存this指针
  3. this指针由编译器通过ecx寄存器自动传递,不需要用户传递

函数调用实际实现如下:

void Init(Date* const this, int year, int month, int day)
    {
        this->_year = year;
        this->_month = month;
        this->_day = day;
    }
    void Print(Date* const this)
    {
        cout << this->_year << '/' << this->_month << '/' << this->_day << endl;
    }

d1.Init(&d1, 2022, 7, 20);
d1.Print(&d1);

217cdf681336469c8edf3dc147864924.png

8340a59ac9844067b3c5bedddb36d5b7.png

 通过验证,发现:对象d1,d2调用Init()和Print()函数时,函数的this指针的值和它们本身地址相同。

5.2 this能否为空指针

在一般情况下,不能对空指针进行解引用操作,所以类对象的指针不能为空,this指针也就不能为空。如果类的成员函数中没有对this指针的实际解引用,即便给this传空指针也不会出现问题。

如下程序会正常执行:

74277205353c4d47954637f003777b89.png

  p->Print()或(*p).Print()好像是对空指针p进行了解引用,实际上它调用Print()函数时会直接把p传给this,因为实参&(*p)和p是相同的。这种情况在实际中可能很少出现,但它是可行的。

如下程序就会运行崩溃:

f7f89f601dd94b6fb0bbba11b3c08c12.png

  因为Print()函数内对成员变量进行操作,访问_a需要对this指针进行解引用,this->_a,对空指针解引用引起运行崩溃。

5.3 代码规范

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;
};

ecfaa2acae424f89abece8f14784b724.png

 执行以上程序,输出的却是随机值,说明对象d1调用了Init()函数但是d1的成员变量并没有初始化,这是为什么呢?

其原因是函数Init()内的year,month,day变量都是局部变量(函数形参),比如year = year,我们期望左边的year是类的成员变量year,右边是函数形参year,事实上左边的year也是函数的形参,因为year是存在的局部变量,函数内的year自然都优先与它对应,编译器不会“智能”地对左边的变量使用this->访问,它就不会对应类的成员变量。以上操作如同定义一个变量int a;,再赋值变量给它自己 a = a;

d04ee7c4249e4527a9b5101eee43355b.png

 因此要实现我们的期望,就有两种方法:显式使用this指针或改变变量名。

2.显式使用this指针

通过显式使用this指针,将左边的变量设为类的成员变量,即:

void Init(int year, int month, int day)
    {
        this->year = year;
        this->month = month;
        this->day = day;
    }

3.成员变量书写规范

将类成员函数的参数与类成员变量书写不同就能避免this指针失灵的问题。常见的代码书写规范是在类的成员变量前或后加_,即:

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

private:
    int _year;
    int _month;
    int _day;

如果要在函数内使用全局变量,其参数也不能和全局变量重名,否则会将全局的名字隐藏,因此规范的变量命名是解决一些问题的通用方法,一般不使用第一种方法(显示使用this指针)。

 

如果本文内容对你有帮助,可以点赞收藏,感谢支持,期待你的关注。

下篇预告:C++ 类和对象(二)构造函数、析构函数、拷贝构造函数 http://t.csdn.cn/noJ87

猜你喜欢

转载自blog.csdn.net/2301_79391308/article/details/132469116