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

news2024/11/25 20:47:14

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爱分享”并关注,或者扫描以下公众号二维码关注,以便在内容更新时直接向您推送。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1846724.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ru俄罗斯域名如何申请SSL证书?

我们日常看到的都是com这种国际域名比较普遍&#xff0c;尤其是主流网站&#xff0c;主要原因考虑的其通用性&#xff0c;那么对于地方性的域名大家很少看到&#xff0c;比如俄罗斯国家域名.ru大家还是有些陌生的&#xff0c;但要说中国.CN域名那你就很熟悉了。 有用户在申请过…

Anthropic AI模型Claude 3.5 Sonnet在Amazon Bedrock上正式可用

Claude 3.5 Sonnet是Anthropic最先进的Claude系列AI模型的新成员&#xff0c;比Claude 3 Opus更智能且价格只有其五分之一 北京——2024年6月21日 亚马逊云科技宣布&#xff0c;Anthropic最新、最强大的模型Claude 3.5 Sonnet现已在Amazon Bedrock上正式可用&#xff0c;该模型…

Syslog日志外发

Syslog是一种广泛应用于网络设备、操作系统和应用程序的日志通信协议&#xff0c;通过收集、监控和分析Syslog日志&#xff0c;企业可以有效维护网络安全、故障排除和运营管理。 除了内部监控&#xff0c;有时企业也需要将Syslog日志外发以实现更多的管理和合规需求。在实现Sy…

C#委托:事件驱动编程的基石

