C++类和对象(上)

一、类的定义

1.1类定义格式

class A
{

};

这便是类类型的定义格式,class为定义类的关键字,A为类的名字,{}中为类的主体,注意类定义结束时后⾯分号不能省略。类体中内容称为类的成员:类中的变量称为类的属性或成员变量; 类中的函数称为类的⽅法或者成员函数。

因为C++是兼容C语言的,所以在C++中,struct同样可以用来定义类

struct A
{

};

与class定义类类型相似,struct为定义类的关键字,A为类的名字,{}中为类的主体,他们两之间的区别我们在后面会讲到。

注意:我们在定义类的成员变量时一般会加上一个特殊标识,如成员变量前⾯或者后⾯加_ 或者m 开头,这样做的目的主要是区分类的成员变量和类成员函数中的形参变量。

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

private:
	// 为了区分成员变量,⼀般习惯上成员变量
	// 会加⼀个特殊标识,如_ 或者 m开头
    // 成员变量只是声明,不是定义,因为他们没有开辟空间 
	int _year; // year_ m_year
	int _month;
	int _day;
};

来看上述代码,如果不在成员变量前面加上一个特殊标识,那么在成员函数Init中成员变量和形参就会分辨不清。

1.2 访问限定符

C++⼀种实现封装的⽅式,用类将对象的属性与⽅法结合在⼀块,让对象更加完善,通过访问权限
选择性的将其接⼝提供给外部的⽤户使⽤。
访问限定符有三种,他们分别是:
公有(public): 定义为公有的,即都能访问使用,在类外可以访问。
私有(private):定义为私有的,即不给你访问使用,只能在类内访问,在类外不能访问。
保护(protect):定义为保护的,即要保护起来,也不能再类外访问。

我们就以刚才的日期类来举个例子:

#include<iostream>
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;
	}

private:
	// 为了区分成员变量,⼀般习惯上成员变量
	// 会加⼀个特殊标识,如_ 或者 m开头
	int _year; // year_ m_year
	int _month;
	int _day;
};

int main()
{
	Date d1;
	d1.Init(2024,7,18);
	d1.Print();
	return 0;
}

我们在主函数定义了一个日期类d1,输入d1._year编译器会报错。

说明了私有成员是无法在类外进行访问的,而Init函数是类中的成员函数,在类内即可访问私有的成员变量。 

而我们通过类内的成员函数便可以访问私有成员变量。 

访问限定符的几点注意事项:

1.访问权限作⽤域从该访问限定符出现的位置开始直到下⼀个访问限定符出现时为⽌,如果后⾯没有访问限定符,作⽤域就到 }即类结束。

2.class定义成员没有被访问限定符修饰时默认为private,struct默认为public。这点区别就是我们上面所讲到的用class和struct定义类的区别。

3.⼀般成员变量都会被限制为private/protected,需要给别⼈使⽤的成员函数会放为public。

1.3 类域

类定义了⼀个新的作⽤域,类的所有成员都在类的作⽤域中,在类体外定义成员时,需要使⽤ :: 作
⽤域操作符指明成员属于哪个类域。
class A
{
public:
	void Print();
};

class B
{
public:
	void Print();
};

此时我们定义了两个类,分别是A和B,此时编译器不会报错,但两个Print函数之间也没有构成函数重载,因为两个Print函数分别处于两个不同的作用域,我们如果要实现Print函数,就要指定作用域,是实现A的还是B的,不然编译器分不清我们实现的是谁的Print函数,所以需要我们使⽤ :: 作 ⽤域操作符指明成员属于哪个类域。

#include<iostream>
using namespace std;

class A
{
public:
	void Print();
};

class B
{
public:
	void Print();
};

void A::Print()//类A的Print
{
	cout << "A::Print" << endl;
}

void B::Print()//类B的Print
{
	cout << "B::Print" << endl;
}


int main()
{
	A a;
	B b;
	a.Print();
	b.Print();
	return 0;
}

结果:

注意:当类中的成员函数声明与定义分离时,就需要指定类域。

 

二、实例化

2.1 实例化的概念

1.⽤类类型在物理内存中创建对象的过程,称为类实例化出对象。
2. 类是对象进⾏⼀种抽象描述,是⼀个模型⼀样的东西,限定了类有哪些成员变量,这些成员变量只 是声明,没有分配空间,⽤类实例化出对象时,才会分配空间。
3. ⼀个类可以实例化出多个对象,实例化出的对象 占⽤实际的物理空间,存储类成员变量。
打个比方,我们定义出来的类就相当于是一张房子的设计图,但设计图在没建造出房子之前都是没有空间的,这也就是为什么上面我说类内的成员变量只是声明没有空间,只有当类实例化后,才会分配空间。
int main()
{
	A a;
	B b;
	a.Print();
	b.Print();
	return 0;
}

