《C++ Primer》导学系列:第 7 章 - 类

7.1 定义抽象数据类型

7.1.1 类的基本概念

在C++中,类是用户定义的类型,提供了一种将数据和操作这些数据的函数(成员函数)组合在一起的方法。类定义了对象的属性和行为,通过实例化类来创建对象。

7.1.2 定义类

定义类时,需要指定类的名称,并在类体内声明数据成员和成员函数。类定义的一般形式如下:

class ClassName {
public:
    // 成员函数声明
    returnType functionName(parameterList);

private:
    // 数据成员声明
    dataType memberName;
};
示例代码
#include <iostream>
#include <string>

class Person {
public:
    // 成员函数声明
    void setName(const std::string &name);
    std::string getName() const;

private:
    // 数据成员声明
    std::string name;
};

// 成员函数定义
void Person::setName(const std::string &name) {
    this->name = name;
}

std::string Person::getName() const {
    return name;
}

int main() {
    Person person;
    person.setName("Alice");
    std::cout << "Name: " << person.getName() << std::endl;
    return 0;
}

在这个示例中,定义了一个Person类,包含数据成员name和成员函数setNamegetName

7.1.3 成员函数

成员函数是类的函数,可以访问类的成员变量。成员函数可以在类内部声明,在类外部定义。成员函数的定义需要使用类名和作用域运算符::

示例代码
#include <iostream>
#include <string>

class Book {
public:
    void setTitle(const std::string &title);
    std::string getTitle() const;

private:
    std::string title;
};

void Book::setTitle(const std::string &title) {
    this->title = title;
}

std::string Book::getTitle() const {
    return title;
}

int main() {
    Book book;
    book.setTitle("C++ Primer");
    std::cout << "Title: " << book.getTitle() << std::endl;
    return 0;
}

在这个示例中,定义了一个Book类,包含数据成员title和成员函数setTitlegetTitle

7.1.4 构造函数

构造函数是用于创建类对象并初始化数据成员的特殊成员函数。构造函数的名称与类名相同,没有返回类型。

示例代码
#include <iostream>
#include <string>

class Car {
public:
    Car(const std::string &brand, int year); // 构造函数声明
    void display() const;

private:
    std::string brand;
    int year;
};

Car::Car(const std::string &brand, int year) : brand(brand), year(year) {} // 构造函数定义

void Car::display() const {
    std::cout << "Brand: " << brand << ", Year: " << year << std::endl;
}

int main() {
    Car car("Toyota", 2020);
    car.display();
    return 0;
}

在这个示例中,定义了一个Car类,包含数据成员brandyear,并通过构造函数初始化这些成员。

7.1.5 类的接口和实现

类的接口是指类的公共成员,包括公共数据成员和公共成员函数。类的实现是指类的私有成员和成员函数的定义。通过将接口和实现分离,可以提高代码的可读性和可维护性。

示例代码
#include <iostream>
#include <string>

class Animal {
public:
    void setName(const std::string &name);
    std::string getName() const;

private:
    std::string name;
};

void Animal::setName(const std::string &name) {
    this->name = name;
}

std::string Animal::getName() const {
    return name;
}

int main() {
    Animal animal;
    animal.setName("Elephant");
    std::cout << "Animal: " << animal.getName() << std::endl;
    return 0;
}

在这个示例中,定义了一个Animal类,包含数据成员name和成员函数setNamegetName

重点与难点分析

重点

  1. 类的定义:掌握类的基本结构和定义方法,理解类的成员变量和成员函数的声明与定义。
  2. 构造函数:理解构造函数的作用和定义方法,掌握构造函数的初始化列表的使用。
  3. 类的接口和实现:理解类的接口和实现的概念,掌握将类的接口与实现分离的方法。

难点

  1. 成员函数的定义:初学者需要通过实践掌握成员函数在类外部定义的方法,理解作用域运算符::的使用。
  2. 构造函数的初始化列表:掌握构造函数初始化列表的语法和使用方法,理解其在类对象初始化中的作用。

练习题解析

  1. 练习7.1:定义一个Student类,包含数据成员nameage,以及相应的构造函数和成员函数。
    • 示例代码
#include <iostream>
#include <string>

class Student {
public:
    Student(const std::string &name, int age);
    void display() const;

private:
    std::string name;
    int age;
};

Student::Student(const std::string &name, int age) : name(name), age(age) {}

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

int main() {
    Student student("John", 21);
    student.display();
    return 0;
}
  1. 练习7.2:编写一个Rectangle类,包含成员函数areaperimeter,用于计算矩形的面积和周长。
    • 示例代码
#include <iostream>

class Rectangle {
public:
    Rectangle(double width, double height);
    double area() const;
    double perimeter() const;

private:
    double width;
    double height;
};

Rectangle::Rectangle(double width, double height) : width(width), height(height) {}

double Rectangle::area() const {
    return width * height;
}

double Rectangle::perimeter() const {
    return 2 * (width + height);
}

int main() {
    Rectangle rect(5.0, 3.0);
    std::cout << "Area: " << rect.area() << std::endl;
    std::cout << "Perimeter: " << rect.perimeter() << std::endl;
    return 0;
}
  1. 练习7.3:定义一个Circle类,包含数据成员radius,并实现计算圆周长和面积的成员函数。
    • 示例代码
#include <iostream>
#include <cmath>

class Circle {
public:
    Circle(double radius);
    double circumference() const;
    double area() const;

private:
    double radius;
};

Circle::Circle(double radius) : radius(radius) {}

double Circle::circumference() const {
    return 2 * M_PI * radius;
}

double Circle::area() const {
    return M_PI * radius * radius;
}

int main() {
    Circle circle(5.0);
    std::cout << "Circumference: " << circle.circumference() << std::endl;
    std::cout << "Area: " << circle.area() << std::endl;
    return 0;
}
  1. 练习7.4:编写一个BankAccount类,包含数据成员balance,实现存款和取款的成员函数。
    • 示例代码
#include <iostream>

class BankAccount {
public:
    BankAccount(double initialBalance);
    void deposit(double amount);
    void withdraw(double amount);
    double getBalance() const;

private:
    double balance;
};

BankAccount::BankAccount(double initialBalance) : balance(initialBalance) {}

void BankAccount::deposit(double amount) {
    balance += amount;
}

void BankAccount::withdraw(double amount) {
    if (amount <= balance) {
        balance -= amount;
    } else {
        std::cout << "Insufficient balance." << std::endl;
    }
}

double BankAccount::getBalance() const {
    return balance;
}

int main() {
    BankAccount account(1000.0);
    account.deposit(500.0);
    account.withdraw(200.0);
    std::cout << "Balance: $" << account.getBalance() << std::endl;
    return 0;


}

总结与提高

本节总结

  1. 学习了类的定义和基本概念,掌握了成员函数和数据成员的声明与定义方法。
  2. 理解了构造函数的作用和定义方法,掌握了构造函数初始化列表的使用。
  3. 通过示例代码和练习题,加深了对类的接口和实现的理解和应用。

