在C++中,类(Class)是面向对象编程(OOP)的核心概念之一,它允许我们将数据(成员变量)和操作数据的方法(成员函数)封装在一起,形成自定义的数据类型。类的定义和声明是掌握C++面向对象编程的基础,本文将全面解析类的定义与声明相关知识。
类是一种用户自定义的数据类型,它可以包含数据成员(变量)和成员函数(方法)。类就像是一个蓝图,它定义了对象的属性和行为。例如,我们可以定义一个Person类来表示一个人,这个类可能包含姓名、年龄等数据成员,以及获取姓名、增加年龄等成员函数。
对象是类的实例。类定义了对象的通用结构和行为,而对象则是根据类的定义创建的具体实体。可以将类看作是模板,而对象是根据这个模板制造出来的产品。例如,根据Person类,我们可以创建出多个具体的Person对象,每个对象都有自己独特的姓名和年龄。
① 存储类型对比
类型 | 声明方式 | 生命周期 | 内存位置 |
|---|---|---|---|
普通成员 | int value; | 对象生命周期 | 对象内存块 |
静态成员 | static int s; | 程序生命周期 | 全局数据区 |
常量成员 | const int c; | 对象生命周期 | 对象内存块 |
引用成员 | int& ref; | 对象生命周期 | 对象内存块 |
class InitDemo {
public:
// 直接初始化(C++11)
int a = 10;
// 构造函数初始化列表
InitDemo(int b_val) : b(b_val) {}
private:
const int b;
int& c = a; // 引用成员必须初始化
};类的声明通常使用class关键字,其基本语法如下:
class ClassName {
// 成员声明
};这里的ClassName是类的名称,大括号内是类的成员声明部分。例如,声明一个简单的Point类:
class Point {
int x;
int y;
};Point类包含两个数据成员x和y,用于表示二维平面上的一个点的坐标。
类的声明只是告诉编译器有这样一个类存在,但并没有提供类的具体实现。而类的定义则需要提供类的成员函数的具体代码。例如,可以先声明一个Circle类:
class Circle {
double radius;
double getArea();
};这里只是声明了Circle类有一个数据成员radius和一个成员函数getArea,但getArea函数的具体实现还没有给出。
类的定义需要给出成员函数的具体实现。有两种方式可以实现类的成员函数:
① 在类内部定义
可以直接在类的大括号内给出成员函数的定义,例如:
class Rectangle {
int width;
int height;
public:
int getArea() {
return width * height;
}
};getArea函数的定义直接放在了Rectangle类的内部。
②在类外部定义
也可以在类外部定义成员函数,但需要使用作用域解析运算符::来指定函数所属的类。例如:
class Square {
int side;
public:
int getPerimeter();
};
int Square::getPerimeter() {
return 4 * side;
} getPerimeter函数的定义在类外部,通过Square::来表明它是Square类的成员函数。
C++ 使用访问修饰符来控制类的成员的访问权限,主要有三种访问修饰符:public、private、protected三个关键字实现,决定了类成员的可见性和访问权限。
访问修饰符 | 类内部 | 类外部 | 派生类 |
|---|---|---|---|
public | ✔️ | ✔️ | ✔️ |
private | ✔️ | ❌ | ❌ |
protected | ✔️ | ❌ | ✔️ |
① public
public成员可以被类的外部代码直接访问。例如:
class Student {
public:
string name;
void displayName() {
cout << "Name: " << name << endl;
}
};
int main() {
Student s;
s.name = "Alice";
s.displayName();
return 0;
}name和displayName都是public成员,所以可以在main函数中直接访问。
②private
private成员只能被类的成员函数访问,类的外部代码无法直接访问。例如:
class BankAccount {
private:
double balance;
public:
void deposit(double amount) {
balance += amount;
}
double getBalance() {
return balance;
}
};
int main() {
BankAccount account;
account.deposit(1000);
cout << "Balance: " << account.getBalance() << endl;
// account.balance = 2000; // 错误,不能直接访问private成员
return 0;
}这里balance是private成员,只能通过deposit和getBalance等成员函数来访问。
③ protected
protected成员类似于private成员,但可以被派生类的成员函数访问。这在继承关系中非常有用,后续会详细介绍。
④默认访问权限
private。struct默认的访问权限是public。构造函数是一种特殊的成员函数,用于在创建对象时初始化对象的成员。构造函数的名称与类名相同,并且没有返回类型。例如:
class Book {
string title;
int pages;
public:
Book(string t, int p) {
title = t;
pages = p;
}
void displayInfo() {
cout << "Title: " << title << ", Pages: " << pages << endl;
}
};
int main() {
Book myBook("C++ Primer", 976);
myBook.displayInfo();
return 0;
}Book类有一个构造函数,它接受两个参数title和pages,并将它们赋值给类的成员变量。
如果没有定义任何构造函数,编译器会自动生成一个默认构造函数,将所有成员变量初始化为默认值(如int初始化为0,指针初始化为nullptr)。
①带参数的构造函数
可以定义带参数的构造函数来初始化对象:
class Point {
public:
double x;
double y;
// 带参数的构造函数
Point(double x_val, double y_val) {
x = x_val;
y = y_val;
}
};
int main() {
Point p(3.0, 4.0); // 使用带参数的构造函数创建对象
return 0;
}②构造函数初始化列表
使用初始化列表可以更高效地初始化成员变量,尤其是const成员和引用成员:
class MathUtils {
public:
static int add(int a, int b) {
return a + b;
}
};
int main() {
int result = MathUtils::add(3, 5);
cout << "Result: " << result << endl;
return 0;
}析构函数也是一种特殊的成员函数,用于在对象销毁时执行一些清理工作,例如释放动态分配的内存。析构函数的名称是在类名前加上波浪号~,并且没有返回类型和参数。例如:
class ArrayWrapper {
int* arr;
int size;
public:
ArrayWrapper(int s) {
size = s;
arr = new int[size];
}
~ArrayWrapper() {
delete[] arr;
}
};
int main() {
{
ArrayWrapper wrapper(10);
// 对象在离开这个代码块时会自动调用析构函数
}
return 0;
} ArrayWrapper类的析构函数负责释放动态分配的数组内存。
友元函数是一种可以访问类的私有和保护成员的非成员函数。要将一个函数声明为类的友元函数,需要在类的定义中使用friend关键字。例如:
class Rectangle {
int width;
int height;
public:
Rectangle(int w, int h) : width(w), height(h) {}
friend int getArea(const Rectangle& rect);
};
int getArea(const Rectangle& rect) {
return rect.width * rect.height;
}
int main() {
Rectangle rect(5, 3);
cout << "Area: " << getArea(rect) << endl;
return 0;
}getArea函数是Rectangle类的友元函数,所以它可以直接访问Rectangle类的私有成员width和height。
除了友元函数,还可以将一个类声明为另一个类的友元类。友元类的所有成员函数都可以访问被友元类的私有和保护成员。例如:
class ClassA {
private:
int data;
public:
ClassA(int d) : data(d) {}
friend class ClassB;
};
class ClassB {
public:
void displayData(const ClassA& obj) {
cout << "Data in ClassA: " << obj.data << endl;
}
};
int main() {
ClassA a(10);
ClassB b;
b.displayData(a);
return 0;
}这里ClassB是ClassA的友元类,所以ClassB的成员函数displayData可以访问ClassA的私有成员data。
静态数据成员是类的所有对象共享的成员,它不属于任何一个具体的对象。静态数据成员需要在类内部声明,在类外部定义和初始化。例如:
class Counter {
public:
static int count;
Counter() {
count++;
}
};
int Counter::count = 0;
int main() {
Counter c1;
Counter c2;
cout << "Number of objects: " << Counter::count << endl;
return 0;
}count是Counter类的静态数据成员,每次创建Counter对象时,count的值就会加 1。
静态成员函数也是类的所有对象共享的函数,它只能访问静态数据成员,不能访问非静态成员。静态成员函数可以通过类名直接调用,不需要创建对象。例如:
class MathUtils {
public:
static int add(int a, int b) {
return a + b;
}
};
int main() {
int result = MathUtils::add(3, 5);
cout << "Result: " << result << endl;
return 0;
}这里add是MathUtils类的静态成员函数,可以直接通过类名调用。
继承是面向对象编程的重要特性,允许派生类继承基类的成员。
class DerivedClass : access_specifier BaseClass {
// 派生类新增成员
};access_specifier:继承方式,包括public(公有继承)、protected(保护继承)、private(私有继承)。class Shape {
public:
void setColor(const std::string& color) {
this->color = color;
}
std::string getColor() const {
return color;
}
private:
std::string color;
};
class Circle : public Shape {
public:
void setRadius(double radius) {
this->radius = radius;
}
double getRadius() const {
return radius;
}
private:
double radius;
};
int main() {
Circle c;
c.setColor("Red"); // 允许:公有继承可以访问基类的公有成员
std::cout << "Color: " << c.getColor() << std::endl; // 输出:Color: Red
return 0;
}继承方式 | 基类public成员 | 基类protected成员 | 基类private成员 |
|---|---|---|---|
public继承 | public | protected | 不可访问 |
protected继承 | protected | protected | 不可访问 |
private继承 | private | private | 不可访问 |
多态是面向对象编程的另一个重要特性,允许不同类的对象对同一消息作出不同的响应。
在基类中使用virtual关键字声明虚函数,派生类可以重写该函数。
class Shape {
public:
virtual void draw() const {
std::cout << "Drawing a shape" << std::endl;
}
};
class Circle : public Shape {
public:
void draw() const override {
std::cout << "Drawing a circle" << std::endl;
}
};
int main() {
Shape* shape = new Circle();
shape->draw(); // 输出:Drawing a circle
delete shape;
return 0;
}在基类中定义纯虚函数(= 0),使基类成为抽象类,不能实例化。
class Shape {
public:
virtual void draw() const = 0; // 纯虚函数
};
class Circle : public Shape {
public:
void draw() const override {
std::cout << "Drawing a circle" << std::endl;
}
};
int main() {
Shape* shape = new Circle();
shape->draw(); // 输出:Drawing a circle
delete shape;
return 0;
}原则 | 说明 | 示例 |
|---|---|---|
单一职责 | 一个类只做一件事 | 分离数据存储和格式转换 |
开闭原则 | 对扩展开放,对修改关闭 | 使用策略模式代替修改类 |
里氏替换 | 子类必须能替换基类 | 保持继承关系的语义一致性 |
接口隔离 | 多个专用接口优于单个通用接口 | 分解庞大的接口类 |
依赖倒置 | 依赖抽象而非具体实现 | 使用抽象基类定义接口 |
安全等级 | 说明 |
|---|---|
基本保证 | 对象保持有效状态,无资源泄漏 |
强保证 | 操作要么完全成功,要么保持原状态 |
不抛保证 | 承诺不抛出任何异常 |
使用gdb查看对象内存布局:
(gdb) p /x *(ClassType*)obj_ptr打印虚函数表:
(gdb) info vtbl obj_ptrValgrind检测内存泄漏:
valgrind --leak-check=full ./program类是 C++ 面向对象编程的核心,掌握类的定义和声明对于编写高质量的 C++ 代码至关重要。通过不断实践和学习,能够更深入地理解和运用类的各种特性,开发出更加复杂和强大的程序。
using声明在模板编程中有着重要应用,如定义模板类型别名等。