C++类和对象上

本文介绍了C++中的类和对象概念,包括面向对象编程的基本原理、类的定义与访问限定符(公有、私有、受保护)、封装以及this指针的使用。文章强调了面向对象编程的灵活性、易维护性和易扩展性,并通过实例展示了如何在C++中创建和使用类。
摘要由CSDN通过智能技术生成

专栏:C/C++
个人主页:HaiFan.
专栏简介:本章为大家带来C++类和对象相关内容。

前言

面向对象跟函数一样,是比较重要的内容。

那什么是对象呢?

现实生活中的对象:足球,人,手机,等一切物品都可以看作对象

程序中的对象:现实生活中具体的事物

那么把现实中的事物,转换成电脑程序的形式,所以在这里就提到了面向对象的概念

那么面向对象的好处是什么呢?

  1. 灵活性更高,如果代码出现问题,只需要更改出现问题的部分即可
  2. 易维护
  3. 易扩展

面向对象会涉及到:

  1. 对象
  2. 属性
  3. 方法
  4. 等等

对象:张三的手机,王五的手机。

手机就是具体的事物,而手机是对象的集合,那么就可以从对象中提取共同的特征,作为一个类别。

在这里的一个类别是手机类。

那么,能用手机干什么呢?

打电话,接电话,打游戏,刷视频等等,这些都是能用手机干的事。先称之为动作吧

手机还有品牌,颜色,大小,价格之分,这些都是通过特征去展示的。

可以通过具体的事物,来推出手机所具有共同特征和动作。

还可以分为人类。

动作:走,跑等等

特征:性别,年龄,身高,婚否等等

但是,在真正开发的时候,不会把所有的动作都列出来,而是根据需求,需要什么做什么就行了。

在程序中,把特征称为属性,把动作称为方法。

面向过程和面向对象

面向过程和面向对象是两种不同的编程范式。

面向过程编程是一种基于问题解决的编程思想,把问题看做过程或方法的集合,通过把一个大问题分解为一系列子问题的方法来解决它,刻意将问题和数据分开,重点关注数据的操作。面向过程编程中,数据和操作数据的方法是分离的,数据是被单独处理的。

而面向对象编程则是一种基于对象的编程思想,将问题看做对象之间的协作,表示成一个对象的集合,重点关注对象之间的交互和通信,以及对象的属性和方法。面向对象编程中,数据和操作数据的方法是相互关联的,它们被组合成了一个对象。

在面向对象编程中,数据和操作数据的方法总是一起的,它们封装在类的定义中,以提供更好的抽象和封装性。面向对象编程中,重点是把问题抽象成一个模型,并通过完善的类和对象来实现这个模型。

C语言就是面向过程的,关注的是过程的分析。

C++是面向对象的语言,关注的是对象。

类的引入

在C语言中,结构体是一种自定义的数据类型,可以包含不同类型的数据成员,这些数据成员将按照声明的次序在内存中依次存储。结构体可以帮助开发者组织复杂的数据结构,例如,一个由不同数据类型组成的实体对象。以下是一个简单的结构体声明:

struct student {
     int id;
     char name[32];
     float score;
};

在C++中,结构体也是自定义的数据类型,但是具有与类相同的功能。结构体可以包括数据成员、函数成员、继承等功能。在C++中,可以像使用类一样使用结构体,如下所示:

struct Stack
{
	void Init(int capacity)
	{
		return;
	}

	void Push(int data)
	{
		return;
	}

	int capacity;
	int stk[100];
	int _size;

};

类的定义

在C++中,可以通过定义类来创建新的数据类型。类定义了一个对象的特性、行为和方法。类可以包括数据成员、成员函数、构造函数和析构函数等。

那么类是如何定义的呢?

class className
{
	....
};

class关键字用于声明一个新的类。className是类的名称。 {}中 可以声明类的数据成员和成员函数。

{}中的内容称为类的成员,类中变量称为类的属性或成员变量,类中函数称为类的方法或者成员函数

类的两种定义方式

  1. 头文件中定义成员函数,实现文件中实现函数。

在这里插入图片描述

  1. 声明和定义全部放在类体中,需注意:成员函数如果在类中定义,编译器可能会将其当成内联函数处理

在这里插入图片描述

对于类中成员的命名建议

  1. 命名规则:采用驼峰命名法,即成员变量和成员函数的名称首字母小写,每个新单词的首字母大写,而类名和枚举类型的名称首字母大写。

  2. 成员变量命名:在命名成员变量时,建议使用名词来描述它所代表的数据类型,例如使用“m_”前缀来表示成员变量。

  3. 成员函数命名:在命名成员函数时,使用动词来描述它所执行的操作,例如使用“set”前缀来表示设定函数,“get”前缀来表示获取函数等。

  4. 避免使用简单的单词作为成员变量名,例如“i”、“j”等,因为这些名称可能具有多种含义,不易于理解和维护。同样的,避免使用变量名和关键字相同的名称,例如“int”、“float”,否则编译器可能无法识别变量名。