提高建议

  1. 多练习类的定义与使用:通过编写各种包含类的程序,熟悉类的定义和使用方法,提高代码的组织性和可读性。
  2. 深入理解构造函数:通过实践掌握构造函数的初始化列表和重载构造函数的方法,理解其在对象初始化中的作用。
  3. 封装类的接口与实现:在编写类时,合理设计类的接口与实现,提高代码的可维护性和安全性。

7.2 访问控制与封装

7.2.1 访问控制

访问控制用于限制类成员的访问权限。C++ 提供了三种访问控制级别:

  • public:公有成员可以被类的任何部分访问,也可以被类外部的代码访问。
  • protected:受保护成员可以被类的成员和派生类访问,但不能被类外部的代码访问。
  • private:私有成员只能被类的成员访问,不能被派生类和类外部的代码访问。
示例代码
#include <iostream>

class Base {
public:
    int publicVar;
protected:
    int protectedVar;
private:
    int privateVar;
};

class Derived : public Base {
public:
    void accessMembers() {
        publicVar = 1;         // 可以访问
        protectedVar = 2;      // 可以访问
        // privateVar = 3;     // 无法访问,编译错误
    }
};

int main() {
    Base base;
    base.publicVar = 1;        // 可以访问
    // base.protectedVar = 2;  // 无法访问,编译错误
    // base.privateVar = 3;    // 无法访问,编译错误
    return 0;
}

在这个示例中,Base类有公有、受保护和私有成员,Derived类可以访问公有和受保护成员,但不能访问私有成员。

7.2.2 封装

封装是将数据和操作数据的函数绑定在一起,并将细节隐藏起来,只暴露接口。通过封装,可以保护数据不被外界直接访问和修改,增强代码的安全性和可维护性。

示例代码
#include <iostream>
#include <string>

class Employee {
public:
    void setName(const std::string &name);
    std::string getName() const;
    void setSalary(double salary);
    double getSalary() const;

private:
    std::string name;
    double salary;
};

void Employee::setName(const std::string &name) {
    this->name = name;
}

std::string Employee::getName() const {
    return name;
}

void Employee::setSalary(double salary) {
    this->salary = salary;
}

double Employee::getSalary() const {
    return salary;
}

int main() {
    Employee emp;
    emp.setName("John Doe");
    emp.setSalary(50000);
    std::cout << "Employee: " << emp.getName() << ", Salary: $" << emp.getSalary() << std::endl;
    return 0;
}

在这个示例中,Employee类通过公有成员函数对私有数据成员进行封装,保护数据成员不被直接访问。

7.2.3 友元

友元(Friend)是可以访问类的私有和受保护成员的非成员函数或其他类。友元关系不是传递的,也不能继承。

友元函数
#include <iostream>

class Box {
    friend void printBox(const Box &box);  // 声明友元函数
public:
    Box(double length, double width, double height) : length(length), width(width), height(height) {}

private:
    double length;
    double width;
    double height;
};

void printBox(const Box &box) {
    std::cout << "Length: " << box.length << ", Width: " << box.width << ", Height: " << box.height << std::endl;
}

int main() {
    Box box(10.0, 5.0, 3.0);
    printBox(box);
    return 0;
}

在这个示例中,printBox函数是Box类的友元,可以访问Box类的私有成员。

友元类
#include <iostream>

class Engine {
    friend class Car;  // 声明友元类
public:
    Engine(int horsepower) : horsepower(horsepower) {}

private:
    int horsepower;
};

class Car {
public:
    Car(const std::string &model, int horsepower) : model(model), engine(horsepower) {}

    void showDetails() const {
        std::cout << "Model: " << model << ", Horsepower: " << engine.horsepower << std::endl;
    }

private:
    std::string model;
    Engine engine;
};

int main() {
    Car car("Toyota", 150);
    car.showDetails();
    return 0;
}

在这个示例中,Car类是Engine类的友元类,可以访问Engine类的私有成员。

重点与难点分析

重点

  1. 访问控制:掌握publicprotectedprivate访问控制的用法和区别,理解其在类中的应用。
  2. 封装:理解封装的概念,掌握通过公有成员函数访问私有数据成员的方法。
  3. 友元:了解友元函数和友元类的定义和用法,理解其在访问私有和受保护成员中的作用。

难点

  1. 友元的使用:初学者需要通过实践掌握友元的定义和使用方法,理解友元关系的非传递性和不可继承性。
  2. 封装的实现:通过实践理解封装的概念,掌握在类中实现封装的方法,提高代码的安全性和可维护性。

练习题解析

  1. 练习7.5:定义一个Laptop类,包含私有数据成员brandprice,并实现公有成员函数设置和获取这些成员的值。
    • 示例代码
#include <iostream>
#include <string>

class Laptop {
public:
    void setBrand(const std::string &brand);
    std::string getBrand() const;
    void setPrice(double price);
    double getPrice() const;

private:
    std::string brand;
    double price;
};

void Laptop::setBrand(const std::string &brand) {
    this->brand = brand;
}

std::string Laptop::getBrand() const {
    return brand;
}

void Laptop::setPrice(double price) {
    this->price = price;
}

double Laptop::getPrice() const {
    return price;
}

int main() {
    Laptop laptop;
    laptop.setBrand("Dell");
    laptop.setPrice(999.99);
    std::cout << "Brand: " << laptop.getBrand() << ", Price: $" << laptop.getPrice() << std::endl;
    return 0;
}
  1. 练习7.6:编写一个Box类,包含私有数据成员lengthwidthheight,并实现友元函数计算盒子的体积。
    • 示例代码
#include <iostream>

class Box {
    friend double calculateVolume(const Box &box);  // 声明友元函数
public:
    Box(double length, double width, double height) : length(length), width(width), height(height) {}

private:
    double length;
    double width;
    double height;
};

double calculateVolume(const Box &box) {
    return box.length * box.width * box.height;
}

int main() {
    Box box(10.0, 5.0, 3.0);
    std::cout << "Volume: " << calculateVolume(box) << " cubic units" << std::endl;
    return 0;
}
  1. 练习7.7:定义一个Library类,包含私有数据成员namebooks(书籍数量),并实现友元类Librarian,能够访问和修改Library类的私有成员。
    • 示例代码
#include <iostream>
#include <string>

class Library {
    friend class Librarian;  // 声明友元类
public:
    Library(const std::string &name, int books) : name(name), books(books) {}

private:
    std::string name;
    int books;
};

class Librarian {
public:
    void setLibraryName(Library &library, const std::string &name) {
        library.name = name;
    }

    void addBooks(Library &library, int count) {
        library.books += count;
    }

    void showLibrary(const Library &library) const {
        std::cout << "Library: " << library.name << ", Books: " << library.books << std::endl;
    }
};

int main() {
    Library library("Central Library", 1000);
    Librarian librarian;
    librarian.showLibrary(library);
    librarian.addBooks(library, 200);
    librarian.showLibrary(library);
    librarian.setLibraryName(library, "City Library");
    librarian.showLibrary(library);
    return 0;
}
  1. 练习7.8:编写一个Account类,包含私有数据成员balance,实现存款、取款和显示余额的公有成员 函数,并确保封装性。
    • 示例代码:
#include <iostream>

class Account {
public:
Account(double initialBalance);
void deposit(double amount);
void withdraw(double amount);
void displayBalance() const;

private:
double balance;
};

