【C++】类和对象(上)

目录

类的定义

1、类的定义格式

2、访问限定符

3、类域

实例化

1、实例化的概念

2、对象大小

this指针

定义

面试题


类的定义

1、类的定义格式

//定义类
class Date
{
	int _year;
	int _month;
	int _day;
};

class是C++中类的定义需要用到一个关键字,Date为类的名字,{ }中为类的主体,注意类定义结束时后面分号不能省略。类的主体中内容称为类的成员:类中的变量称为类的属性或成员变量类中的函数称为类的方法或者成员函数。

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

为了区分成员变量,一般习惯上成员变量会加一个特殊标识,如成员变量前面或者后面加 _ 或者m 开头,注意C++中这个并不是强制的,只是⼀些惯例。(习惯上在前面加上_)

在成员函数前加的public,以及成员变量前加的private都是访问限定符,我们下面会讲到。

C++中 struct 也可以定义类,C++兼容C中struct的用法,同时struct升级成了类,明显的变化是 struct中可以定义函数,⼀般情况下我们还是推荐用class定义类。

//struct定义类
struct Stack
{
	//成员函数
public:
	void Init(int n=4)
	{
		int* tmp = (int*)malloc(sizeof(int) * n);
		if(tmp==nullptr)
			exit(1);
		arr = tmp;
		top = 0;
		capacity = n;
	}
	//...
	
	//成员变量
private:
	int* arr;
	size_t top;
	size_t capacity;
};

相比于C语言,C++中用struct定义类不再需要用到typedef,对于上面的类,可以直接用Stack代表类型。

2、访问限定符

C++用类将成员变量与成员函数结合在⼀块,让对象更加完善,但是,我们在使用过程中往往不希望改变我们成员变量的值,同时,希望成员函数可以被公开使用,所以我们通过访问权限选择性的将其接口提供给外部的用户使用。

C++中有三个访问限定符,public修饰的成员在类外可以直接被访问protectedprivate修饰的成员在类外不能直接被访问,protected和private是⼀样的,以后继承章节才能体现出他们的区别。我们还需要注意以下几点:

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

class定义成员没有被访问限定符修饰时默认为private,struct默认为public。

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

3、类域

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

class Date
{
public:
	//成员函数
    //只进行了函数声明
	void Init(int year, int month, int day);

	//成员变量,一般在前面加上_,来区分
private:
	int _year;
	int _month;
	int _day;
};

//函数定义,需要指定具体类域
void Date::Init(int year, int month, int day)
{
	_year = year;
	_month = month;
	_day = day;
}

类域影响的是编译的查找规则,上面程序中 Init 如果不指定类域Date,那么编译器就把Init当成全 局函数,那么编译时,找不到_year ,_month,_day等成员的声明/定义在哪里,就会报错。指定类域Date,就是知道Init是成员函数,当前域找不到的_year ,_month,_day等成员,就会到类域中去查找。

实例化

1、实例化的概念

用类类型在物理内存中创建对象的过程,称为类实例化出对象

类是对象进行一种抽象描述,是一个模型一样的东西,限定了类有哪些成员变量,这些成员变量只 是声明,没有分配空间,当用类实例化出对象时,才会为这些成员变量分配空间。

一个类可以实例化出多个对象,实例化出的对象占用实际的物理空间,存储类成员变量。打个比方:类实例化出对象就像现实中使用建筑设计图建造出房子,类就像是设计图,设计图规划了有多 少个房间,房间大小功能等,但是并没有实体的建筑存在,也不能住人,用设计图修建出房子,房 子才能住人。同样类就像设计图⼀样,不能存储数据,实例化出的对象分配物理内存存储数据。

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和d2中的成员变量分配空间
	Date d1;
	Date d2;
	d1.Init(2025, 8, 14);
	d1.Print();
	d2.Init(2025, 8, 14);
	d2.Print();
	return 0;
}

2、对象大小

类实例化出的每个对象,都有独立的数据空间,即各自的成员变量存储着各自的数据,所以对象中肯定包含成员变量,那么成员函数是否包含呢?

