当前位置: 首页 > news >正文

《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, bvoid 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::drawdrawShape(rectangle);   // 调用 Rectangle::drawreturn 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 隐式转换为 Complexvoid 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 隐式转换为 Complexvoid 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爱分享”并关注,或者扫描以下公众号二维码关注,以便在内容更新时直接向您推送。 

相关文章:

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

7.1 定义抽象数据类型 7.1.1 类的基本概念 在C中&#xff0c;类是用户定义的类型&#xff0c;提供了一种将数据和操作这些数据的函数&#xff08;成员函数&#xff09;组合在一起的方法。类定义了对象的属性和行为&#xff0c;通过实例化类来创建对象。 7.1.2 定义类 定义类…...

idea intellij 2023打开微服务项目部分module未在左侧项目目录展示(如何重新自动加载所有maven项目model)

项目场景&#xff1a; springcloud微服务项目,部分模块暂时不需要用到&#xff0c;就在pom.xml文件中注释掉相应的模块&#xff0c;突然有一天打开项目&#xff0c;部分项目module 在idea intellij工具左侧文件夹找不到了&#xff0c;重新file->open本地项目也还是部分模块…...

生成视频 zeroscope_v2_576w 学习笔记

目录 生成视频代码&#xff1a; 维度报错&#xff1a; 解决方法&#xff0c;修改代码&#xff1a; 已开源&#xff1a; 视频生成模型 Zeroscope开源 免费无水印 视频生成模型 Zeroscope_v2_576w 开源 - 腾讯云开发者社区-腾讯云 生成视频代码&#xff1a; import torch fro…...

H3C综合实验

实验拓扑 实验要求 1、按照图示配置IP地址 2、sw1和sw2之间的直连链路配置链路聚合 3、 公司内部业务网段为VLAN10和VLAN20; VLAN 10是市场部&#xff0c;vlan20是技术部&#xff0c;要求对VLAN进行命名以便识别&#xff1b;PC1属于vlan10&#xff0c;PC2属于vlan20&#xf…...

QThread 与QObject::moveToThread在UI中的应用

1. QThread的两种用法 第一种用法就是继承QThread&#xff0c;然后覆写 virtual void run()&#xff0c; 这种用法的缺点是不能利用信号槽机制。 第二种用法就是创建一个线程&#xff0c;创建一个对象&#xff0c;再将对象moveToThread, 这种可以充分利用信号槽机制&#xff…...

安卓逆向案例——X酷APP逆向分析

X酷APP逆向分析 这里介绍一下两种不同的挂载证书的方法。 chls.pro/ssl无法在浏览器中下载证书是什么原因解决方法&#xff1a; 法一 1. 挂载系统分区为读写 使用正确的挂载点来挂载系统分区为读写&#xff1a; su mount -o remount,rw /dev/uijISjR/.magisk/block/syste…...

创新案例|星巴克中国市场创新之路: 2025目标9000家店的挑战与策略

星巴克创始人霍华德舒尔茨&#xff1a;“为迎接中国市场的全面消费复苏&#xff0c;星巴克2025年推进9000家门店计划&#xff0c;将外卖、电商以及家享和外出场景咖啡业务纳入中国新一轮增长计划中。”在面临中国市场同店增长大幅下滑29%背景下&#xff0c;星巴克通过DTC用户体…...

计算机网络 MAC地址表管理

一、理论知识 1.MAC地址表&#xff1a;交换机使用MAC地址表来记录各MAC地址对应的端口&#xff0c;用于帧转发的目的。 2.老化机制&#xff1a;交换机会为每一条MAC地址表项设置老化时间&#xff0c;老化时间到期后未收到该MAC地址报文的表项将被删除&#xff0c;释放资源。 …...

【免费API推荐】:各类API资源免费获取【11】

欢迎来到各类API资源的免费获取世界&#xff01;幂简集成为您提供了一个集合了各种免费API接口的平台。无论您是开发者、数据分析师还是创业者&#xff0c;都可以通过我们的平台轻松免费获取所需的API资源。幂简精心整理了各类API接口&#xff0c;涵盖了不同领域的需求&#xf…...

技术驱动会展:展位导航系统的架构与实现

