当前位置: 首页 > news >正文

类和对象基础(C++)

目录

1.struct和class的异同

1.1 C语言中的struct

1.2 C++中的struct

1.3 class

2.类的定义

3.访问限定符

3.1访问限定符的作用域

3.2成员属性设置为私有

4.类的默认成员函数

4.1构造函数和析构函数

4.2拷贝构造函数

5.深拷贝和浅拷贝

6.再谈构造函数 

6.1初始化列表

6.2类对象作为类成员

6.3静态成员

7.C++对象模型和this指针

7.1成员变量和成员函数分开存储

7.2this指针的概念

7.3空指针访问成员函数

7.4const修饰成员函数


1.struct和class的异同

1.1 C语言中的struct

C语言中,struct可以定义结构体:

struct SNode
{
	struct SNode* next;
	int val;
};

 结构体中只能有变量,而不能出现函数。

1.2 C++中的struct

在C++中,兼容了C语言中使用struct定义结构体的语法,而且新增加了一种用法:使用struct定义类。

struct在C++和C语言中的不同点:

  • C语言定义的结构体中只能出现变量; C++定义的类中可以出现函数。

1.3 class

C++中可以使用struct定义类,也可以使用class来定义类,这两种定义方式也有区别:

  • struct定义的默认成员时公有的; class定义的默认成员时私有的。

提到公有、私有,就不得不谈一谈访问限定符了。

2.类的定义

语法:class 类名{  访问权限:  属性/行为};

实例代码:

//设计一个圆类
const double PI = 3.14;
class Circle
{
public:  //访问权限  公共的权限

	//属性
	int m_r;//半径

	//行为
	//获取到圆的周长
	double calculateZC()
	{
		//2 * pi  * r
		//获取圆的周长
		return  2 * PI * m_r;
	}
};

3.访问限定符

三种访问限定符:

  • public(公有的)  在类内和类外都可以直接访问
  • protected(保护的)  
  • private(私有的) protected和private都是在类内可以访问,在类外不能访问(具体的区别在继承,在这里不进行区分)

提到类和对象,就少不了访问限定符,类对成员的封装,就借助了访问限定符。

上面我们提到struct定义的类默认是共有的,class定义的类默认是私有的,下面我们写一段程序,来进一步理解:

struct person
{
	const char* name;
	int age;
	void show()
	{
		cout << name << " " << age << endl;
	}
};

int main()
{
	person p1;
	p1.name = "zhangsan";
	p1.age = 18;
	p1.show();
	return 0;
}

默认权限是公有的,在类外和类内都可以直接访问。

把以上代码中的struct 改为class,程序就会报错:

原因就是class定义的类成员默认是私有属性,在类外不能直接访问。 

但如果我们在类中使用访问限定符之后,就能解决这个问题: 

class person
{
public:
	void show()
	{
		cout << name << " " << age << endl;
	}
	const char* name;
	int age;
	
};

int main()
{
	person p1;
	p1.name = "zhangsan";
	p1.age = 18;
	p1.show();
	return 0;
}

3.1访问限定符的作用域

1.从一个访问限定符到另一个访问限定符。2.从一个访问限定符到类结束。

class person
{
public:  public和private之间的是public属性
	void show()
	{
		cout << name << " " << age << endl;
	}
private:   private到类定义结束时private属性
	const char* name;
	int age;
	
};

3.2成员属性设置为私有

优点:

  • 将所有成员属性设置为私有,可以控制读写权限
  • 对于写权限,可以检测数据的有效性
class Person {
public:
	//姓名设置可读可写
	void setName(string name) {
		m_Name = name;
	}
	string getName()
	{
		return m_Name;
	}
	//获取年龄 
	int getAge() {
		return m_Age;
	}
	//设置年龄
	void setAge(int age) {
		if (age < 0 || age > 150) {
			cout << "你个老妖精!" << endl;
			return;
		}
		m_Age = age;
	}

	//情人设置为只写
	void setLover(string lover) {
		m_Lover = lover;
	}

private:
	string m_Name; //可读可写  姓名
	int m_Age; //只读  年龄
	string m_Lover; //只写  情人
};

4.类的默认成员函数

4.1构造函数和析构函数