#include <iostream>
#include <cstring>

using namespace std;

class Dog
{
public:
	Dog(const char name[],const char sex[],int age)
	{
		m_name = new char[strlen(name) + 1];
		m_sex = new char[strlen(name) + 1];
		strcpy(m_name, name);
		strcpy(m_sex, sex);
		m_age = age;
	}

	void set_age()
	{
		int age;
		cin >> age;
		m_age = age;
	}

	int get_age()
	{
		return m_age;
	}

	//......

private:
	char* m_name = nullptr;
	char* m_sex = nullptr;
	int m_age = 0;
};

int main()
{
	Dog s("二狗", "公", 2);
	cout << s.get_age() << endl;
	return 0;
}

类的访问限定符及封装

访问限定符

类和对象中有三种限定访问符,分别是公有(public)、私有(private)和受保护的(protected)。它们用于控制类的成员变量和成员函数的访问范围。

  • 公有限定符(public)可以使得类的外部和派生类的成员函数能够直接访问到这些成员,被认为是类的“公共接口”。
  • 私有限定符(private)将类的数据成员和函数封装起来,只有类的成员函数及友元函数能够访问。私有访问符用于实现信息隐藏,确保程序的稳定性和安全性。
  • 受保护的限定符(protected)兼具私有和公有两者的特性,在子类中继承时可以被访问但不能被其他外部类访问,它适合在继承树中作为基类使用,被认为是类的“保护接口”。

默认情况下,如果不指定成员的访问修饰符,默认为 private 。

那么,如何使用呢?

  • public在一个类中,通常将一些对外公开的接口函数和数据成员放在public区域。
  • private区域中一般放置与类定义密切相关,但不希望公开给外界的函数和数据。
  • protected修饰符与private修饰符相似,被声明为protected成员的变量和方法仅限于派生类和其本身内的类成员之间的使用,但是不能被其他类所访问。
#include <iostream>

using namespace std;

class Person
{
public:
	int m_age;  // 声明了一个 public 权限的成员变量
	void eat(); // 声明了一个 public 权限的成员函数

protected:
	char* m_name;  // 声明了一个 protected 权限的成员变量
	void sleep();  // 声明了一个 protected 权限的成员函数

private:
	double m_weight; // 声明了一个 private 权限的成员变量
	void work();     // 声明了一个 private 权限的成员函数
};

m_age和eat函数是公共成员,可以在类外直接被访问

	Person person;
	person.m_age = 19;
	person.eat();

m_name和sleep函数则是保护成员,在类的派生类可以使用,但不能在其他地方进行访问

class Student: public Person {
    void study() {
        m_name = "Bob";  // 可以在派生类中使用
        sleep();         // 可以在派生类中使用
    }
};

Student student;
student.m_name = "Tom";  // 编译错误,无法访问受保护的成员
student.sleep();         // 编译错误,无法访问受保护的成

m_weight 和 work() 是私有成员,只有类本身的成员函数才能访问,外部无法访问到它们。

Person person;
person.m_weight = 60.0;  // 编译错误,无法访问私有成员
person.work();           // 编译错误,无法访问私有成员

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

封装

封装是指将数据和对这些数据的操作封装在一起,形成一个类。封装机制可以隐藏类内部的具体实现细节,只向外部暴露必要的接口,从而保证程序的稳定性和安全性。使用者无需关心类内部具体实现,只需要调用公共接口即可。

以下是一个简单的类和对象实现的栈封装

stack.h文件

#pragma once


#include <iostream>

using namespace std;

typedef int StkDataType;

class Stack
{
private:
	StkDataType* stk;
	int capacity;
	int top;

public:

	void Init(int size = 4);

	void push(StkDataType x);

	bool empty();

	int size();

	void pop();

	void destory();
};

stack.cpp文件

#define _CRT_SECURE_NO_WARNINGS 1

#include "stack.h"

void Stack::Init(int size)
{
	stk = new StkDataType[size];
	top = -1;
	capacity = size;
}

void Stack::push(StkDataType x)
{
	stk[++top] = x;
}

bool Stack::empty()
{
	return capacity == top + 1;
}

int Stack::size()
{
	return top + 1;
}

void Stack::pop()
{
	--top;
}

void Stack::destory()
{
	delete stk;
	capacity = 0;
	top = -1;
}

main.cpp文件

#define _CRT_SECURE_NO_WARNINGS 1


#include "stack.h"

int main()
{
	Stack s;
	s.Init();
	s.push(1);
	s.empty();
	s.size();
	s.pop();
	s.destory();
}

上面的代码,stack类私有成员变量为栈空间stk数组,栈顶元素top和栈容量capacity,公用成员函数包括empty,push等等。

将类定义保存再了头文件stack.h中,将类成员函数的实现写在了stack.cpp中,然后在main.cpp中只需要把栈类的头文件引入一下,在定义一个Stack s对象,就可以使用了。

注:只需要包含头文件即可,不用包含栈类的实现部分,因为在编译的时候需要把stack.cpp文件一起编译成目标文件来生成可执行文件。