随着会展行业的快速发展&#xff0c;大型会展中心面临着如何提升参展者体验、提高招商效率的挑战。针对客户反馈的展馆面积大、展位查找困难等问题&#xff0c;维小帮提出一套智慧会展导航解决方案&#xff0c;旨在通过先进的室内导航技术提升会展中心的运营效率和参展者的满意…...

适用于轨道交通专用的板卡式网管型工业以太网交换机

是网管型 CompactPCI板卡式冗余环网交换机。前面板带有6个 10/100/1000Base-T(X)M12接口。后面的CPCI接口有 8个10/100/1000Base-T (X) 以太网接口。 是特别为轨道交通行业EN50155标准要求而设计的坚固型交换机。它同时具有以下特性&#xff1a; ● 支持2线以太网距离扩展端口&…...

excel基本操作

excel 若要取消在数据表中进行的所有筛选 步骤操作&#xff1a; 单击“数据”选项卡。在“排序和筛选”组中&#xff0c;找到“清除”按钮。点击“清除”按钮。 图例&#xff1a; 将文本文件的数据导入到Excel工作表中进行数据处理 步骤&#xff1a; 在Excel中&#xff0c…...

C++系统相关操作2 - 获取系统环境变量

1. 关键词2. sysutil.h3. sysutil.cpp4. 测试代码5. 运行结果6. 源码地址 1. 关键词 C 系统调用 环境变量 getenv 跨平台 2. sysutil.h #pragma once#include <cstdint> #include <string>namespace cutl {/*** brief Get an environment variable.** param na…...

适合小白学习的项目1906java Web智慧食堂管理系统idea开发mysql数据库web结构java编程计算机网页源码servlet项目

一、源码特点 java Web智慧食堂管理系统是一套完善的信息管理系统&#xff0c;结合java 开发技术和bootstrap完成本系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。 前段主要技术 bootstra…...

AI通用大模型不及垂直大模型?各有各的好

​​​​​​​AI时代&#xff0c;通用大模型和垂直大模型&#xff0c;两者孰优孰劣&#xff0c;一直众说纷纭。 通用大模型&#xff0c;聚焦基础层&#xff0c;如ChatGPT、百度文心一言&#xff0c;科大讯飞星火大模型等&#xff0c;都归属通用大模型&#xff0c;它们可以解答…...

农产品价格信息系统小程序

一键掌握市场脉动 &#x1f33e; 引言&#xff1a;为何关注农产品价格&#xff1f; 在当今社会&#xff0c;农产品价格的波动直接关系到农民的收入和消费者的生活成本。因此&#xff0c;及时、准确地掌握农产品价格信息&#xff0c;对于农民合理安排生产、消费者做出购买决策都…...

【LLM-多模态】高效多模态大型语言模型综述

一、结论写在前面 模型规模的庞大及训练和推理成本的高昂&#xff0c;限制了MLLMs在学术界和工业界的广泛应用。因此&#xff0c;研究高效轻量级的MLLMs具有巨大潜力&#xff0c;特别是在边缘计算场景中。 论文深入探讨了高效MLLM文献的领域&#xff0c;提供了一个全面的视角…...

ASP .Net Core创建一个httppost请求并添加证书

ASP .Net Core创建一个httppost请求并添加证书 创建.net Core程序&#xff0c;使用自签名证书&#xff0c;可以处理https的get和post请求。 创建证书 创建自签名证书的流程可以在这里查看&#xff1a; https://blog.csdn.net/GoodCooking/article/details/139815278创建完毕…...

Redis入门篇

目录 传送门一、前言二、NoSQL1、ont only sql&#xff0c;特点&#xff1a;2、NoSQL的四大分类&#xff1a; 三、Redis概念四、五大数据类型: 传送门 SpringMVC的源码解析&#xff08;精品&#xff09; Spring6的源码解析&#xff08;精品&#xff09; SpringBoot3框架&#…...

变电站智能巡检机器人解决方案

我国拥有庞大的电网体系&#xff0c;变电站数量众多&#xff0c;且近年来快速增长。然而目前我国变电站巡检方式仍以人工为主&#xff0c;存在效率低下、监控不全面等问题。变电站通常是一个封闭的系统空间&#xff0c;设备种类繁多、占地面积广阔&#xff0c;这对巡检人员实时…...