Account::Account(double initialBalance) : balance(initialBalance) {}

void Account::deposit(double amount) {
    balance += amount;
}

void Account::withdraw(double amount) {
    if (amount <= balance) {
        balance -= amount;
    } else {
        std::cout << "Insufficient balance." << std::endl;
    }
}

void Account::displayBalance() const {
    std::cout << "Balance: $" << balance << std::endl;
}

int main() {
    Account account(1000.0);
    account.deposit(500.0);
    account.withdraw(200.0);
    account.displayBalance();
    account.withdraw(2000.0);  // 测试不足余额情况
    return 0;
}

总结与提高

本节总结

  1. 学习了访问控制的基本概念,掌握了publicprotectedprivate访问控制的使用方法。
  2. 理解了封装的概念,掌握了通过公有成员函数访问私有数据成员的方法,提高了代码的安全性和可维护性。
  3. 通过示例代码和练习题,理解了友元函数和友元类的定义和使用方法,掌握了友元在访问私有和受保护成员中的作用。

提高建议

  1. 多练习访问控制的使用:通过编写各种包含不同访问控制级别的类,熟悉publicprotectedprivate的使用方法,理解其在类设计中的作用。
  2. 深入理解封装的实现:通过实践掌握封装的概念,合理设计类的接口与实现,提高代码的安全性和可维护性。
  3. 掌握友元的使用:在编写复杂类时,合理使用友元函数和友元类,提高类之间的协作性和灵活性。

7.3 类的其他特性

7.3.1 类成员再探

在前面的章节中,我们学习了如何定义类的成员函数和数据成员。这一小节将进一步探讨类成员的高级特性和使用方法,包括初始化列表、成员初始化顺序和常量成员。

初始化列表

初始化列表用于在构造函数体之前初始化类成员。使用初始化列表可以提高代码的效率和可读性,尤其是在初始化常量成员和引用成员时。

示例代码
#include <iostream>
#include <string>

class Person {
public:
    Person(const std::string &name, int age) : name(name), age(age) {}
    void display() const {
        std::cout << "Name: " << name << ", Age: " << age << std::endl;
    }

private:
    std::string name;
    int age;
};

int main() {
    Person person("Alice", 30);
    person.display();
    return 0;
}

在这个示例中,初始化列表用于初始化nameage成员。

成员初始化顺序

成员初始化的顺序按照它们在类中声明的顺序进行,而不是在初始化列表中的顺序。因此,确保在编写初始化列表时遵循声明顺序。

示例代码
#include <iostream>

class Example {
public:
    Example(int a, int b) : b(b), a(a) {}  // 初始化顺序依然是 a, b
    void display() const {
        std::cout << "a: " << a << ", b: " << b << std::endl;
    }

private:
    int a;
    int b;
};

int main() {
    Example ex(1, 2);
    ex.display();
    return 0;
}

尽管初始化列表中ba之前,实际初始化顺序仍然是a先于b

常量成员

常量成员在类定义时被声明为const,必须通过初始化列表进行初始化,且初始化后不能修改。

示例代码
#include <iostream>

class Circle {
public:
    Circle(double radius) : radius(radius) {}
    void display() const {
        std::cout << "Radius: " << radius << std::endl;
    }

private:
    const double radius;
};

int main() {
    Circle circle(5.0);
    circle.display();
    return 0;
}

在这个示例中,常量成员radius通过初始化列表进行初始化。

7.3.2 返回*this的成员函数

返回*this的成员函数允许我们将类的成员函数串联起来调用(链式调用)。这种方法在实现流操作符重载和构造复杂对象时非常有用。

示例代码
#include <iostream>
#include <string>

class Person {
public:
    Person &setName(const std::string &name) {
        this->name = name;
        return *this;
    }

    Person &setAge(int age) {
        this->age = age;
        return *this;
    }

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

private:
    std::string name;
    int age;
};

int main() {
    Person person;
    person.setName("Alice").setAge(30);
    person.display();
    return 0;
}

在这个示例中,setNamesetAge函数返回*this,允许链式调用这些成员函数。

7.3.3 友元再探

友元(friend)是可以访问类的私有和受保护成员的非成员函数或其他类。友元关系不是传递的,也不能继承。

类之间的友元关系

类之间可以建立友元关系,使一个类能够访问另一个类的私有成员。

示例代码
#include <iostream>

class Engine;

class Car {
public:
    void displayEngine(const Engine &engine);
};

class Engine {
    friend class Car;  // Car 是 Engine 的友元类
public:
    Engine(int horsepower) : horsepower(horsepower) {}

private:
    int horsepower;
};

void Car::displayEngine(const Engine &engine) {
    std::cout << "Engine horsepower: " << engine.horsepower << std::endl;
}

int main() {
    Engine engine(150);
    Car car;
    car.displayEngine(engine);
    return 0;
}

在这个示例中,Car类是Engine类的友元,可以访问Engine类的私有成员。

成员函数作为友元

一个类的成员函数可以成为另一个类的友元,从而访问该类的私有成员。

示例代码
#include <iostream>

class Building;

class Architect {
public:
    void design(Building &building);
};

class Building {
    friend void Architect::design(Building &building);  // Architect 的 design 函数是 Building 的友元
public:
    Building(int floors) : floors(floors) {}

private:
    int floors;
};

void Architect::design(Building &building) {
    building.floors = 10;  // 访问 Building 的私有成员
}

int main() {
    Building building(5);
    Architect architect;
    architect.design(building);
    return 0;
}

在这个示例中,Architect类的成员函数designBuilding类的友元,可以访问Building类的私有成员。

函数重载和友元

友元函数可以重载,通过定义不同的参数列表实现不同的功能。

示例代码
#include <iostream>

class Rectangle;

class Geometry {
public:
    double calculateArea(const Rectangle &rect);
    double calculateArea(const Rectangle &rect, double height);
};

class Rectangle {
    friend double Geometry::calculateArea(const Rectangle &rect);  // Geometry 的 calculateArea 是 Rectangle 的友元
    friend double Geometry::calculateArea(const Rectangle &rect, double height);
public:
    Rectangle(double width, double height) : width(width), height(height) {}

private:
    double width;
    double height;
};

double Geometry::calculateArea(const Rectangle &rect) {
    return rect.width * rect.height;
}

double Geometry::calculateArea(const Rectangle &rect, double height) {
    return rect.width * height;
}

int main() {
    Rectangle rect(5.0, 3.0);
    Geometry geom;
    std::cout << "Area: " << geom.calculateArea(rect) << std::endl;
    std::cout << "Area with height 4.0: " << geom.calculateArea(rect, 4.0) << std::endl;
    return 0;
}

在这个示例中,Geometry类的calculateArea函数被重载,并且是Rectangle类的友元。

友元声明和作用域

友元声明可以在类的任意位置进行,但友元关系仅在声明的类内有效。友元关系不能被继承,也不是传递的。

示例代码
#include <iostream>

class B;

class A {
    friend class B;  // B 是 A 的友元类
public:
    A(int value) : value(value) {}

private:
    int value;
};

class B {
public:
    void displayA(const A &a) {
        std::cout << "A's value: " << a.value << std::endl;
    }
};