上述代码的a和b便是类实例化后的对象。

2.2 类对象的大小

类所实例化的每个对象中都包含着成员变量,那么是否包含成员函数呢?答案其实是不包含的,每个对象都必须要有自己的成员变量,但成员函数却并不需要,因为成员函数的逻辑都是一样的,如果我们实例化很多个对象都包含着成员函数是没有必要的,成员函数在编译链接时便能找到地址,不需要再存储。

那么我们怎么判断类对象的大小呢?我们在学C语言结构体的大小时学到过内存对齐的规则,其实C++的类的大小也与内存对齐有关,我们只需按照计算C语言结构体的大小去计算类的大小即可。

内存对齐规则:

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

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

3.注意:对⻬数 = 编译器默认的⼀个对⻬数 与 该成员⼤⼩的较⼩值。

4.VS中默认的对⻬数为8

5.结构体总⼤⼩为:最⼤对⻬数(所有变量类型最⼤者与默认对⻬参数取最⼩)的整数倍。

6.如果嵌套了结构体的情况,嵌套的结构体对⻬到⾃⼰的最⼤对⻬数的整数倍处,结构体的整体⼤⼩就是所有最⼤对⻬数(含嵌套结构体的对⻬数)的整数倍。

#include<iostream>
using namespace std;


class A
{
public:
	void Print()
	{
		cout << _ch << endl;
	}
private:
	char _ch;
	int _i;
};


class B
{
public:
	void Print()
	{
		//...
	}
};


class C
{

};


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

下面我们我们来计算一下上面代码的三个类的大小。

 

通过内存对齐的规则我们可以很容易得计算出a的大小为8字节,但类B和C中一个只存放了成员函数,一个什么都没有存放,为什么他们都占1个字节呢?按道理来说不应该1个字节都不占吗?

这里给一个字节是因为要表示对象的存在,因为如果⼀个字节都不给,怎么表⽰对象存在过呢!所以这⾥给1字节,纯粹是为了占位标识对象存在。

三、this指针

我们来看下列代码:

#include<iostream>
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;
	}

private:
	// 为了区分成员变量,⼀般习惯上成员变量
	// 会加⼀个特殊标识,如_ 或者 m开头
	int _year; // year_ m_year
	int _month;
	int _day;
};

int main()
{
	Date d1;
	Date d2;
	d1.Init(2024,7,18);
	d1.Print();
	d2.Init(2024, 7, 19);
	d1.Print();
	return 0;
}

 Date类中有 Init 与 Print 两个成员函数,函数体中没有关于不同对象的区分,那当d1调⽤Init和 Print函数时,该函数是如何知道应该访问的是d1对象还是d2对象呢?那么这⾥就要看到C++给了 ⼀个隐含的this指针解决这⾥的问题。

编译器编译后,类的成员函数默认都会在形参第⼀个位置,增加⼀个当前类类型的指针,叫做this
指针。⽐如Date类的Init的真实原型为, void Init(Date* const this, int year, int month, int day)
可以说,this指针就帮我们分辨出了该打印和初始化哪个类对象。这里要注意的是,this指针是隐含的,那我们能不能在函数上加上this指针呢?这实际上是不行的, C++规定不能在实参和形参的位置显⽰的写this指针(编译时编译器会处理),但是可以在函数体内显示 使⽤this指针。
就比如Init函数 类的成员函数中访问成员变量,本质都是通过this指针访问的,如Init函数中给_year赋值, this->_year = year;我们将_year修改为this- >_year是可以的。
this指针的特性:
1.this指针的类型:类类型* const,所以this指针本身不能修改,但能修改指向的对象。
2.this指针只能在成员函数内部使用,这在前面以及讲过了。
3.隐式存在:this指针不需要在函数参数列表中声明,它是由编译器自动插入的。在成员函数体内部,你可以像使用任何其他指针一样使用 this
4.this指针不是对象的一部分,也不占用对象的内存空间;它是在函数调用栈上创建的,仅在函数执行期间存在。

总结:

以上就是我对于类和对象(上)的理解,如果对你有帮助的话,记得一键三连,感谢大家。

  • 28
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值