Linux Kernel入门到精通系列讲解(QEMU-虚拟化篇) 2.5 Qemu实现RTC设备

1. 概述 上一章节起(5.4小节),我们已经把整个Naruto Pi都跑通了,从BL0到kernel再到Rootfs都通了,目前可以说已经具备学习Linux得基础条件,剩下得都只是添砖加瓦,本小节我们将添加RTC,如果你还没有添加RTC,你可以试试不添加RTC时,Linux的时间戳会很奇怪,加了RTC后,…...

【自动驾驶】通过下位机发送的加速度、角速度计算机器人在世界坐标系中的姿态

文章目录 原始代码全局变量定义逆平方根函数四元数解算函数理论解释四元数加速度计数据归一化计算方向余弦矩阵的第三行计算误差计算并应用积分反馈应用比例反馈积分陀螺仪数据,更新四元数归一化四元数更新姿态数据整体流程原始代码 #define SAMPLING_FREQ 20.0f // 采样频率…...

Python 设计模式(第2版) -- 第四部分(其他设计模式)

Python 设计模式(第2版) 最后介绍下其他设计模式。 模型—视图—控制器&#xff08;MVC&#xff09;-- 复合模式 根据 GoF 的定义&#xff0c;“复合模式将两个或更多模式组合成解决常见或普遍性问题的解决方案”。复合模式不是同时使用的一组模式&#xff0c;而是一个问题的…...

gitlab升级16.11.3-ee

背景 这是事后一段时间补充记录的博客。 升级目的&#xff1a;修补漏洞CVE-2024-4835 未经认证的威胁攻击者能够利用该漏洞在跨站脚本 (XSS) 攻击中&#xff0c;轻松接管受害者账户。 gitlab版本为14.6.2-ee升级至16.11.3-ee 思路 翻阅文档找升级方法及升级版本路径。使用…...

剑指offer 算法题(搜索二维矩阵)

剑指offer 第二题 去力扣里测试算法 思路一&#xff1a; 直接暴力遍历二维数组。 class Solution { public:bool searchMatrix(vector<vector<int>>& matrix, int target) {for (unsigned int i{ 0 }; i < matrix.size(); i){for (unsigned int j{ 0 };…...

SaaS平台数据对接为什么要选择API对接?

SaaS平台数据对接是指将一个或多个SaaS平台中的数据集成到其他应用或平台中的过程。在当前的数字化时代&#xff0c;企业越来越倾向于使用SaaS平台来管理他们的业务和数据。然而&#xff0c;这些数据通常散布在不同的SaaS平台中&#xff0c;这对于企业数据的整合和分析来说可能…...

力扣136. 只出现一次的数字

Problem: 136. 只出现一次的数字 文章目录 题目描述思路复杂度Code 题目描述 思路 由于题目要求使用线性时间复杂度和常量级的空间复杂度&#xff0c;再加上找重复元素这个特性&#xff0c;我们可以想到使用位运算来求解&#xff1a; 1.任何数与其本身异或得0&#xff0c;任何…...

重学java 74.Lombok的使用

少点心气&#xff0c;多点干劲 —— 24.6.18 一、lombok的安装使用 1.作用: 简化javabean开发 2.使用: a.下插件 ->如果是idea2022不用下载了,自带 b.导lombok的jar包 安装教程&#xff1a; http://t.csdnimg.cn/wq9MM c.修改设置 二、lombok的介绍 Lombok通过增加一…...

数据结构6---树

一、定义 树(Tree)是n(n>0)个结点的有限集。当n0时成为空树,在任意一棵非空树中: 1、有且仅有一个特定的称为根(Root)的结点; 2、当n>1时,其余结点可分为m(m>日)个互不相交的有限集T1、T2、...、 Tm&#xff0c;其中每一个集合本身又是一棵树&#xff0c;并且称为根的…...

一键制作,打造高质量的数字刊物