class C : public B {};

int main() {
    A a(100);
    B b;
    C c;
    b.displayA(a);  // B 可以访问 A 的私有成员
    // c.displayA(a);  // 编译错误,C 不能访问 A 的私有成员
    return 0;
}

在这个示例中,BA的友元类,可以访问A的私有成员,但继承自BC不能继承友元关系。

重点与难点分析

重点

  1. 类成员再探:掌握初始化列表、成员初始化顺序和常量成员的定义和使用方法。
  2. 返回*this的成员函数:理解返回*this的成员函数的用途,掌握链式调用的实现方法。
  3. 友元再探:理解类之间的友元关系、成员函数作为友元、函数重载和友元、友元声明和作用域的概念和应用。

难点

  1. 成员初始化顺序:初学者需要理解成员初始化顺序的重要性,避免初始化列表中的顺序与成员声明顺序不一致。
  2. 友元的使用:掌握友元函数和友元类的使用方法,理解其在类设计中的作用,避免滥用友元导致类的耦合度过高。

练习题解析

  1. 练习7.14:定义一个Vehicle类,包含常量成员maxSpeed,并通过初始化列表进行初始化。
    • 示例代码
#include <iostream>

class Vehicle {
public:
    Vehicle(int speed) : maxSpeed(speed) {}
    void display() const {
        std::cout << "Max Speed: " << maxSpeed << " km/h" << std::endl;
    }

private:
    const int maxSpeed;
};

int main() {
    Vehicle car(180);
    car.display();
    return 0;
}
  1. 练习7.15:编写一个Book类,包含返回*this的成员函数setTitlesetAuthor,实现链式调用。
    • 示例代码
#include <iostream>
#include <string>

class Book {
public:
    Book &setTitle(const std::string &title) {
        this->title = title;
        return *this;
    }

    Book &setAuthor(const std::string &author) {
        this->author = author;
        return *this;
    }

    void display() const {
        std::cout << "Title: " << title << ", Author: " << author << std::endl;
    }

private:
    std::string title;
    std::string author;
};

int main() {
    Book book;
    book.setTitle("C++ Primer").setAuthor("Lippman");
    book.display();
    return 0;
}
  1. 练习7.16:定义两个类PointCircle,建立它们之间的友元关系,使Circle类可以访问Point类的私有成员。
    • 示例代码
#include <iostream>

class Point {
    friend class Circle;  // Circle 是 Point 的友元类
public:
    Point(int x, int y) : x(x), y(y) {}

private:
    int x, y;
};

class Circle {
public:
    Circle(int radius) : radius(radius) {}

    void display(const Point &center) {
        std::cout << "Center: (" << center.x << ", " << center.y << "), Radius: " << radius << std::endl;
    }

private:
    int radius;
};

int main() {
    Point center(5, 5);
    Circle circle(10);
    circle.display(center);
    return 0;
}
  1. 练习7.17:编写一个Vector类,并定义友元函数重载加法运算符,实现两个向量的相加。
    • 示例代码
#include <iostream>

class Vector {
    friend Vector operator+(const Vector &v1, const Vector &v2);  // 友元函数重载加法运算符
public:
    Vector(int x, int y) : x(x), y(y) {}

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

private:
    int x, y;
};

Vector operator+(const Vector &v1, const Vector &v2) {
    return Vector(v1.x + v2.x, v1.y + v2.y);
}

int main() {
    Vector v1(1, 2), v2(3, 4);
    Vector v3 = v1 + v2;
    v3.display();
    return 0;
}
  1. 练习7.18:定义一个Matrix类,并将其成员函数transpose声明为友元函数,实现矩阵的转置。
    • 示例代码
#include <iostream>

class Matrix;

class Operations {
public:
    void transpose(Matrix &matrix);
};

class Matrix {
    friend void Operations::transpose(Matrix &matrix);  // Operations 的 transpose 函数是 Matrix 的友元
public:
    Matrix(int rows, int cols) : rows(rows), cols(cols) {
        data = new int*[rows];
        for (int i = 0; i < rows; ++i) {
            data[i] = new int[cols]();
        }
    }

    void setElement(int row, int col, int value) {
        data[row][col] = value;
    }

    void display() const {
        for (int i = 0; i < rows; ++i) {
            for (int j = 0; j < cols; ++j) {
                std::cout << data[i][j] << " ";
            }
            std::cout << std::endl;
        }
    }

    ~Matrix() {
        for (int i = 0; i < rows; ++i) {
            delete[] data[i];
        }
        delete[] data;
    }

private:
    int **data;
    int rows, cols;
};

void Operations::transpose(Matrix &matrix) {
    int **newData = new int*[matrix.cols];
    for (int i = 0; i < matrix.cols; ++i) {
        newData[i] = new int[matrix.rows];
        for (int j = 0; j < matrix.rows; ++j) {
            newData[i][j] = matrix.data[j][i];
        }
    }

    for (int i = 0; i < matrix.rows; ++i) {
        delete[] matrix.data[i];
    }
    delete[] matrix.data;

    matrix.data = newData;
    std::swap(matrix.rows, matrix.cols);
}

int main() {
    Matrix matrix(2, 3);
    matrix.setElement(0, 0, 1);
    matrix.setElement(0, 1, 2);
    matrix.setElement(0, 2, 3);
    matrix.setElement(1, 0, 4);
    matrix.setElement(1, 1, 5);
    matrix.setElement(1, 2, 6);

    std::cout << "Original matrix:" << std::endl;
    matrix.display();

    Operations ops;
    ops.transpose(matrix);

    std::cout << "Transposed matrix:" << std::endl;
    matrix.display();

    return 0;
}

总结与提高

本节总结

  1. 学习了类成员的高级特性,包括初始化列表、成员初始化顺序和常量成员。
  2. 掌握了返回*this的成员函数的定义和使用,理解了链式调用的实现方法。
  3. 通过友元再探,理解了类之间的友元关系、成员函数作为友元、函数重载和友元、友元声明和作用域的概念和应用。

提高建议

  1. 多练习初始化列表和成员初始化顺序:通过编写各种包含初始化列表和成员初始化顺序的类,熟悉这些特性的使用方法。
  2. 深入理解友元的使用:通过实践掌握友元函数和友元类的使用方法,理解其在类设计中的作用,避免滥用友元导致类的耦合度过高。
  3. 掌握返回*this的成员函数:在编写复杂类时,合理使用返回*this的成员函数,提高代码的可读性和灵活性。

7.4 类的作用域

7.4.1 类作用域概述

在C++中,类的成员变量和成员函数有自己的作用域。类作用域决定了这些成员在何处可见以及如何访问。理解类作用域对于正确设计和实现类至关重要。

7.4.2 成员名字的作用域

类的成员名字在类的作用域内是可见的。成员名字包括成员变量、成员函数以及嵌套类型。

示例代码
#include <iostream>

class Example {
public:
    void setValue(int value) {
        this->value = value;
    }

    void display() const {
        std::cout << "Value: " << value << std::endl;
    }

private:
    int value;
};

int main() {
    Example example;
    example.setValue(42);
    example.display();
    return 0;
}

在这个示例中,value是类Example的成员变量,其作用域在整个类内。

