类的定义

C++用类来描述对象,类是对现实世界中相似事物的抽象,比如同是“双轮车”的摩托车和自行车,有共同点,也有许多不同点。“车”类是对摩托车、自行车、汽车等相同点的提取与抽象。

类的定义分为两个部分:

  1. 数据,相当于现实世界中的属性,称为数据成员;

  2. 对数据的操作,相当于现实世界中的行为,称为成员函数

有些地方,会将类的数据成员和成员函数统称为类的成员

从程序设计的观点来说,类就是数据类型,是用户定义的数据类型,对象可以看成某个类的实例(某类的变量)。所以说类是对象的抽象,对象是类的实例。

由对象抽象出类

由类实例化出对象

C++中用关键字class来定义一个类,其基本形式如下:类的定义和声明

class MyClass{//类的定义
    //……
    void myFunc(){}  //成员函数
    int _a;          //数据成员
};//一定要有分号


//类也可以先声明,后完成定义
class MyClass2;//类的声明

class MyClass2{//类的定义
    //……
};//分号不能省略

访问修饰符

如下,我们定义好一个Computer的类,假设我们站在代工厂的视角,这个Computer类拥有两个属性——品牌与价格;两个行为——设置品牌与设置价格

class Computer {
	void setBrand(const char * brand)
	{
		strcpy(_brand, brand);
	}
    
	void setPrice(float price)
	{
		_price = price;
	}
    
	char _brand[20];
	float _price;
};

按之前的理解,现在我们自定义了一个新的类——Computer类,我们需要实例化出一个对象(特定的Computer),再通过这个对象来访问数据成员或调用成员函数,如下:

Computer pc;
pc.setPrice(10000); //error
pc._price; //error

结果发现都会报错,这是什么原因呢?事实上,class中的所有的成员都拥有自己的访问权限,分别可以用以下的三个访问修饰符进行修饰

public: //公有的访问权限,在类外可以通过对象直接访问公有成员

protected://保护的访问权限,派生类中可以访问,在类外不能通过对象直接访问(后面学)

private://私有的访问权限,在本类之外不能访问,比较敏感的数据设为private

注意:

  • 类定义中访问修饰符的管理范围从当前行到下一个访问修饰符或类定义结束;

  • class定义中如果在成员定义(或声明)之前没有任何访问修饰符,其默认的访问权限为私有

class Computer {
public:
	void setBrand(const char * brand)
	{
		strcpy(_brand, brand);
	}
	void setPrice(float price)
	{
		_price = price;
	}
private:
	char _brand[20];
	float _price;
};
    
Computer pc;
pc.setPrice(10000); //ok
pc._price; //error,因为_price是私有的

image-20240307102743014

struct与class的对比

学习了类的定义后,我们会发现它与C语言中的struct很相似。

  • C语言中的struct

回顾一下C语言中struct的写法

struct Student{
    int number;
    char name[25];
    int score;
};

void test0(){
    struct Student s1;
    struct Student s2;
}

采用typedef取别名后更像C++的class

typedef struct{
    int number;
    char name[25];
    int score;
} Student;

void test0(){
    Student s1;
    Student s2;
}

C中的struct只能是一些变量的集合体,可以封装数据但不能隐藏数据,而且成员不能是函数,要使用函数只能使用函数指针的方式。访问权限限制、继承性、构造析构都没有。事实上,C中struct的这种封装属于广义上的封装。面向对象的封装是指隐藏对象的属性和实现细节,仅对外公开接口,控制在程序中属性的读和修改的访问级别;将抽象得到的数据和操作数据的方法相结合,形成“类”

image-20240307110041114

  • C++中的struct

C++中的struct对C中的struct做了拓展(可以在结构体中定义函数或者声明函数),基本等同于class,默认访问权限是public.

#include <string.h>
#include <iostream>
using std::cout;
using std::endl;