随着数字化时代的到来&#xff0c;数字刊物已经成为信息传播的重要载体。它以便捷、环保、互动性强等特点&#xff0c;受到了越来越多人的青睐。然而&#xff0c;如何快速、高效地制作出高质量的数字刊物&#xff0c;成为许多创作者面临的难题。今天&#xff0c;教大家一个制作…...

Java面试题:对比继承Thread类和实现Runnable接口两种创建线程的方法,以及它们的优缺点

Java 中创建线程有两种主要的方法&#xff1a;继承 Thread 类和实现 Runnable 接口。下面我将分别介绍这两种方法&#xff0c;并对比它们的优缺点。 继承 Thread 类 方法&#xff1a; 创建一个继承自 Thread 的子类。重写 Thread 类的 run 方法。创建子类的实例并调用 start…...

编译原理-各章典型题型+思路求解

第2章文法和语言习题 基础知识&#xff1a; 思路&#xff1a; 基础知识&#xff1a; 思路&#xff1a; 基础知识&#xff1a; 编译原理之 短语&直接短语&句柄 定义与区分_编译原理短语,直接短语,句柄-CSDN博客 思路&#xff1a; 题目&#xff1a; 基础解释&#xff1a…...

【绝对有用】C++ vector排序

在 C 中&#xff0c;有多种方法可以对向量&#xff08;即 std::vector&#xff09;进行排序。最常用的方法是使用标准库中的 std::sort 函数。以下是一些例子&#xff1a; 使用 std::sort 函数 std::sort 函数是标准库 <algorithm> 中的一个函数&#xff0c;可以对向量…...

linux——VScode安装

方法一&#xff1a;使用snap一键安装 Snap Store 是 Ubuntu、Debian、Fedora 和其他几个 Linux 发行版中的一个应用商店&#xff0c;提供了数千个应用程序和工具的安装。Snap Store 使用 Snap 包格式&#xff0c;这是一种通用的 Linux 软件包格式&#xff0c;使得在不同的 Lin…...

X-LoRA:高效微调 LoRA 系列,实现不同领域知识专家混合模型

&#x1f4dc; 文献卡 X-LoRA: Mixture of Low-Rank Adapter Experts, a Flexible Framework for Large Language Models with Applications in Protein Mechanics and Molecular Design作者: Eric L. Buehler; Markus J. BuehlerDOI: 10.48550/arXiv.2402.07148摘要:We report…...

基于卷积神经网络的目标检测

卷积神经网络基础知识 1.什么是filter 通常一个6x6的灰度图像&#xff0c;构造一个3*3的矩阵&#xff0c;在卷积神经网络中称之为filter,对&#xff16;x6的图像进行卷积运算。 2.什么是padding 假设输出图像大小为nn与过滤器大小为ff&#xff0c;输出图像大小则为(n−f1)∗(…...

Mysqld数据库管理