7.4.3 类外部定义成员函数

成员函数的定义可以在类的外部进行,但仍属于类的作用域。这种方法可以使类的声明更加简洁,同时将实现细节分离到类的外部。

示例代码
#include <iostream>

class Rectangle {
public:
    Rectangle(double width, double height);
    double area() const;

private:
    double width;
    double height;
};

// 类外部定义成员函数
Rectangle::Rectangle(double width, double height) : width(width), height(height) {}

double Rectangle::area() const {
    return width * height;
}

int main() {
    Rectangle rect(5.0, 3.0);
    std::cout << "Area: " << rect.area() << std::endl;
    return 0;
}

在这个示例中,Rectangle类的构造函数和area函数在类的外部定义。

7.4.4 类的嵌套作用域

嵌套类是定义在另一个类内部的类。嵌套类可以访问其外部类的私有成员,而外部类不能直接访问嵌套类的成员。嵌套类有自己的作用域,独立于外部类。

示例代码
#include <iostream>

class Outer {
public:
    class Inner {
    public:
        void display() const {
            std::cout << "Inner class" << std::endl;
        }
    };

    void show() const {
        std::cout << "Outer class" << std::endl;
        inner.display();
    }

private:
    Inner inner;
};

int main() {
    Outer outer;
    outer.show();
    return 0;
}

在这个示例中,Inner类是Outer类的嵌套类,有自己的作用域。

7.4.5 类作用域中的名字查找

名字查找是指在类作用域中查找成员名字的过程。名字查找规则决定了在类中查找成员名字时的顺序和范围。

示例代码
#include <iostream>

class Base {
public:
    void display() const {
        std::cout << "Base display" << std::endl;
    }
};

class Derived : public Base {
public:
    void display() const {
        std::cout << "Derived display" << std::endl;
    }
};

int main() {
    Derived derived;
    derived.display();  // 调用的是 Derived 类的 display 函数
    derived.Base::display();  // 显式调用 Base 类的 display 函数
    return 0;
}

在这个示例中,名字查找规则决定了默认调用Derived类的display函数,可以通过显式作用域指定调用Base类的display函数。

7.4.6 类的命名空间作用域

类也可以定义在命名空间中,这样类及其成员名字的作用域就被限制在命名空间内。命名空间作用域有助于避免名字冲突。

示例代码
#include <iostream>

namespace MyNamespace {
    class MyClass {
    public:
        void display() const {
            std::cout << "MyNamespace::MyClass" << std::endl;
        }
    };
}

int main() {
    MyNamespace::MyClass obj;
    obj.display();
    return 0;
}

在这个示例中,MyClass类定义在MyNamespace命名空间中,其名字作用域被限制在命名空间内。

重点与难点分析

重点

  1. 类作用域:理解类成员名字的作用域,掌握类外部定义成员函数的方法。
  2. 嵌套类:理解嵌套类的作用域和访问规则,掌握嵌套类的定义和使用。
  3. 名字查找规则:掌握类作用域中的名字查找规则,理解显式作用域指定的用法。
  4. 命名空间作用域:理解类的命名空间作用域,掌握在命名空间中定义类的方法。

难点

  1. 名字查找规则:初学者需要通过实践掌握名字查找规则,避免名字冲突和作用域错误。
  2. 嵌套类的访问规则:理解嵌套类与外部类之间的访问规则,避免访问权限错误。

练习题解析

  1. 练习7.19:定义一个Library类,包含嵌套类Book,并实现展示图书信息的功能。
    • 示例代码
#include <iostream>
#include <string>

class Library {
public:
    class Book {
    public:
        Book(const std::string &title, const std::string &author) : title(title), author(author) {}
        void display() const {
            std::cout << "Title: " << title << ", Author: " << author << std::endl;
        }

    private:
        std::string title;
        std::string author;
    };

    void addBook(const std::string &title, const std::string &author) {
        Book book(title, author);
        book.display();
    }
};

int main() {
    Library library;
    library.addBook("1984", "George Orwell");
    return 0;
}
  1. 练习7.20:编写一个Company类,包含嵌套类Employee,并实现添加和显示员工信息的功能。
    • 示例代码
#include <iostream>
#include <string>
#include <vector>

class Company {
public:
    class Employee {
    public:
        Employee(const std::string &name, int id) : name(name), id(id) {}
        void display() const {
            std::cout << "Employee ID: " << id << ", Name: " << name << std::endl;
        }

    private:
        std::string name;
        int id;
    };

    void addEmployee(const std::string &name, int id) {
        employees.emplace_back(name, id);
    }

    void showEmployees() const {
        for (const auto &employee : employees) {
            employee.display();
        }
    }

private:
    std::vector<Employee> employees;
};

int main() {
    Company company;
    company.addEmployee("Alice", 101);
    company.addEmployee("Bob", 102);
    company.showEmployees();
    return 0;
}
  1. 练习7.21:定义一个Shape类及其派生类CircleRectangle,并展示多态性的名字查找规则。
    • 示例代码
#include <iostream>

class Shape {
public:
    virtual void draw() const {
        std::cout << "Drawing shape" << std::endl;
    }
};

class Circle : public Shape {
public:
    void draw() const override {
        std::cout << "Drawing circle" << std::endl;
    }
};

class Rectangle : public Shape {
public:
    void draw() const override {
        std::cout << "Drawing rectangle" << std::endl;
    }
};

void drawShape(const Shape &shape) {
    shape.draw();
}

int main() {
    Circle circle;
    Rectangle rectangle;
    drawShape(circle);      // 调用 Circle::draw
    drawShape(rectangle);   // 调用 Rectangle::draw
    return 0;
}
  1. 练习7.22:编写一个Zoo类,包含嵌套类Animal,并实现展示动物信息的功能。
    • 示例代码
#include <iostream>
#include <string>
#include <vector>

class Zoo {
public:
    class Animal {
    public:
        Animal(const std::string &name, const std::string &species) : name(name), species(species) {}
        void display() const {
            std::cout << "Name: " << name << ", Species: " << species << std::endl;
        }

    private:
        std::string name;
        std::string species;
    };

    void addAnimal(const std::string &name, const std::string &species) {
        animals.emplace_back(name, species);
    }

    void showAnimals() const {
        for (const auto &animal : animals) {
            animal.display();
        }
    }

private:
    std::vector<Animal> animals;
};

int main() {
    Zoo zoo;
    zoo.addAnimal("Leo", "Lion");
    zoo.addAnimal("Ella", "Elephant");
    zoo.showAnimals();
    return 0;
}
  1. 练习7.23:定义一个Team类,并在命名空间Sports中定义其成员函数,实现添加和显示队员信息的功能。
    • 示例代码
#include <iostream>
#include <string>
#include <vector>

namespace Sports {
    class Team {
    public:
        void addPlayer(const std::string &name) {
            players.push_back(name);
        }

        void showPlayers() const {
            for (const auto &player : players) {
                std::cout << "Player: " << player << std::endl;
            }
        }

    private:
        std::vector<std::string> players;
    };
}

int main() {
    Sports::Team team;
    team.addPlayer("John");
    team.addPlayer("Alice");
    team.showPlayers();
    return 0;
}

