在C++中,类是一种用户自定义的数据类型,用于将数据和操作数据的函数封装在一起。类是面向对象编程的基础载体,通过类可以创建具有相同属性和行为的对象,从而实现代码的模块化与复用。定义一个类不仅需要掌握class关键字的使用方式,还需要理解访问权限、构造函数、析构函数以及实例化等核心概念。

C++类定义的基础结构
定义一个类需要使用class关键字,后面紧跟类名以及由花括号包围的类体,类体结束后必须以分号结尾。类体内部可以声明成员变量和成员函数,成员变量用于保存对象的状态数据,成员函数用于描述对象可以执行的操作。下面的代码定义了一个简单的Person类,其中包含姓名和年龄两个私有成员变量,以及设置和输出这些成员变量的公有函数。
在类的默认访问权限下,所有成员都是私有的。也就是说,如果没有显式使用访问修饰符,外部代码将无法直接访问类中的成员。为了让外部能够操作对象,通常需要在类体中通过public:标签暴露一组公有接口,同时把数据成员保留在private:区域中,这是C++中实现封装的基本手段。
#include <iostream>
#include <string>
class Person {
private:
std::string name;
int age;
public:
void setName(const std::string& n) {
name = n;
}
void setAge(int a) {
age = a;
}
void printInfo() const {
std::cout << "姓名:" << name << ",年龄:" << age << std::endl;
}
};
访问控制与封装原则
访问修饰符用于控制类成员对外部的可见性。private表示私有成员,只能在当前类的成员函数内部访问;public表示公有成员,外部代码可以直接访问;protected表示受保护成员,除了当前类,在派生类中同样可以访问,但外部代码仍然无法直接访问。
良好的封装要求将数据成员声明为private,只通过公有成员函数提供受控的访问方式。这样可以隐藏对象内部状态,避免外部代码直接修改数据导致不一致。访问权限可以多次出现,也可以根据需要组织不同的访问区域。下面的示例展示了三种访问级别在同一个类中的配合使用。
#include <iostream>
#include <string>
class Account {
private:
std::string owner;
double balance;
protected:
std::string getOwner() const {
return owner;
}
public:
void setOwner(const std::string& name) {
owner = name;
}
void deposit(double amount) {
if (amount > 0) {
balance += amount;
}
}
void showBalance() const {
std::cout << owner << " 的余额为:" << balance << std::endl;
}
};
构造函数与析构函数
构造函数是一种特殊的成员函数,函数名与类名完全相同,且没有返回值。它在对象创建时自动调用,负责为成员变量赋初值。构造函数支持重载,因此一个类可以同时拥有无参数的默认构造函数和带参数的构造函数。如果没有显式定义任何构造函数,编译器会自动生成一个默认构造函数,但这种自动生成的构造函数不会为基本类型成员提供有意义的初始值。
析构函数的名称是在类名前加上波浪号~,同样没有返回值。它在对象生命周期结束时自动调用,主要用来释放动态申请的资源、关闭文件或输出调试信息。一个类只能有一个析构函数,析构函数不能重载,也不能带参数。下面的例子演示了构造函数与析构函数的调用过程。
#include <iostream>
#include <string>
class Person {
private:
std::string name;
int age;
public:
// 默认构造函数
Person() {
name = "未知";
age = 0;
std::cout << "默认构造函数被调用" << std::endl;
}
// 带参数的构造函数
Person(const std::string& n, int a) {
name = n;
age = a;
std::cout << "带参数构造函数被调用" << std::endl;
}
// 析构函数
~Person() {
std::cout << "析构函数被调用,对象 " << name << " 被销毁" << std::endl;
}
void printInfo() const {
std::cout << "姓名:" << name << ",年龄:" << age << std::endl;
}
};
int main() {
Person p1;
p1.printInfo();
Person p2("张三", 20);
p2.printInfo();
return 0;
}
类的实例化与使用
定义好类之后,就可以像使用内置类型一样创建对象,这个过程称为实例化。创建对象时,编译器会根据括号中的参数选择合适的构造函数。如果没有任何参数,则调用默认构造函数;如果提供了初始值,则调用匹配的带参数构造函数。
对象创建完成后,可以通过点运算符.调用公有成员函数,也可以通过指针或引用访问对象。对于栈上创建的对象,生命周期结束时析构函数会自动调用;如果使用new在堆上创建对象,则需要使用delete手动释放,否则会造成内存泄漏。
#include <iostream>
#include <string>
class Person {
private:
std::string name;
int age;
public:
Person(const std::string& n, int a) {
name = n;
age = a;
}
void setName(const std::string& n) {
name = n;
}
void setAge(int a) {
age = a;
}
void printInfo() const {
std::cout << "姓名:" << name << ",年龄:" << age << std::endl;
}
};
int main() {
Person p1("李四", 18);
p1.printInfo();
p1.setName("王五");
p1.setAge(25);
p1.printInfo();
Person* p2 = new Person("赵六", 30);
p2->printInfo();
delete p2;
return 0;
}
类定义中的常见注意事项
类名的命名通常采用大驼峰方式,例如StudentRecord、FileManager。成员变量为了避免与成员函数参数或局部变量混淆,可以在名称前加上m_前缀,如m_name、m_age。这样在成员函数内部能够清楚地看出哪些变量属于当前对象。
如果类中包含指向动态内存的指针成员,仅使用编译器自动生成的拷贝构造函数和赋值运算符会导致浅拷贝问题。两个对象会指向同一块内存,析构时可能重复释放。因此,含有指针成员的类通常需要自定义拷贝构造函数、赋值运算符和析构函数,即“三法则”所描述的资源管理要求。
成员函数的定义既可以直接放在类体内部,也可以放在类体外部。类外定义使用作用域运算符::指明函数属于哪个类,此时类体内部只需要提供函数声明。类外定义可以让类声明更加简洁,也便于将接口与实现分离。
#include <iostream>
#include <string>
class Person {
private:
std::string m_name;
int m_age;
public:
Person(const std::string& n, int a);
void printInfo() const;
};
Person::Person(const std::string& n, int a) {
m_name = n;
m_age = a;
}
void Person::printInfo() const {
std::cout << "姓名:" << m_name << ",年龄:" << m_age << std::endl;
}
总结与要点回顾
在C++中定义一个类,核心是使用class关键字构造类体,并合理安排成员变量和成员函数。访问修饰符决定了成员的可见范围,构造函数与析构函数保证了对象初始化和资源释放的时机,实例化过程则通过选择合适的构造函数创建对象并调用公有接口完成操作。
理解类定义的基本规范后,还应关注命名一致性、类外定义以及动态资源的拷贝控制问题。掌握这些内容,能够帮助开发者编写出结构清晰、安全性更好的面向对象程序。在实际项目中,类设计通常与继承、多态等特性结合使用,但无论多么复杂的类体系,其定义基础仍然离不开本文所介绍的这些基本规则。