对象的初始化和清理是两个非常重要的安全问题,一个对象或者变量没有初始化状态的话,那么造成的后果是严重的。

同样的使用完一个对象或者变量,没有及时清理的话,也会造成安全问题。

C++利用构造函数和析构函数来解决以上问题,这两个函数由编译器自动调用,完成对象的初始化和清理工作;

我们不需要提供构造和析构函数,编译器会提供构造和析构函数的空实现。

构造函数语法:类名(){}

  • 构造函数,没有返回值也不写void
  • 函数名和类名相同
  • 构造函数可以有参数,因此可以发生重载
  • 程序在调用对象的时候自动进行构造,无需手动调用,而且只调用一次

构造函数的调用规则:

默认情况下,C++编译器至少给一个类添加3个函数

  • 默认构造函数(无参,函数体为空)
  • 默认析构函数(无惨,函数体为空)
  • 默认拷贝函数,对属性进行拷贝

构造函数的调用规则如下:

  • 如果用户定义有参构造函数,C++不会提供默认构造函数,但是会提供拷贝函数
  • 如果用户定义拷贝构造函数,C++不会再提供其他构造函数
//以person类为例
class person
{
public:
	void show()
	{
		cout << name << " " << age << endl;
	}
	person() //构造函数
	{
		cout << "person()" << endl;
	}
private:
	const char* name;
	int age;
	
};

析构函数的语法~类名(){}

  • 析构函数也是没有返回值的,也不写void
  • 函数名和类名相同,在名称的前面加上~
  • 析构函数不可以有参数,因此不可以发生重载
  • 程序在对象销毁前自动调用析构函数,无需手动调用,而且只调用一次
class person
{
public:
	//析构函数
	~person()
	{
		cout << "~person()" << endl;
	}

};

4.2拷贝构造函数

class person
{
public:
	person()
	{
		cout << "person()" << endl;
	}
	person(const char* name, int age)
	{
		_name = name;
		_age = age;
	}
private:
	const char* _name;
	int _age;
	
};

int main()
{
	person p1("lisi", 20);//调用构造函数
	person p2(p1);//调用拷贝构造
	return 0;
}

 通过调试可以看到,p2初始化的值就是拷贝的p1对象的,而且拷贝构造函数是编译器自动生成的。

5.深拷贝和浅拷贝

  • 浅拷贝:简单的赋值拷贝操作
  • 深拷贝:在堆区重新申请空间,进行拷贝操作
class Person {
public:
	//无参(默认)构造函数
	Person() {
		cout << "无参构造函数!" << endl;
	}
	//有参构造函数
	Person(int age ,int height) {
		
		cout << "有参构造函数!" << endl;

		m_age = age;
		m_height = new int(height);
		
	}
	//拷贝构造函数  
	Person(const Person& p) {
		cout << "拷贝构造函数!" << endl;
		//如果不利用深拷贝在堆区创建新内存,会导致浅拷贝带来的重复释放堆区问题
		m_age = p.m_age;
		m_height = new int(*p.m_height);
		
	}

	//析构函数
	~Person() {
		cout << "析构函数!" << endl;
		if (m_height != NULL)
		{
			delete m_height;
		}
	}
public:
	int m_age;
	int* m_height;
};

浅拷贝可能导致的问题和解决办法:

进行拷贝时,这两个对象指向的是同一块空间,而在使用析构函数对每个对象进行销毁时都会释放这块空间,这就会造成堆区空间重复释放的问题。  解决办法:深拷贝,在堆区开辟不同的空间。

6.再谈构造函数 

6.1初始化列表

作用:C++提供了初始化列表的语法,用来初始化属性。

语法:构造函数():  属性1(值1), 属性2(值2)...{}

代码举栗:

class Person {
public:
	//初始化列表方式初始化
	Person(int a, int b, int c) 
        :m_A(a)
        ,m_B(b)
        ,m_C(c) 
        {}
	void PrintPerson() {
		cout << "mA:" << m_A << endl;
		cout << "mB:" << m_B << endl;
		cout << "mC:" << m_C << endl;
	}
private:
	int m_A;
	int m_B;
	int m_C;
};

 当然m_A,m_B,m_C也可以在构造函数内部进行初始化,这种成员变量是没有限制的;

 但是类中有以下成员的话,就必须使用初始化列表才能进行初始化(不使用初始化列表是错误的):

  • 引用成员变量
  • const成员变量
  • 自定义类型成员(且该类中没有默认构造函数)