总结与提高

本节总结

  1. 学习了类作用域的基本概念,理解了成员名字的作用域和类外部定义成员函数的方法。
  2. 掌握了嵌套类的定义和使用,理解了嵌套类的作用域和访问规则。
  3. 理解了类作用域中的名字查找规则,掌握了显式作用域指定的用法。
  4. 理解了类的命名空间作用域,掌握了在命名空间中定义类的方法。

提高建议

  1. 多练习类作用域和名字查找:通过编写各种包含复杂作用域的类,熟悉名字查找规则和显式作用域指定的方法。
  2. 深入理解嵌套类的访问规则:通过实践掌握嵌套类与外部类之间的访问规则,提高类的设计能力。
  3. 掌握命名空间作用域:在编写大型程序时,合理使用命名空间,避免名字冲突,提高代码的可读性和可维护性。

7.5 构造函数再探

7.5.1 构造函数初始化列表

构造函数初始化列表用于在构造函数体之前初始化类成员。它的优点是能够直接初始化成员,而不是在构造函数体内进行赋值,从而提高效率。

示例代码
#include <iostream>

class Rectangle {
public:
    Rectangle(double width, double height) : width(width), height(height) {} // 初始化列表
    double area() const {
        return width * height;
    }

private:
    double width;
    double height;
};

int main() {
    Rectangle rect(5.0, 3.0);
    std::cout << "Area: " << rect.area() << std::endl;
    return 0;
}

在这个示例中,Rectangle类的构造函数使用初始化列表直接初始化widthheight成员。

7.5.2 默认构造函数

默认构造函数是指不带参数或所有参数都有默认值的构造函数。当我们定义一个类而没有定义任何构造函数时,编译器会为我们生成一个默认构造函数。

示例代码
#include <iostream>

class Person {
public:
    Person() : name("Unknown"), age(0) {} // 默认构造函数
    void display() const {
        std::cout << "Name: " << name << ", Age: " << age << std::endl;
    }

private:
    std::string name;
    int age;
};

int main() {
    Person person;
    person.display();
    return 0;
}

在这个示例中,Person类有一个默认构造函数,该构造函数将name初始化为"Unknown",age初始化为0。

7.5.3 委托构造函数

C++11引入了委托构造函数的概念,一个构造函数可以调用同一类的另一个构造函数以简化初始化代码。

示例代码
#include <iostream>

class Student {
public:
    Student() : Student("Unknown", 0) {} // 委托构造函数
    Student(const std::string &name, int age) : name(name), age(age) {}
    void display() const {
        std::cout << "Name: " << name << ", Age: " << age << std::endl;
    }

private:
    std::string name;
    int age;
};

int main() {
    Student student1;
    Student student2("Alice", 20);
    student1.display();
    student2.display();
    return 0;
}

在这个示例中,Student类的默认构造函数委托给另一个带参数的构造函数,以简化初始化代码。

7.5.4 拷贝构造函数

拷贝构造函数用于创建一个新对象,它是用现有对象的值初始化的。拷贝构造函数的参数是该类对象的引用,通常为const引用。

示例代码
#include <iostream>

class Box {
public:
    Box(double length, double width, double height) : length(length), width(width), height(height) {}
    Box(const Box &other) : length(other.length), width(other.width), height(other.height) {} // 拷贝构造函数
    double volume() const {
        return length * width * height;
    }

private:
    double length;
    double width;
    double height;
};

int main() {
    Box box1(10.0, 5.0, 3.0);
    Box box2 = box1; // 使用拷贝构造函数
    std::cout << "Box1 volume: " << box1.volume() << std::endl;
    std::cout << "Box2 volume: " << box2.volume() << std::endl;
    return 0;
}

在这个示例中,Box类的拷贝构造函数通过将另一个Box对象的值赋给当前对象的成员来初始化新对象。

7.5.5 移动构造函数

C++11引入了移动构造函数,用于高效地转移资源而不是复制。移动构造函数的参数是该类对象的右值引用。

示例代码
#include <iostream>
#include <utility>

class Resource {
public:
    Resource() : data(new int[1000]) {}
    ~Resource() { delete[] data; }

    // 拷贝构造函数
    Resource(const Resource &other) : data(new int[1000]) {
        std::copy(other.data, other.data + 1000, data);
    }

    // 移动构造函数
    Resource(Resource &&other) noexcept : data(other.data) {
        other.data = nullptr;
    }

private:
    int *data;
};

int main() {
    Resource res1;
    Resource res2 = std::move(res1); // 使用移动构造函数
    return 0;
}

在这个示例中,Resource类的移动构造函数通过转移资源所有权避免了不必要的资源复制,提高了效率。

7.5.6 隐式的类类型转换

隐式的类类型转换允许我们通过单参数构造函数将其他类型的对象转换为类类型。为了防止这种隐式转换,可以在构造函数前加上explicit关键字。

示例代码
#include <iostream>

class Complex {
public:
    Complex(double real, double imag) : real(real), imag(imag) {}
    Complex(double real) : Complex(real, 0.0) {} // 允许从 double 隐式转换为 Complex

    void display() const {
        std::cout << "Real: " << real << ", Imaginary: " << imag << std::endl;
    }

private:
    double real;
    double imag;
};

int main() {
    Complex c1 = 4.5; // 隐式转换
    c1.display();
    return 0;
}

在这个示例中,单参数构造函数允许从double隐式转换为Complex类型。

示例代码(使用explicit防止隐式转换)
#include <iostream>

class Complex {
public:
    explicit Complex(double real, double imag) : real(real), imag(imag) {}
    explicit Complex(double real) : Complex(real, 0.0) {} // 防止从 double 隐式转换为 Complex

    void display() const {
        std::cout << "Real: " << real << ", Imaginary: " << imag << std::endl;
    }

private:
    double real;
    double imag;
};

int main() {
    // Complex c1 = 4.5; // 编译错误:不能隐式转换
    Complex c2(4.5); // 必须显式转换
    c2.display();
    return 0;
}

在这个示例中,explicit关键字防止了从doubleComplex的隐式转换,必须显式调用构造函数。

重点与难点分析

重点

  1. 构造函数初始化列表:掌握构造函数初始化列表的语法和使用方法,理解其优点。
  2. 默认构造函数:理解默认构造函数的概念及其生成规则,掌握自定义默认构造函数的方法。
  3. 委托构造函数:理解委托构造函数的概念,掌握其简化代码的方法。
  4. 拷贝构造函数:理解拷贝构造函数的作用,掌握其定义和使用方法。
  5. 移动构造函数:理解移动构造函数的概念,掌握其在资源转移中的作用及使用方法。
  6. 隐式的类类型转换:理解隐式类类型转换的概念,掌握通过构造函数进行隐式转换的方法,以及使用explicit关键字防止隐式转换。

难点

  1. 初始化列表与成员初始化顺序:初学者需要通过实践掌握初始化列表的使用,并注意成员初始化顺序。
  2. 拷贝构造函数与移动构造函数的区别:理解这两者的区别及各自的使用场景,避免资源管理问题。
  3. 隐式转换与显式转换:理解隐式转换的风险,掌握使用explicit关键字防止不必要的隐式转换。

