类与对象 (上)

一、类的引入

在 C语言中,结构体内只可以定义变量,而在 C++ 中,将结构体升级为了类,既可以定义变量,又可以定义函数,但是 C++ 中,更喜欢用 class 来定义类

//C++ 兼容 C,并且将结构升级为类,可以在其中定义函数
struct A
{
	int A;
	
	void f()
	{
		cout << "struct A f()" << endl;
	}
};

类也是新定义的一个域,内部访问内部时会在整个类中查找,不受限制,外部访问内部时需要用类名 + 域作用限定符指定访问

二、类的定义

用 class 关键字定义类类中的变量 叫做 类的属性成员变量类中的函数 叫做 类的方法成员函数

class 类名
{
	//成员变量的声明
	//...

	//成员函数
	//...
	
};	//和结构体一样,这里有分号

成员函数可以直接定义在类中,也可以定义在类外,直接定义在类中的成员函数,可能会被编译器识别为内联函数

写项目时通常需要将声明和定义分离:在 .h 文件中声明成员变量和成员函数,在 .cpp 文件中指定类中的函数进行定义

//A.h 文件
#pragma once
#include <iostream>

using namespace std;

class A
{
	//声明成员变量
	int a1;
	int a2;

	//声明成员函数 f1、f2
	void f1();
	void f2();
};

//A.cpp 文件
#include "A.h"

//类也是一个域,默认不会到类域中找
//如果定义的是类中的函数,需要显示指定,否则认为是定义在全局中的函数
void A::f1()
{
	cout << "A f1()" << endl;
}

void A::f2()
{
	cout << "A f2()" << endl;
}

为了避免下述情况,需要对成员变量名进行修饰,可以在变量名之前或之后加下划线 _,也可以采用其他方法

class Date
{
	//成员变量的声明
	int year;
	int month;
	int day;

	//此时 Init 的参数便不能使用 year 这个名字,成员函数的参数取名变得麻烦了
	//这种情况下,局部优先,使用的是参数 year
	void Init(int year, int month, int day)
	{
		year = year;
		month = month;
		day = day;
	}
};

三、类的访问限定符

类的访问限定符是为了 可以选择性的将类中的成员提供给外部使用
通常将成员变量设置为 private,提供给外部使用的成员函数设置为 public

public(公开的):类中和类外都可以直接访问类中的成员
private(私有的):类中可以直接访问类中的成员,类外不可以直接访问类中的成员
protected(保护的):在以后学习继承时补充

访问限定符的作用域:
从该访问限定符的位置到下一个访问限定符之间,如果后面没有访问限定符,作用域到类的右花括号 }

在这里插入图片描述

C++ 中 struct 和 class 定义类的区别:struct 默认访问控制权限为 public(因为struct要兼容C),class 的默认访问权限为 private

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

四、类的实例化及对象的大小

类是一个自定义类型,用类这个类型 创建变量的过程,称为类的实例化,习惯上将这里的变量叫做对象

类实例化对象时可以省略关键字 class,struct 也可以定义类,因此 C++ 中结构体创建变量或实例化对象的时候都可以省略 struct

class Date
{
public:
	int _year;
	int _month;
	int _day;

public:
	void Init(int year, int month, int day)
	{
		//...
	}
};

int main()
{
	//两种方式都可以实例化对象,通常省略 class
	class Date d1;
	Date d2;

	return 0;
}

类的实例化就是为类创建的对象开辟内存空间,对于类本身而言,就像整形 int 一样,是不占空间的

class Date
{
public:
	int _year;
	int _month;
	int _day;

	void Init(int year, int month, int day)
	{
		//...
	}
};

int main()
{
	//实例化对象开辟空间
	Date d;
	
	//Date._year; //error 类本身没有开辟空间
	d._year = 2023;	//对象有空间

	return 0;
}

类中既有成员变量,又有成员函数,类实例化后的对象所占空间如何计算呢?

类可以实例化多个对象,每个对象的成员变量可能会存储不同的数据,但是调用的函数都是相同的