总结:内置类型可以使用初始化列表,以上三种特殊类型只能使用初始化列表,所以能使用初始化列表的话就使用初始化列表

6.2类对象作为类成员

C++类中的成员可以是另一个类的对象,我们称该成员为对象成员

class A {}
class B
{
    A a;
}

创建和销毁对象的时候有一个顺序问题,构造:先调用A or 先调用B?析构:先调用A or 先调用B?

#include <iostream>
using namespace std;
class Phone
{
public:
	Phone(string name)
		:m_PhoneName(name)
	{
		cout << "Phone构造" << endl;
	}
	~Phone()
	{
		cout << "Phone析构" << endl;
	}
	string m_PhoneName;
};

class Person
{
public:
	Person(string name, string pName)
		:m_Name(name)
		, m_Phone(pName)
	{
		cout << "Person构造" << endl;
	}
	~Person()
	{
		cout << "Person析构" << endl;
	}
	void playGame()
	{
		cout << m_Name << " 使用" << m_Phone.m_PhoneName << " 手机" << endl;
	}
	string m_Name;
	Phone m_Phone;

};

int main()
{
	Person p("阿飞", "ipone13");
	p.playGame();
	return 0;
}

我们发现,初始化先调用的是Phone的构造,然后再调用的Person的构造;

销毁先调用的是Person的析构,再调用的是Phone的析构。

这个循序我们可以把Phone比作一个汽车的零件,把Person比作汽车,在组装的时候,肯定要先构造零件,再构造汽车;

在拆掉汽车的时候,要先把汽车拆掉,才能进一步拆除汽车的零件。

6.3静态成员

静态成员包括:

静态成员变量

  • 所有对象共享同一份数据
  • 在编译阶段分配内存
  • 类内生命,类外初始化

静态成员函数

  • 所有对象共享同一个函数
  • 静态成员函数只能访问静态成员变量

类内生命,类外初始化:

class Person
{
public:
	static int m_A; //静态成员变量
private:
	static int m_B; //静态成员变量也是有访问权限的
};
int Person::m_A = 10;
int Person::m_B = 10;

静态成员函数:

class Person
{
public:
	static void func()
	{
		cout << "func调用" << endl;
		m_A = 100;  //静态成员函数只能访问静态成员变量
	}
private:
	static int m_A; 
	int m_B; 
};

7.C++对象模型和this指针

7.1成员变量和成员函数分开存储

在C++中,类内的成员变量和成员函数分开存储,只有非静态的成员变量才属于类的对象上

class Person {
public:
	Person() 
    {
		mA = 0;
	}
	void func() 
    {
		cout << "mA:" << this->mA << endl;
	}
private:
    int mA;
	static int mB; 
};

int main() 
{
    Person p;
	cout << sizeof(p) << endl;//只有非静态的成员变量属于对象,所以输出为4
	return 0;
}

7.2this指针的概念

每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会公用同一块代码;

那么问题就是:这一块代码是如何区分是哪个对象调用了自己呢?答:this指针。

this指针指向被调用的成员函数所属的对象,this指针是隐含每一个非静态成员函数的一种指针。

this指针是不需要定义的,可以直接使用。

this指针的两个用途:

  • 当形参和成员变量同名的时候,可以用this指针来区分(这个作用和Java中的this是相同的)
  • 在类的非静态成员函数中返回对象本身,可以使用return *this
class Person
{
public:
	Person(int age)
	{
		//1、当形参和成员变量同名时,可用this指针来区分
		this->age = age;
	}
	Person& PersonAddPerson(Person p)
	{
		this->age += p.age;
		return *this;
	}
	int age;
};
void test01()
{
	Person p1(10);
	cout << "p1.age = " << p1.age << endl;

	Person p2(10);
	p2.PersonAddPerson(p1).PersonAddPerson(p1).PersonAddPerson(p1);
	cout << "p2.age = " << p2.age << endl;
}
int main()
{
	test01();
	return 0;
}