struct Computer{
	//成员函数
	void setBrand(const char * brand)//设置品牌
    {
        strcpy(_brand, brand); 
    }
	void setPrice(float price)//设置价格
    {
        _price = price;
    }
    void print()//打印信息
    {
        cout << "hello" << endl;
        cout << "brand: " << _brand <<endl;
        cout << "price: " << _price << endl;
    }
	//数据成员
	char _brand[20];
	float _price;
};
void test0()
{
    Computer pc;
    pc.setBrand("xiaomi");
    pc.setPrice(5699);
    pc.print();
}
int main()
{
    test0();
    return 0;
}

  • C++中的class

class默认访问权限是private.

成员函数的定义

  • 成员函数定义的形式

成员函数可以在类内部完成定义,也可以在类内部只进行声明,在类外部完成定义。

class Computer {
public:
	//成员函数
	void setBrand(const char * brand);//设置品牌

	void setPrice(float price);//设置价格
        
    void print();//打印信息
private:
	//数据成员
	char _brand[20];
	float _price;
};

void Computer::setBrand(const char * brand)
{ 
    strcpy(_brand, brand); 
}
void Computer::setPrice(float price)
{ 
    _price = price;
}

实际开发中为什么采用成员函数声明和实现分离的写法?

当类中成员函数比较多(复杂),不容易看,如果只在类中进行成员函数的声明(同时配上注释),会方便理解。

image-20240307111238986

  • 多文件联合编译时可能出现的错误

为什么一般不在头文件中定义函数?

在头文件中定义一个函数时,如果多个源文件都包含了该头文件,那么在联合编译时会出现重定义错误。因为头文件的内容在每个源文件中都会被复制一份,而每个源文件都会生成对应的目标文件。在链接的阶段,会出现多个相同函数定义的情况,导致重定义错误。

编译过程
  1. 预处理:在编译之前,预处理器会处理所有的#include指令,将指定的头文件内容直接插入到源文件中相应的位置。这意味着如果一个头文件被多个源文件包含,那么该头文件中的所有内容都会被复制到每个源文件中。

  2. 编译:接下来,编译器会对每个单独的源文件(.cpp)进行编译,生成目标文件(.obj 或 .o)。在这个过程中,编译器只会检查语法正确性和类型一致性等,而不会考虑其他源文件中的内容。因此,即使同一个函数在不同的源文件中有重复定义,编译器也不会报错,因为每个源文件是独立编译的。

  3. 链接:最后一步是链接,这是将所有目标文件组合成一个可执行文件的过程。在这个阶段,链接器会尝试解决跨文件的符号引用问题,比如某个函数或变量在一处声明而在另一处定义。如果链接器发现同一符号有多个定义(例如,由于多个源文件包含了相同的函数定义),它就会报告重定义错误。

重定义错误的原因

当您在一个头文件中定义了一个非内联、非模板的函数,并且该头文件被多个源文件包含时,每次包含该头文件的源文件都会得到这个函数的一份副本。在链接阶段,链接器会看到这些函数定义都是全局作用域下的,而且它们具有相同的名称和签名,于是认为它们是重复定义的,从而引发重定义错误。

对于成员函数,也存在这样的问题。

如果在头文件中采用成员函数声明和定义分离的形式,在类外部完成成员函数的实现,就会陷入这个错误。

解决方法1:在成员函数的定义前加上inline关键字,inline函数定义在头文件中是ok的

image-20240307113202693

解决方法2:将成员函数放到类内部进行定义(说明类内部定义的成员函数就是inline函数——默认效果)

image-20240307113425919

解决方法3:函数声明放在头文件,函数定义(要加作用域限定)放在实现文件中,就算有多个测试文件,也不会出现重定义(最常用的方式)。

之后遇到这种需求(定义一个非常复杂的类,多处都需要用到这个类)

Logo

一起探索未来云端世界的核心,云原生技术专区带您领略创新、高效和可扩展的云计算解决方案,引领您在数字化时代的成功之路。

更多推荐