Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

类与对象

C++ 是一种支持面向对象编程(Object-Oriented Programming, OOP)的语言。 在面向对象的设计思想中,类(Class)是 C++ 的核心特性之一,常被称为用户自定义的数据类型(user-defined type)。

类用于定义对象的结构与行为,是一种将数据与操作这些数据的函数封装在一起的抽象描述。 在类中,用于存储数据的部分称为成员变量(Member Variables),而用于操作这些数据的函数称为成员函数(Member Functions)。

类本质上是一种模板(Template)或蓝图(Blueprint),通过它可以创建出多个具有相同属性和行为的具体实例,这些实例被称为对象(Objects)。每个对象都拥有属于自己的成员变量副本,并可以通过成员函数来执行特定的操作。

一、类的结构

在 C++ 中,类(Class)是一种由用户定义的数据类型(User-defined Data Type), 它将数据(成员变量)操作数据的函数(成员函数)有机地结合在一起, 从而实现封装(Encapsulation)抽象(Abstraction)

一个类的基本结构如下:

class ClassName {
private:
    // 私有成员(数据与函数)
protected:
    // 受保护成员
public:
    // 公有成员
};

1. 类的基本组成

类由以下几个主要部分构成:

组成部分说明
类名(Class Name)类的标识符,用于定义和引用该类。
成员变量(Member Variables)用于存储对象状态的数据。
成员函数(Member Functions)用于操作数据或定义对象行为的函数。
访问控制符(Access Specifiers)控制外部对类成员的访问权限:privateprotectedpublic
构造函数与析构函数(Constructor & Destructor)对象创建与销毁时自动调用的特殊成员函数。
静态成员(Static Members)属于类本身而非某个对象的成员。
友元(Friend)特殊访问权限,允许外部函数或类访问私有成员。

2. 访问控制符(Access Specifiers)

访问控制符用于限定类成员的可见性和访问范围:

控制符访问范围典型用途
private仅类内可访问封装内部实现细节
protected类内和子类可访问继承时保留部分访问权限
public任何地方都可访问提供对外接口

示例:

class Example {
private:
    int secret;          // 私有成员
protected:
    int semi_secret;     // 受保护成员
public:
    int visible;         // 公有成员
};

默认情况下,class 的成员默认是 private, 而 struct 的成员默认是 public

3. 成员变量(Member Variables)

成员变量用于存储对象的状态。 每个对象都会拥有独立的一份成员变量副本。

class Student {
private:
    std::string name;
    int age;
};

成员变量可以是基本类型、对象、指针、引用、数组、容器等。

也可以为成员变量提供默认初始值(C++11 起支持):

class Point {
    int x = 0;
    int y = 0;
};

4. 成员函数(Member Functions)

成员函数用于定义对象的行为,通常用于访问和修改成员变量。

class Student {
private:
    std::string name;
    int age;

public:
    void setInfo(const std::string& n, int a) {
        name = n;
        age = a;
    }

    void display() {
        std::cout << "Name: " << name << ", Age: " << age << std::endl;
    }
};

也可以在类外定义成员函数:

void Student::display() {
    std::cout << "Name: " << name << ", Age: " << age << std::endl;
}

5. 构造函数(Constructor)

构造函数在对象创建时自动执行,用于初始化成员变量。 它与类同名,无返回值。

class Student {
private:
    std::string name;
    int age;

public:
    // 构造函数
    Student(const std::string& n, int a) : name(n), age(a) {
        std::cout << "Constructor called." << std::endl;
    }
};

构造函数的种类包括:

类型说明示例
默认构造函数无参版本Student()
有参构造函数初始化时传参Student("张三", 20)
拷贝构造函数用已有对象创建新对象Student(const Student& s)

构造函数支持多态,可以使用多个参数不同的构造函数,在使用时会自动匹配。

6. 析构函数(Destructor)

析构函数在对象销毁时自动调用,用于资源释放或清理。

class Student {
public:
    ~Student() {
        std::cout << "Destructor called." << std::endl;
    }
};

析构函数名以 ~ 开头,无参、无返回值,每个类最多有一个析构函数。

7. this 指针