练习题解析

  1. 练习7.24:定义一个Book类,包含默认构造函数和初始化列表的构造函数,并实现展示书籍信息的功能。
    • 示例代码
#include <iostream>
#include <string>

class Book {
public:
    Book() : title("Unknown"), author("Unknown"), pages(0) {} // 默认构造函数
    Book(const std::string &title, const std::string &author, int pages) 
        : title(title), author(author), pages(pages) {} // 初始化列表

    void display() const {
        std::cout << "Title: " << title << ", Author: " << author << ", Pages: " << pages << std::endl;
    }

private:
    std::string title;
    std::string author;
    int pages;
};

int main() {
    Book book1;
    Book book2("C++ Primer", "Lippman", 976);
    book1.display();
    book2.display();
    return 0;
}
  1. 练习7.25:编写一个Movie类,包含委托构造函数和默认构造函数,实现添加和显示电影信息的功能。
    • 示例代码
#include <iostream>
#include <string>

class Movie {
public:
    Movie() : Movie("Unknown", "Unknown", 0) {} // 委托构造函数
    Movie(const std::string &title, const std::string &director, int duration) 
        : title(title), director(director), duration(duration) {}

    void display() const {
        std::cout << "Title: " << title << ", Director: " << director << ", Duration: " << duration << " minutes" << std::endl;
    }

private:
    std::string title;
    std::string director;
    int duration;
};

int main() {
    Movie movie1;
    Movie movie2("Inception", "Christopher Nolan", 148);
    movie1.display();
    movie2.display();
    return 0;
}
  1. 练习7.26:定义一个Vector类,包含拷贝构造函数和移动构造函数,并实现向量的初始化和展示功能。
    • 示例代码
#include <iostream>

class Vector {
public:
    Vector(size_t size) : size(size), data(new int[size]) {
        std::fill(data, data + size, 0);
    }
    ~Vector() { delete[] data; }

    // 拷贝构造函数
    Vector(const Vector &other) : size(other.size), data(new int[other.size]) {
        std::copy(other.data, other.data + other.size, data);
    }

    // 移动构造函数
    Vector(Vector &&other) noexcept : size(other.size), data(other.data) {
        other.size = 0;
        other.data = nullptr;
    }

    void display() const {
        for (size_t i = 0; i < size; ++i) {
            std::cout << data[i] << " ";
        }
        std::cout << std::endl;
    }

private:
    size_t size;
    int *data;
};

int main() {
    Vector vec1(5);
    Vector vec2 = vec1;  // 拷贝构造函数
    Vector vec3 = std::move(vec1);  // 移动构造函数
    vec2.display();
    vec3.display();
    return 0;
}
  1. 练习7.27:编写一个Game类,包含委托构造函数和拷贝构造函数,实现游戏信息的添加和展示功能。
    • 示例代码
#include <iostream>
#include <string>

class Game {
public:
    Game() : Game("Unknown", "Unknown", 0) {} // 委托构造函数
    Game(const std::string &title, const std::string &developer, int rating) 
        : title(title), developer(developer), rating(rating) {}

    // 拷贝构造函数
    Game(const Game &other) : title(other.title), developer(other.developer), rating(other.rating) {}

    void display() const {
        std::cout << "Title: " << title << ", Developer: " << developer << ", Rating: " << rating << "/10" << std::endl;
    }

private:
    std::string title;
    std::string developer;
    int rating;
};

int main() {
    Game game1("The Witcher 3", "CD Projekt", 10);
    Game game2 = game1;  // 使用拷贝构造函数
    game1.display();
    game2.display();
    return 0;
}
  1. 练习7.28:定义一个Currency类,包含隐式的类类型转换,并实现从double类型到Currency类型的隐式转换。
    • 示例代码
#include <iostream>

class Currency {
public:
    Currency(double amount) : amount(amount) {} // 隐式转换构造函数

    void display() const {
        std::cout << "Amount: $" << amount << std::endl;
    }

private:
    double amount;
};

int main() {
    Currency money = 100.50; // 隐式转换
    money.display();
    return 0;
}
  1. 练习7.29:使用explicit关键字防止隐式转换,并展示从double类型到Currency类型的显式转换。
    • 示例代码
#include <iostream>

class Currency {
public:
    explicit Currency(double amount) : amount(amount) {} // 防止隐式转换

    void display() const {
        std::cout << "Amount: $" << amount << std::endl;
    }

private:
    double amount;
};

int main() {
    // Currency money = 100.50; // 编译错误:不能隐式转换
    Currency money(100.50); // 必须显式转换
    money.display();
    return 0;
}

总结与提高

本节总结

  1. 学习了构造函数初始化列表的概念及其应用,掌握了如何通过初始化列表直接初始化成员变量。
  2. 掌握了默认构造函数和委托构造函数的使用方法,理解了其简化代码的优势。
  3. 理解了拷贝构造函数和移动构造函数的作用,掌握了它们的定义和使用方法。
  4. 学习了隐式的类类型转换的概念,掌握了通过构造函数进行隐式转换的方法,以及使用explicit关键字防止隐式转换的方法。

提高建议

  1. 多练习构造函数的使用:通过编写各种包含默认构造函数、初始化列表、委托构造函数、拷贝构造函数和移动构造函数的类,熟悉这些特性的使用方法。
  2. 深入理解拷贝和移动语义:通过实践掌握拷贝构造函数和移动构造函数的区别及各自的使用场景,避免资源管理问题。
  3. 合理设计构造函数:在编写类时,合理设计构造函数,确保对象初始化的正确性和效率。
  4. 掌握隐式转换与显式转换:通过实践理解隐式转换的风险,掌握使用explicit关键字防止不必要的隐式转换的方法。

7.6 类的静态成员

7.6.1 静态数据成员

静态数据成员是属于类的,而不是属于某个具体对象的成员。所有对象共享同一个静态数据成员,它在类的所有对象之间共享。静态数据成员必须在类的外部进行定义和初始化。

示例代码
#include <iostream>

class Account {
public:
    void deposit(double amount) {
        balance += amount;
        totalDeposits += amount;
    }

    double getBalance() const {
        return balance;
    }

    static double getTotalDeposits() {
        return totalDeposits;
    }

private:
    double balance = 0.0;
    static double totalDeposits;
};

double Account::totalDeposits = 0.0;

int main() {
    Account acc1, acc2;
    acc1.deposit(100);
    acc2.deposit(200);
    std::cout << "Account 1 Balance: " << acc1.getBalance() << std::endl;
    std::cout << "Account 2 Balance: " << acc2.getBalance() << std::endl;
    std::cout << "Total Deposits: " << Account::getTotalDeposits() << std::endl;
    return 0;
}

在这个示例中,totalDeposits是一个静态数据成员,在所有Account对象之间共享。

7.6.2 静态成员函数

静态成员函数不属于某个具体的对象,而是属于类的。静态成员函数只能访问静态数据成员和其他静态成员函数,不能访问非静态数据成员或非静态成员函数。

示例代码
#include <iostream>

class Example {
public:
    static void setValue(int val) {
        value = val;
    }

    static int getValue() {
        return value;
    }

private:
    static int value;
};

int Example::value = 0;