为了避免浪费空间(每个对象中都要存储相同的东西),于是 对象中即不会保存成员函数的代码,也不会保存成员函数的地址,而是 将函数的地址放到公共代码区(代码段),并用类域进行限制,函数的代码也放到公共代码区中

对象的大小即为所有成员变量的大小,但是成员变量需要满足 结构体内存对齐

#include <iostream>

using namespace std;

class demo
{
private:
	char m1;
	int m2;
	int m3;
	
public:
	void print()
	{
		cout << "class demo" << endl;
	}
};

int main()
{
	demo d;

	//sizeof 计算类型创建的变量所占空间的大小,输出 12 12
	cout << sizeof(d) << " ";
	cout << sizeof(demo) << endl;

	return 0;
}

空类 和 仅有成员函数的类 都没有成员变量,但是可以实例化对象,因此为了区别对象的不同,会为对象开辟一个空间

#include <iostream>

using namespace std;

//空类
class A1
{

};

//仅有成员函数的类
class A2
{
public:
	void f()
	{
		cout << "class A2 f()" << endl;
	}
};

int main()
{
	//空类可以实例化对象
	A1 a1;

	//输出 1 1
	cout << sizeof(a1) << " ";
	cout << sizeof(A1) << endl;

	//仅有成员函数的类,也可以实例化对象
	A2 a2;

	//输出 1 1
	cout << sizeof(a2) << " ";
	cout << sizeof(A2) << endl;
}

五、this 指针

首先创建一个 Date 类

#include <iostream>

using namespace std;

class Date
{
public:
	void Init(int year = 1970, int month = 1, int day = 1)
	{
		_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;

	//对象通过 .运算符访问成员
	//输出 1970年1月1日
	d1.Init();
	d1.Print();

	//对象指针通过 -> 运算符访问指向的对象的成员
	//也可以先解引用后,再用 .运算符访问成员
	//输出 2023年2月4日
	d2->Init(2023, 2, 4);
	(*d2).Print();

	return 0;
}

既然对象中不存储函数的地址,对象调用成员函数时,应该指定访问,但是编译器会根据对象的类型,自动的将 d1.Init 解释为 Date:: Init去公共代码去的类域中找地址

d1 和 d2 在调用 Init、Print 成员函数时编译器是如何知道成员函数中使用的 _year 等成员变量 是调用对象的成员变量呢?
编译器 在对象调用成员函数时,会隐式的将对象的地址作为参数传递还会隐式的在成员函数的参数中用 (对象指针 const this)这个参数 来接收对象的地址,并且成员函数中的 _year都会被编译器解释为 this->_year,在成员函数中可以使用 this 指针

const 修饰 this,this 在成员函数中不能被修改指向

void Init(int year ...) 编译器处理为 void Init(Date* const this, int year ...)
void Print() 编译器处理为 void Print(Date* const this)

d1.Print() 编译器处理为 d1.Print(&d1)
d2->Print() 编译器处理为 d2.Print(d2)

在调用函数时,编译器需要为 this 指针传递调用成员的对象的地址,因此 Date.Init() 是错误的

注意:用户不能主动为成员函数中的 this 传参,也不能在成员函数中主动设置 this 参数,这些都只能编译器做

看如下代码,你认为会报错吗?

#include <iostream>

using namespace std;

class A
{
public:
	void Init(int a = 1)
	{
		_a = a;
	}

	void Print()
	{
		cout << this << endl;
	}

private:
	int _a;
};

int main()
{
	A* pa = nullptr;

	pa->Init();		//运行崩溃
	pa->Print();	//正常运行 输出 0000000000000000

	(*pa).Init();	//运行崩溃
	(*pa).Print();	//正常运行 输出 0000000000000000

	return 0;
}

虽然 pa 是 nullptr,但是在对象调用函数时,编译器只做两件事

  1. 在 pa 所指对象的类中查找函数地址
  2. 传递 this 指针

因此只有当成员函数对 nullptr 进行解引用时,才会崩溃

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值