一.Mysqld数据库类型 常用的数据类型 int 整型 无符号[0-4294967296&#xff08;2的32次方&#xff09;-1]&#xff0c;有符号[-2147483648&#xff08;2的31次方&#xff09;-2147483647]float单精度浮点 4字节32位double双精度浮点 8字节64位char固定长度的字符类型…...

Wifi通信协议:WEP,WPA,WPA2,WPA3,WPS

前言 无线安全性是保护互联网安全的重要因素。连接到安全性低的无线网络可能会带来安全风险&#xff0c;包括数据泄露、账号被盗以及恶意软件的安装。因此&#xff0c;利用合适的Wi-Fi安全措施是非常重要的&#xff0c;了解WEP、WPA、WPA2和WPA3等各种无线加密标准的区别也是至…...

开源【汇总】

开源【汇总】 前言版权推荐开源【汇总】最后 前言 先占个位 2024-6-21 21:29:33 以下内容源自《【创作模板】》 仅供学习交流使用 版权 禁止其他平台发布时删除以下此话 本文首次发布于CSDN平台 作者是CSDN日星月云 博客主页是https://jsss-1.blog.csdn.net 禁止其他平台发…...

英文字母表

目录 一 设计原型 二 后台源码 一 设计原型 二 后台源码 namespace 英文字母表 {public partial class Form1 : Form{public Form1(){InitializeComponent();}private void Form1_Load(object sender, EventArgs e){foreach (var item in panel1.Controls){if (item ! null)…...

Redis缓存穿透

缓存穿透&#xff1a; 查询一个不存在的数据&#xff0c;mysql查询不到数据也不会直接写入缓存&#xff0c;就会导致每次请求都查数据库。 方法一&#xff1a; 方法二&#xff1a; 布隆过滤器&#xff1a; 简单来说就是一个二进制数组&#xff0c;用0和1来判断数组中是否存在…...

SHELL脚本学习(十一)正则表达式

一、锚点字符 1.1 锚点行首 脱字符(^)指出行首位置 $ cat < file1 test line1 test line2 test line3 line4 test#打印所有包括文本 test的行 $ sed -n /test/p file1 test line1 test line2 test line3 line4 test#打印所有以test为首的行 $ sed -n /^test/p file1 test…...

Leetcode Java学习记录——代码随想录哈希表篇

文章目录 哈希表几种哈希实现 Java数组HashSetmap方法charAt()toCharArray()for 遍历长度 哈希表 当需要快速判断一个元素是否出现在集合里的时候&#xff0c;就要用到哈希表。 无限循环就意味着重复出现。 几种哈希实现 数组&#xff1a;大小固定set&#xff1a;只存keymap…...

我又挖到宝了!小米、352、希喂宠物空气净化器除毛能力PK

养宠家庭常常因为猫咪们掉毛的问题烦恼。无论是短毛猫还是长毛猫&#xff0c;它们的毛发总是无处不在&#xff0c;从沙发到地毯&#xff0c;从床铺到衣物&#xff0c;甚至飘散在空气中。其中最难清理的就是飘浮在空气中的浮毛&#xff0c;最让人担心的是&#xff0c;空气中的浮…...

每月 GitHub 探索|10 款引领科技趋势的开源项目

1.IT-Tools 仓库名称&#xff1a; CorentinTh/it-tools 截止发稿星数: 16842 (近一个月新增:5744) 仓库语言: Vue 仓库开源协议&#xff1a; GNU General Public License v3.0 引言 CorentinTh/it-tools 是一个开源项目&#xff0c;提供各种对开发者友好的在线工具&#xff0…...

【如何让新增的Android.mk参与编译】

步骤1&#xff1a; 你需要在你新增的Android.mk目录以上的位置找一个已有的Android.mk 步骤2&#xff1a; 在原本已有的Android.mk中加入&#xff1a; //这是你新增的Android.mk文件的路径 include $(LOCAL_PATH)/xxx/xxx/Android.mk如果有些多可以这样写 //dir1 dir2是你新…...

【windows|009】计算机网络基础知识

&#x1f341;博主简介&#xff1a; &#x1f3c5;云计算领域优质创作者 &#x1f3c5;2022年CSDN新星计划python赛道第一名 &#x1f3c5;2022年CSDN原力计划优质作者 ​ &#x1f3c5;阿里云ACE认证高级工程师 ​ &#x1f3c5;阿里云开发者社区专家博主 &#x1f48a;交流社…...

C语言循环中获取之前变量的值

获取上个数组变量的值 #include <stdio.h> #include <string.h>enum { GG, DD }; int main() {int bi[] {0, 0};int bi_s1[] {0, 0};for (int i 0; i < 5; i) {memcpy(bi_s1, bi, sizeof(bi));bi[GG] i * 3;bi[DD] i * 2;printf("bigg %d, bigg_s1 …...

must be built with the ios 17 sdk or later,included in Xcode 15 or later.

2024.4.29 号开始&#xff0c;苹果又开始搞开发者了。 Xcode - 支持 - Apple Developer xcode可以从这里下载&#xff0c; Sign In - Apple 电脑不支持&#xff0c;头疼&#xff0c;必须 macOS Ventura 13.5 或以上才能支持。 电脑哪里搞&#xff0c;再买一台吗&#xff1f; 用…...

Unity2D计算两个物体的距离

1.首先新建一个场景并添加2个物体 2.创建一个脚本并编写代码 using UnityEngine;public class text2: MonoBehaviour {public GameObject gameObject1; // 第一个物体public GameObject gameObject2; // 第二个物体void Update(){// 计算两个物体之间的距离float distance Vec…...