7.3空指针访问成员函数

C++中空指针也是可以调用成员函数的,但是需要注意的是有没有用到this指针;

如果用到this指针,需要加以判断保证代码的健壮性。

//空指针访问成员函数
class Person {
public:

	void ShowClassName() {
		cout << "Person类!" << endl;
	}

	void ShowPerson() {
		if (this == NULL) {
			return;
		}
		cout << mAge << endl;
	}

public:
	int mAge;
};

void test01()
{
	Person * p = NULL;
	p->ShowClassName(); //空指针,可以调用成员函数
	p->ShowPerson();  //但是如果成员函数中用到了this指针,就不可以了
}

int main() {

	test01();
	return 0;
}

成员函数在使用this指针的时候,需要进行检查。

7.4const修饰成员函数

常函数:

  • 成员函数后面加const之后我们称这个函数为常函数
  • 常函数内不可以修改成员属性

常对象:

  • 声明对象前加const称该对象为常对象
  • 常对象只能调用常函数

const对象和函数的使用:

class Person {
public:
	Person() 
    {
		m_A = 0;
		m_B = 0;
	}
	//this指针的本质是一个指针常量,指针的指向不可修改
	//如果想让指针指向的值也不可以修改,需要声明常函数
	void ShowPerson() const 
    {
		//const Type* const pointer;
		//this = NULL; //不能修改指针的指向 Person* const this;
		//this->mA = 100; //但是this指针指向的对象的数据是可以修改的

		//const修饰成员函数,表示指针指向的内存空间的数据不能修改,除了mutable修饰的变量
		this->m_B = 100;
	}

	void MyFunc() const 
    {
		//mA = 10000;
	}
public:
	int m_A;
	mutable int m_B; //可修改 可变的
};
//const修饰对象  常对象
void test01() 
{
	const Person person; //常量对象  
	cout << person.m_A << endl;
	//person.mA = 100; //常对象不能修改成员变量的值,但是可以访问
	person.m_B = 100; //但是常对象可以修改mutable修饰成员变量
	//常对象访问成员函数
	person.MyFunc(); //常对象不能调用const的函数
}

int main() 
{
	test01();
	return 0;
}

类和对象的基础内容到这里就结束了,类和对象是我们学习面向对象语言的重点,像封装、继承、多态都和类和对象有着密不可分的关系;一定要熟练掌握!!!

相关文章:

  • npm详解:Node.js包管理器的奥秘
  • 高扬程消防水泵在火灾中的关键作用/恒峰智慧科技
  • Leetcode—2739. 总行驶距离【简单】
  • 笔记本上打造专属的LLama3聊天机器人
  • 美国洛杉矶服务器托管需要了解什么?
  • 蓝桥杯国赛填空题(跑步计划)
  • android开发平台,Java+性能优化+APP开发+NDK+跨平台技术
  • 什么是去中心化云计算?
  • shardingsphere 集成springboot【水平分表】
  • 半导体行业案例:Jira与龙智插件助力某半导体企业实现精益项目管理
  • 【wine】中文字体显示乱码 口口口 直接复制windows字体
  • 设计模式——策略模式
  • Maven简介、安装、使用、依赖传递
  • 11.MongoDB系列之连接副本集
  • 电子与电路复习题重点大题(附答案)
  • 【精品】seata综合示例:订单-库存-扣款
  • Spring常用注解的详细介绍(包你学明白)
  • Torchtext快速入门(一)——Vocab
  • 34461A数字万用表参数
  • AI加速(四)| 衣柜般的分层存储设计
  • Linux格式化输出当前时间
  • c++类和对象中
  • ATT汇编总结_9_静态库与动态库
  • VS Code For Web 深入浅出 -- 导读篇
  • 设计模式(一)前言
  • MyBatis
  • CAD机械零件平面绘制练习六
  • 相比Vue和React,Svelte可能更适合你
  • HTTP/HTTPS/TCP原理
  • 基于TCP的socket API,让你拥有另一套自己的服务器~
  • 关于IO的探究:BIO、NIO、AIO(未完待续)
  • API接口名称(item_search - 按关键字搜索淘宝商品)[item_search,item_get,item_search_shop等]