int main() {
    Example::setValue(42);
    std::cout << "Value: " << Example::getValue() << std::endl;
    return 0;
}

在这个示例中,value是一个静态数据成员,setValuegetValue是静态成员函数,它们用于访问和修改静态数据成员。

7.6.3 静态成员的初始化

静态数据成员通常必须在类的外部进行初始化,但在某些情况下,特别是对于const整型和枚举类型的静态数据成员,可以在类内直接进行初始化。

示例代码
#include <iostream>

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

    static int getCount() {
        return count;
    }

private:
    static int count;
    static const int limit = 100;  // 类内初始化
};

int Counter::count = 0;

int main() {
    Counter c1, c2, c3;
    std::cout << "Number of objects created: " << Counter::getCount() << std::endl;
    std::cout << "Limit: " << Counter::limit << std::endl;
    return 0;
}

在这个示例中,count是一个静态数据成员,在类的外部进行初始化,而limit是一个const整型静态数据成员,可以在类内直接进行初始化。

对于非const整型或非枚举类型的静态数据成员,仍然需要在类的外部进行初始化。

7.6.4 静态成员的使用场景

静态成员常用于需要在多个对象之间共享数据或在没有对象的情况下调用的功能。例如:

  1. 计数器:用于统计类的对象创建的数量。
  2. 配置参数:用于存储在多个对象之间共享的配置信息。
  3. 工厂方法:用于创建和管理类的实例。
示例代码(工厂方法)
#include <iostream>
#include <vector>

class Singleton {
public:
    static Singleton* getInstance() {
        if (!instance) {
            instance = new Singleton();
        }
        return instance;
    }

    void display() const {
        std::cout << "Singleton instance" << std::endl;
    }

private:
    Singleton() = default;
    static Singleton* instance;
};

Singleton* Singleton::instance = nullptr;

int main() {
    Singleton* s1 = Singleton::getInstance();
    Singleton* s2 = Singleton::getInstance();
    s1->display();
    s2->display();
    std::cout << "s1 and s2 are " << (s1 == s2 ? "the same" : "different") << " instances." << std::endl;
    return 0;
}

在这个示例中,Singleton类使用静态数据成员和静态成员函数实现了单例模式,确保类的唯一实例。

重点与难点分析

重点

  1. 静态数据成员:理解静态数据成员的概念和用途,掌握静态数据成员的定义和初始化方法。
  2. 静态成员函数:掌握静态成员函数的定义和使用方法,理解静态成员函数的限制。
  3. 静态成员的初始化:理解静态数据成员必须在类的外部初始化的要求。

难点

  1. 静态成员的作用域和生命周期:初学者需要理解静态成员的作用域和生命周期,掌握静态成员在不同对象之间共享数据的特性。
  2. 静态成员函数的限制:静态成员函数不能访问非静态成员,需要掌握如何在静态成员函数中进行适当的设计和实现。

练习题解析

  1. 练习7.30:定义一个Library类,包含静态数据成员totalBooks,并实现统计图书数量的功能。
    • 示例代码
#include <iostream>

class Library {
public:
    Library() {
        ++totalBooks;
    }

    static int getTotalBooks() {
        return totalBooks;
    }

private:
    static int totalBooks;
};

int Library::totalBooks = 0;

int main() {
    Library lib1, lib2, lib3;
    std::cout << "Total books: " << Library::getTotalBooks() << std::endl;
    return 0;
}
  1. 练习7.31:编写一个Database类,包含静态成员函数connect,用于模拟数据库连接。
    • 示例代码
#include <iostream>

class Database {
public:
    static void connect() {
        std::cout << "Connecting to database..." << std::endl;
        ++connections;
    }

    static int getConnections() {
        return connections;
    }

private:
    static int connections;
};

int Database::connections = 0;

int main() {
    Database::connect();
    Database::connect();
    std::cout << "Total connections: " << Database::getConnections() << std::endl;
    return 0;
}
  1. 练习7.32:定义一个Student类,包含静态数据成员totalStudents和静态成员函数getTotalStudents,并实现统计学生数量的功能。
    • 示例代码
#include <iostream>
#include <string>

class Student {
public:
    Student(const std::string &name) : name(name) {
        ++totalStudents;
    }

    static int getTotalStudents() {
        return totalStudents;
    }

private:
    std::string name;
    static int totalStudents;
};

int Student::totalStudents = 0;

int main() {
    Student s1("Alice"), s2("Bob"), s3("Charlie");
    std::cout << "Total students: " << Student::getTotalStudents() << std::endl;
    return 0;
}
  1. 练习7.33:编写一个Logger类,包含静态数据成员logCount和静态成员函数log,用于记录日志信息。
    • 示例代码
#include <iostream>
#include <string>

class Logger {
public:
    static void log(const std::string &message) {
        ++logCount;
        std::cout << "Log #" << logCount << ": " << message << std::endl;
    }

    static int getLogCount() {
        return logCount;
    }

private:
    static int logCount;
};

int Logger::logCount = 0;

int main() {
    Logger::log("Starting the application");
    Logger::log("Performing an operation");
    Logger::log("Shutting down the application");
    std::cout << "Total log entries: " << Logger::getLogCount() << std::endl;
    return 0;
}
  1. 练习7.34:定义一个Configuration类,包含静态数据成员settings和静态成员函数getSetting,用于存储和访问配置参数。
    • 示例代码
#include <iostream>
#include <unordered_map>
#include <string>

class Configuration {
public:
    static void setSetting(const std::string &key, const std::string &value) {
        settings[key] = value;
    }

    static std::string getSetting(const std::string &key) {
        return settings[key];
    }

private:
    static std::unordered_map<std::string, std::string> settings;
};

std::unordered_map<std::string, std::string> Configuration::settings;

int main() {
    Configuration::setSetting("language", "C++");
    Configuration::setSetting("version", "C++11");
    std::cout << "Language: " << Configuration::getSetting("language") << std::endl;
    std::cout << "Version: " << Configuration::getSetting("version") << std::endl;
    return 0;
}

总结与提高

本节总结

  1. 学习了静态数据成员和静态成员函数的定义和使用方法,理解了它们在类中的作用和特性。
  2. 掌握了静态成员的初始化方法,理解了静态数据成员必须在类的外部进行初始化的要求。
  3. 通过示例代码和练习题,理解了静态成员在实际编程中的应用场景和设计方法。

提高建议

  1. 多练习静态成员的定义与使用:通过编写各种包含静态成员的类,熟悉静态成员的初始化和调用方法,掌握其在不同对象之间共享数据的特性。
  2. 深入理解静态成员函数的限制:通过实践掌握静态成员函数不能访问非静态成员的限制,理解在静态成员函数中进行设计和实现的方法。
  3. 合理设计静态成员:在编写类时,合理设计静态成员,确保数据和功能在多个对象之间的共享和管理,提高代码的可维护性和可扩展性。

本主页会定期更新,为了能够及时获得更新,敬请关注我:点击左下角的关注。也可以关注公众号:请在微信上搜索公众号“iShare爱分享”并关注,或者扫描以下公众号二维码关注,以便在内容更新时直接向您推送。 

  • 30
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

iShare_爱分享

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值