目录 了解委托 委托使用的基本步骤 声明委托(定义一个函数的原型&#xff1a;返回值 参数类型和个数&#xff09; 根据委托定义的函数原型编写需要的方法 创建委托对象&#xff0c;关联“具体方法” 通过委托调用方法&#xff0c;而不是直接使用方法 委托对象所关联的方…

YOLOv8改进 | FPN | 新型上采样算子CARAFE【全网独家】

&#x1f4a1;&#x1f4a1;&#x1f4a1;本专栏所有程序均经过测试&#xff0c;可成功执行&#x1f4a1;&#x1f4a1;&#x1f4a1; 专栏目录 &#xff1a;《YOLOv8改进有效涨点》专栏介绍 & 专栏目录 | 目前已有40篇内容&#xff0c;内含各种Head检测头、损失函数Loss、…

构建个人文件上传服务:Python Flask实现上传和下载完整指南

介绍 在本教程中&#xff0c;我们将学习如何使用Python Flask框架将文件上传到服务器&#xff0c;并使用SQLite数据库来跟踪上传的文件。我们将提供后端代码和一个示例项目的Git链接&#xff0c;以便您可以轻松地跟随本教程。 准备工作 首先&#xff0c;您需要安装Python和F…

揭秘无局放电源:定义、工作原理及其在工业设备中的重要性

当代社会对电源安全性&#xff0c;精密性要求越来也高&#xff0c;对电源设备的需求也越来越高。无局放电源作为电源设备中的一种&#xff0c;由于其独特的优点&#xff0c;越来越受到各行各业的关注。目前&#xff0c;无局放电源在全球范围内得到了广泛应用&#xff0c;尤其是…

springboot特殊问题处理2——springboot集成flowable实现工作流程的完整教程(一)

在实际项目开发过程中&#xff0c;流程相关的业务实现采用工作流会异常清晰明了&#xff0c;但是Activity学习成本和开发难度对追求效率的开发工作者来说异常繁琐&#xff0c;但是作为Activity的亲儿子之一的flowable&#xff0c;其轻量化的使用和对应的api会让开发者感受简单&…

今日分享丨浅谈RAG的技术特点及优势

一、引言 随着AI技术的发展&#xff0c;我们见证了人工智能在理解和处理自然语言方面取得的巨大进步。特别是结合了检索增强生成&#xff08;Retrieval-Augmented Generation, RAG&#xff09;和大语言模型&#xff08;Large Language Model, LLM&#xff09;的系统&#xff0…

华企网安技术博弈:白帽子团队如何破解网赌网站

在数字化时代&#xff0c;網賭作为一种新型犯罪形式&#xff0c;其隐蔽性和跨国性给执法机关带来了前所未有的挑战。一批专业的网络安全团队——白帽子&#xff0c;正利用他们的专业技能与犯罪分子进行技术博弈&#xff0c;有效地破解網賭网站&#xff0c;为打击網賭犯罪贡献力…

银河麒麟V10安装docker和docker-compose

1. 说明 系统镜像使用的是Kylin-Server-V10-SP3-2403-Release-20240426-x86_64.iso如果是在VMware中安装这个系统&#xff0c;需选择Ubuntu&#xff0c;如果选Centos会有问题。 尝试使用在线方式安装docker&#xff0c;报了很多错误&#xff0c;比较麻烦&#xff0c;建议使用离…

Bilibili开源发布轻量级 Index 系列语言模型:2.8T 训练数据,支持角色扮演

Bilibili首次发布 Index 系列模型中的轻量版本&#xff1a;Index-1.9B 系列 本次开源的 Index-1.9B系列包含以下模型&#xff1a; Index-1.9Bbase:基座模型&#xff0c;具有 19 亿 非词嵌入参数量&#xff0c;在 2.8T 中英文为主的语料上预训练&#xff0c;多个评测基准上与…

Marin说PCB之电源完整性PDN--Murata三端子电容--01

不知道诸位老铁们有没有忘记我们常用的电容的一些特性&#xff0c;例如在其谐振频率左边&#xff0c;电容主要呈容性&#xff0c;在谐振频率右边&#xff0c;电容主要呈感性。我随便找了一个电容&#xff1a;GRT188C80J226ME13&#xff0c;其频率特性图如下所示&#xff1a; 我…

Qt creator day5练习

Qt 中实现TCP 聊天服务器 大致流程 创建套接字服务器QTcpServer对象 通过QTcpServer对象设置监听&#xff0c;即QTcpServer&#xff1a;&#xff1a;listen&#xff08;&#xff09; 基于QTcpServer&#xff1a;&#xff1a;newConnection&#xff08;&#xff09;信号检测…

大模型面试题目精选分享

前言 掌握大模型面试关键&#xff0c;我精选了一系列面试题目。从模型理解到应用实践&#xff0c;全方位考察大模型知识。无论你是初学者还是资深专家&#xff0c;这里都有适合你的题目。快来挑战自己&#xff0c;提升面试技能吧&#xff01; 面试题笔记分享 为了助力朋友们…

日常工作中常用的管理工具

日常工作中常用的管理工具 SWOT分析法&#xff1a; 帮你清晰地把我全局&#xff0c;分析自己在资源方面的优势域劣势&#xff0c;把握环境提供的机会&#xff0c;防范可能存在的风险与威胁&#xff0c;对我们的成功有非常重要的意义 PDCA循环规则&#xff1a; 每一项工作&#…

RPM 打包入门(基于openEuler)

主要参考内容&#xff08;均为官方文档&#xff09;&#xff1a; https://rpm-packaging-guide.github.io/#building-rpms https://docs.redhat.com/zh_hans/documentation/red_hat_enterprise_linux/8/html/packaging_and_distributing_software/introduction-to-rpm_packagin…

用Python设置Excel工作表网格线的隐藏与显示

Excel表格界面的直观性很大程度上得益于表格中的网格线设计&#xff0c;这些线条帮助用户精确对齐数据&#xff0c;清晰划分单元格。网格线是Excel界面中默认显示的辅助线&#xff0c;用于辅助定位&#xff0c;与单元格边框不痛&#xff0c;不影响打印输出。然而&#xff0c;在…

Spring的启动扩展点机制详解

在Java的世界中&#xff0c;我们知道Spring是当下最主流的开发框架&#xff0c;没有之一。而在使用Dubbo、Mybatis等开源框架时&#xff0c;我们发现可以采用和Spring完全一样的使用方式来使用它们。 可能你在平时的使用过程中并没有意识到这一点&#xff0c;但仔细想一想&…

34 张动图:线缆,以太网与网络寻址

以太网 本地主机访服务器 以太网标准 非屏蔽双绞线&#xff08;UTP&#xff09; 电磁干扰 串扰 双绞线 真实的双绞线 线缆与引脚对齐方式 科普&#xff1a;网线每根芯的作用 引脚连接 数据传输与接收 主机直连到交换机 主机直连到主机&#xff08;或交换器&#xff09; Auto M…