this 是一个隐含指针,指向调用成员函数的当前对象。 它可用于区分成员变量与同名参数:

class Student {
private:
    std::string name;

public:
    Student(const std::string& name) {
        this->name = name; // 区分成员变量与参数
    }
};

8. 静态成员(Static Members)

静态成员属于类本身,而非具体对象。 所有对象共享同一份静态数据。

静态成员分为两类:

  1. 静态成员变量(Static Member Variables)
  2. 静态成员函数(Static Member Functions)

(1)静态成员变量

静态成员变量在所有对象之间共享同一份存储空间。 它不依赖于任何对象存在,无论创建多少个对象,这个变量都只有一份。

因此,静态成员变量常用于表示类级别的公共信息,例如计数器、配置、全局状态等。

class Counter {
public:
    static int count;
    Counter() { count++; }
};

int Counter::count = 0;

(2)静态函数变量

静态成员函数同样属于类本身,而不是对象。 它的主要特征是:

  • 不依赖任何对象实例
  • 无法访问非静态成员变量或函数(因为没有具体对象可供操作)
  • 通常用于类级别的操作,如访问静态数据或执行与对象无关的逻辑。
  • 没有 this 指针,因为它不属于任何对象
class Counter {
private:
    static int count;

public:
    Counter() { count++; }

    // 静态成员函数
    static void showCount() {
        std::cout << "Current count: " << count << std::endl;
    }
};

// 类外定义静态变量
int Counter::count = 0;

int main() {
    Counter a, b;
    Counter::showCount();  // 通过类名访问
    a.showCount();         // 也可通过对象访问
}

9. 常成员(const Members)

  • 常成员函数:在函数后加 const,表示不修改成员变量。
  • 常对象:对象一旦定义,其状态不可更改。
class Point {
private:
    int x, y;

public:
    void display() const {
        std::cout << "(" << x << ", " << y << ")" << std::endl;
    }
};

10. 友元(Friend)

友元函数或友元类可以访问类的私有成员。 这在操作符重载、类间紧密协作时非常有用。

#include <iostream>

class Box {
private:
    int width;
public:
    void set_width(int val){
        width = val;
    }
    friend void printWidth(Box b);
};

void printWidth(Box b) {
    std::cout << "Width: " << b.width << std::endl;
}

int main(){
    Box a;
    a.set_width(20);
    printWidth(a);
}

友元会破坏封装性,应谨慎使用。

友元函数不是成员函数,不能用 对象.函数() 方式调用。它只是可以访问类私有成员的普通函数,调用方式与普通函数相同。

11. 类的组合与嵌套

一个类可以将另一个类作为成员,这种关系称为组合(Composition)。

class Address {
public:
    std::string city;
};

class Person {
public:
    std::string name;
    Address addr;  // 组合
};

当对象销毁时,其组合成员也会自动销毁。

二、对象

在 C++ 中,对象(Object)是类的实例(Instance)。 当我们定义了一个类后,这个类本身只是一个抽象的模板或蓝图, 而对象才是真正占用内存、可操作的具体实体。

类定义了“事物的共性”,对象体现了“事物的个性”。

1. 对象的创建与定义

定义一个类对象与定义普通变量非常相似:

class Student {
public:
    std::string name;
    int age;
};

int main() {
    Student s1;  // 创建对象 s1
    s1.name = "张三";
    s1.age = 20;

    Student s2;  // 再创建一个对象 s2
    s2.name = "李四";
    s2.age = 21;
}

每个对象都有独立的成员变量副本,互不影响。

例如:

s1.age = 20;
s2.age = 21;
// 修改 s1 的 age 不会影响 s2

2. 对象的初始化

当类中定义了构造函数时,对象可以在定义时直接初始化:

class Point {
private:
    int x, y;

public:
    Point(int a, int b) {  // 构造函数
        x = a;
        y = b;
    }

    void show() {
        std::cout << "(" << x << ", " << y << ")" << std::endl;
    }
};

int main() {
    Point p1(1, 2);  // 调用构造函数
    Point p2 = Point(3, 4); // 另一种写法
    p1.show();
    p2.show();
}