首先函数被编译后是⼀段指令,对象中没办法存储,这些指令存储在一个单独的区域(代码段),那么对象中非要存储的话,只能是成员函数的指针。再分析一下,对象中是否有存储指针的必要呢,Date实例化d1和d2两个对象,d1和d2都有各自独立的成员变量 _year,_month,_day存储各自的数据,但是d1和d2的成员函数 Init 和 Print 指针却是一样的,存储在对象中就浪费了。如果用Date实例化100个对象,那么成员函数指针就重复存储100次,太浪费了。

其实函数指针是不需要存储的,函数指针是一个地址,调用函数被编译成汇编指令[call 地址],其实编译器在编译链接时,就要找到函数的地址,不是在运行时找,只有动态多态是在运行时找,就需要存储函数地址。

总之,类实例化出的每个对象中的成员变量都需要存储,而成员函数不需要存储。

上面我们分析了对象中只存储成员变量,C++规定类实例化的对象也要符合内存对齐的规则。

1. 结构体内存对齐规则:

可以参考我之前写的博客:C语言_结构体-CSDN博客

C语言_结构体-CSDN博客1、结构体的第一个成员对齐到和结构体变量偏移量为0的地址处(即结构体变量申请内存空间的起始地址处)

2、从第二个成员开始对齐到某个“对齐数”的整数倍的地址处。

对齐数为编译器默认的一个对齐数与该成员变量所占内存空间大小的较小值(vs编译器默认对齐数为8)

3、结构体总大小为最大对齐数的整数倍(结构体成员变量都有一个对齐数,所有对齐数中最大的就是最大对齐数)

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

//计算用类A实例化出的对象的大小?
class A
{
public:
	void Print()
	{
		cout << _ch << endl;
	}
private:
	char _ch;
	int _i;
};

//计算用类B实例化出的对象的大小?
class B
{
public:
	void Print()
	{
		//...
	}
};

我们看到,类B中没有成员变量,所以,按理来说用B实例化出的对象的大小应该为0,但是在C++中规定此时对象的大小为1个字节,因为如果⼀个字节都不给,怎么表示对象存在过呢!所以这里给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和d2
	Date d1;
	Date d2;
	d1.Init(2025, 8, 14);
	d1.Print();
	d2.Init(2025, 8, 15);
	d2.Print();
	return 0;
}

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

编译器编译后,类的成员函数默认都会在形参第⼀个位置,增加⼀个当前类类型的指针,叫做this 指针。但是,C++规定不能在实参和形参的位置显示的写this指针(编译时编译器会处理),但是可以在函数体内显示使用this指针。类的成员函数中访问成员变量,本质都是通过this指针访问的,如Init函数中给_year赋值, this-> _year = year ; 

//Date类的Init的真实原型为
void Init(Date* const this, int year, int month, int day)//实际不能将this指针显示的写出来
{
	_year = year;
	_month = month;
	_day = day;
}

void(Date* const this)//实际不能将this指针显示的写出来
{
    //函数体内可以显示的写this
	cout << this-> _year << " " << this-> _month << " " <<this-> _day << endl;
}

面试题

1.下面程序编译运行结果是()

A、编译报错 B、运行崩溃 C、正常运行

#include<iostream>
using namespace std;
class A
{
public:
	void Print()
	{
		cout << "A::Print()" << endl;
	}
private:
	int _a;
};
int main()
{
	A* p = nullptr;
	p->Print();
	return 0;
}

答案:C。 因为p->Print()只是函数的调用,不涉及空指针的解引用,且函数体内也没有执行解引用操作,所以,这里的p虽然为nullptr,但是程序正常运行。

2.下面程序编译运行结果是()

A、编译报错 B、运行崩溃  C、正常运行

#include<iostream>
using namespace std;
class A
{
public:
	void Print()
	{
		cout << "A::Print()" << endl;
		cout << _a << endl;
	}
private:
	int _a;
};
int main()
{
	A* p = nullptr;
	p->Print();
	return 0;
}

答案:B。 这道题相比于第一道,唯一的差别在于:Print函数体中多了 cout << _a << endl; 这句代码,我们知道编译器编译后,类的成员函数默认都会在形参第一个位置,增加一个当前类类型的this 指针,而此时this == p,则this指针为空,this->_a是对空指针的解引用,所以程序崩溃。

3. this指针存在内存哪个区域的()

A. 栈        B.堆       C.静态区       D.常量区     E.对象里面

答案:A。 this指针其实就是类里面成员函数的一个参数,而函数的参数就是存储在内存的栈区中。

评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值