类的作用域

类的作用域是指类定义的可见范围。一个类的作用域可以分为两个部分声明和定义。

类的作用域规则与其他作用域规则相同(都是通过 className::成员函数 ),在声明该类的作用域内,类名和成员函数,成员变量的的名称都是可见的,而在定义该类的作用域内,所有成员变量和成员函数都是可见的。

例如,在定义一个类时如果需要引用另一个类,则需要在类定义之前进行声明。另外,在不同的源文件中使用同一个类时,需要包含该类的头文件,以便编译器能够找到该类的定义。

比如上面封装内容里的栈类成员函数的实现文件中,是通过 ::来访问的,这样可以说明这个函数是这个类里的成员函数。

类的实例化

创建对象就是类的实例化

类是一个集合,比如手机类,手机有品牌,大小,颜色,型号之分,把这个公共的特征进行提取,可以封装为一个类。对象可以被看作是集合中的一个元素,是类的一个实例。通过手机类可以创建出多个不同的手机,这些创建出来的手机就是对象。

class Phone
{
private:
	char* m_brand = nullptr;
	double m_price = 0;

public:
	void Init(const char* brand = nullptr, double price = 0)
	{
		m_brand = new char[(strlen(brand) + 1)];
		strcpy(m_brand, brand);
		m_price = price;
	}
};

int main()
{
	Phone a;
	a.Init("C++", 999.99);

	return 0;
}

可以把类理解为一个模板或者蓝图,实例化就是根据这个模板或者蓝图造成了个东西。

比如上面的代码,Phone类当成一个模板,根据这个模板创建出了a对象,a对象能进行初始化,这个初始化是模板中存在的功能,对象创建成功后,也可以使用。

如何计算类对象的大小

C语言中结构体的大小是根据内存对齐规则来计算的,以空间换取时间,而C++兼容C,那么C++的类对象的大小是如何计算的呢?

class MyClass {
public:
    int num1;
    float num2;
    char ch;
    double num3;
};

int main() {
    cout << "Size of MyClass: " << sizeof(MyClass) << endl;
    return 0;
}

------>输出:24

这个代码计算出结构体大小并不算难,只是基本类型的计算和内存对齐。

如果在类中添加上一些成员函数呢?成员函数的大小怎么去计算呢?

class MyClass 
{
public:
    int num1;
    float num2;
    char ch;
    double num3;

    void SetNum()
    {
        num1 = 1;
        num2 = 1.1;
    }
};

依旧------>输出:24

这是因为成员函数不会像成员变量一样被存储在类对象中,相反,它们被存储在代码段中的特定位置,可被所有该类的对象所共享。当执行类的成员函数时,对象只会通过一个指针来调用该函数,这个指针指向代码段中存储该函数的位置。

this指针

this指针是一个关键字,它是一个指向当前对象(调用该成员函数的对象)的指针。当类的成员函数被调用时,系统会将调用函数的对象的地址作为参数传给成员函数,并将该地址存储在this指针中。

class Person
{
public:
	string name;
	int age;

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

int main()
{
	Person a, b;
	cout << &a << "<=====>";
	a.Print();
	return 0;
}

输出结果:00DBF9F8<=====>00DBF9F8

this就是对象a的地址,通过这个地址去访问Print。


class Person
{
public:
	string name;
	int age;

	void Print()
	{
		cout << "name:" << this->name << endl;
		cout << "age:" << this->age << endl;
	}
};

int main()
{
	Person a, b;
	a.age = 18;
	a.name = "小明";
	a.Print();
	return 0;
}

输出结果:name:小明 age:18

Print成员函数使用this指针访问了a的name和age成员。


注:this 指针是一个常指针,不能被赋值。而且,this 指针只有在非静态成员函数中才有意义,在静态成员函数中不能使用 this 指针。编译器会自动处理成员函数隐含的this指针,不需要用户自己传递。


this指针存在哪里?

this指针指向当前对象的指针,存在于成员函数的局部变量中,其类型与类的类型相同

this指针可以为空吗?

答案是可以为空,但使用的时候要小心,否则会造成程序崩溃。所以必须先确保当前对象是有效的。否则在访问对象的成员时会发生不可预知的错误,比如读取或写入未定义区域的值,导致程序崩溃。

比如下面的代码

class Data
{
public:
	void print()
	{
		cout << 1 << endl;
	}
};

int main()
{
	Data* p = nullptr;
	p->print();
	return 0;
}

这个代码能够正常运行。虽然p是null但是在成员函数print中,并没有任何对this进行的操作。

class Data
{
private:
	int _a;
public:
	void print()
	{
		cout << _a << endl;
	}
};

int main()
{
	Data* p = nullptr;
	p->print();
	return 0;
}

但是这个程序就会造成程序崩溃。

因为在调用 p 指针所指向的对象的 Print() 函数时,由于 p 是空指针,它不指向任何有效的对象,因此会产生未定义的行为,导致程序崩溃。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值