注意:对象创建时,构造函数会被自动调用;对象销毁时,析构函数会被自动调用。

3. 对象的作用域与生命周期

对象的生命周期与其定义的位置有关。

定义位置生命周期说明
局部对象(栈上)作用域结束时自动销毁
全局对象程序结束时销毁
静态对象程序结束时销毁
动态对象(堆上)需要手动释放(使用 new / delete

示例:

class Example {
public:
    Example() { std::cout << "Constructed\n"; }
    ~Example() { std::cout << "Destructed\n"; }
};

int main() {
    Example local;            // 局部对象
    static Example global;    // 静态对象
    Example* ptr = new Example();  // 动态对象

    delete ptr; // 手动释放
}

输出顺序体现了不同对象的生命周期管理。

4. 对象数组

可以定义一个包含多个对象的数组:

class Point {
public:
    int x, y;
    Point(int a = 0, int b = 0) : x(a), y(b) {}
};

int main() {
    Point arr[3] = { {1,2}, {3,4}, {5,6} };
    for (auto& p : arr)
        std::cout << "(" << p.x << ", " << p.y << ")\n";
}

若类中没有默认构造函数,则必须在定义对象数组时显式提供初始化参数。

5.对象的指针与引用

(1)对象指针

和基本类型类似,可以使用指针指向对象。

Student s1;
Student* ptr = &s1;
ptr->name = "王五";
ptr->age = 22;

使用 -> 运算符访问对象的成员。

也可以使用 new 创建动态对象:

Student* stu = new Student;
stu->name = "赵六";
stu->age = 18;
delete stu; // 释放内存

(2)对象引用

引用可以直接操作已有对象:

Student s1;
Student& ref = s1;
ref.name = "张三";

引用不会创建新对象,只是为已有对象取别名。

6. 对象的拷贝与赋值

当我们用一个对象初始化另一个对象时,会自动调用拷贝构造函数

class Box {
public:
    int width;
    Box(int w) : width(w) {}
    Box(const Box& b) { // 拷贝构造函数
        width = b.width;
        std::cout << "Copy constructor called\n";
    }
};

int main() {
    Box b1(10);
    Box b2 = b1; // 调用拷贝构造函数
}

赋值操作调用的是赋值运算符(operator=),而不是拷贝构造。

7. const 对象

可以将对象声明为常量,使其内容不可被修改:

class Point {
public:
    int x, y;
    void show() const {
        std::cout << x << ", " << y << std::endl;
    }
};

int main() {
    const Point p = {1, 2};
    p.show();
    // p.x = 5; // 错误:常对象不可修改成员
}

常对象只能调用常成员函数(即声明为 void func() const 的函数)。

8. 对象之间的比较与赋值

对象可以相互赋值:

Student s1, s2;
s1.name = "A";
s2 = s1; // 成员变量逐个拷贝

但若希望对象间的比较(==、< 等)有意义,需要重载运算符(进阶内容,后续讲述)。

9. 对象与内存模型

每个对象在内存中都有独立的成员变量副本

Student s1 ──► name="张三", age=20
Student s2 ──► name="李四", age=21

而静态成员在所有对象间共享。

三、完整示例

#include <iostream>
#include <string>
using namespace std;

class Student {
public:
    string name;
    int age;

    Student(string n = "未知", int a = 0) : name(n), age(a) {
        cout << "Constructed: " << name << endl;
    }

    ~Student() {
        cout << "Destructed: " << name << endl;
    }

    void show() const {
        cout << "Name: " << name << ", Age: " << age << endl;
    }
};

int main() {
    Student s1("张三", 20);
    Student s2("李四", 21);

    s1.show();
    s2.show();

    Student* p = new Student("王五", 22);
    p->show();
    delete p;  // 手动释放动态对象
}

输出:

Constructed: 张三
Constructed: 李四
Constructed: 王五
Name: 张三, Age: 20
Name: 李四, Age: 21
Name: 王五, Age: 22
Destructed: 王五
Destructed: 李四
Destructed: 张三

四、面向对象的三大特征

面向对象编程(OOP)的核心思想可以概括为三大特征:封装(Encapsulation)、继承(Inheritance)和多态(Polymorphism)。这三大特征是 C++ 类与对象设计的基础,也是理解和应用面向对象程序设计的关键。

继承与多态的内容属于C++面向对象进阶内容。

1. 封装(Encapsulation)

封装是面向对象最基本的特征之一,也是类和对象概念的核心。

封装的核心思想是将对象的状态(数据)和行为(函数)组合到一个整体——类中,并通过访问控制机制对外部可见性进行限制和保护,对受保护的私有数据仅可以使用成员函数进行操作。

作用

  1. 对内部数据进行保护,只允许可信的方法或对象访问;
  2. 隐藏实现细节,使类的使用者无需了解内部工作原理;
  3. 提供统一接口,提高模块化和可维护性。

示例:

class Student {
private:
    std::string name; // 私有成员,外部无法直接访问
    int age;

public:
    void setInfo(const std::string& n, int a) { // 提供接口修改数据
        name = n;
        age = a;
    }

    void display() const { // 提供接口访问数据
        std::cout << "Name: " << name << ", Age: " << age << std::endl;
    }
};

在上例中,nameage 仅能通过 setInfodisplay 访问和修改,这就是封装的典型应用。

封装就是把客观事物封装成抽象类,并控制外部访问权限,保护数据安全并提高代码可维护性。

2. 继承(Inheritance)

继承是面向对象中的第二大特征,它允许新建的类复用已有类的属性和行为,并在此基础上进行扩展或修改。

被继承的类称为基类(Base Class)或父类,从基类继承的类称为派生类(Derived Class)或子类。

作用

  1. 代码复用:无需重复编写已有功能;
  2. 构建类层次:形成“父类-子类”的组织结构;
  3. 支持多态:继承是实现运行时多态的前提。

实现方式

  • 公有继承(public inheritance):基类公有成员在派生类中仍为公有
  • 保护继承(protected inheritance):基类公有/保护成员在派生类中变为保护
  • 私有继承(private inheritance):基类公有/保护成员在派生类中变为私有
  • 组合(Composition):在类中包含另一个类对象作为成员,用于实现“has-a”关系

示例:

class Person {
protected:
    std::string name;
public:
    void setName(const std::string& n) { name = n; }
};

class Student : public Person { // Student 继承 Person
private:
    int grade;
public:
    void setGrade(int g) { grade = g; }
    void showInfo() const {
        std::cout << "Name: " << name << ", Grade: " << grade << std::endl;
    }
};

Student 继承了 Personname,同时扩展了 grade,这就是继承的典型应用。 如果需要类间关系更紧密,还可以通过组合在类中嵌套其他类。

继承是在无需重写已有类功能的前提下扩展功能,实现类复用与层次化管理。

3. 多态(Polymorphism)

多态是面向对象的第三大特征,允许同一个操作作用于不同对象表现出不同的行为。

多态允许父类指针或引用指向子类对象,并根据对象实际类型执行不同操作。

英文 polymorphism 来自“多形性”,意为“一个接口,多种形态”。

类型

  1. 编译时多态(静态多态):

    • 通过函数重载、运算符重载实现
    • 在编译阶段就确定调用哪个函数
  2. 运行时多态(动态多态):

    • 通过虚函数(virtual)实现
    • 在程序运行时根据实际对象类型决定调用哪个函数

实现方式:

  • 覆盖(Override):派生类重新定义基类的虚函数,运行时根据实际对象调用
  • 重载(Overload):同名函数参数不同,编译时决定调用哪个函数

示例(运行时多态):

class Animal {
public:
    virtual void speak() const { std::cout << "Animal sound" << std::endl; }
};

class Dog : public Animal {
public:
    void speak() const override { std::cout << "Woof!" << std::endl; }
};

int main() {
    Animal* a = new Dog();
    a->speak(); // 输出 "Woof!",根据实际对象类型调用
    delete a;
}

speak 被声明为虚函数,父类指针调用时根据子类实际类型执行函数,这就是运行时多态。

多态使得统一接口处理不同对象成为可能,提高了程序的灵活性和可扩展性。