《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和成员函数setName及getName。
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和成员函数setTitle及getTitle。
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类,包含数据成员brand和year,并通过构造函数初始化这些成员。
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和成员函数setName及getName。
重点与难点分析
重点:
- 类的定义:掌握类的基本结构和定义方法,理解类的成员变量和成员函数的声明与定义。
- 构造函数:理解构造函数的作用和定义方法,掌握构造函数的初始化列表的使用。
- 类的接口和实现:理解类的接口和实现的概念,掌握将类的接口与实现分离的方法。
难点:
- 成员函数的定义:初学者需要通过实践掌握成员函数在类外部定义的方法,理解作用域运算符
::的使用。 - 构造函数的初始化列表:掌握构造函数初始化列表的语法和使用方法,理解其在类对象初始化中的作用。
练习题解析
- 练习7.1:定义一个
Student类,包含数据成员name和age,以及相应的构造函数和成员函数。
-
- 示例代码:
#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;
}
- 练习7.2:编写一个
Rectangle类,包含成员函数area和perimeter,用于计算矩形的面积和周长。
-
- 示例代码:
#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;
}
- 练习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;
}
- 练习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;}
总结与提高
本节总结:
- 学习了类的定义和基本概念,掌握了成员函数和数据成员的声明与定义方法。
- 理解了构造函数的作用和定义方法,掌握了构造函数初始化列表的使用。
- 通过示例代码和练习题,加深了对类的接口和实现的理解和应用。
提高建议:
- 多练习类的定义与使用:通过编写各种包含类的程序,熟悉类的定义和使用方法,提高代码的组织性和可读性。
- 深入理解构造函数:通过实践掌握构造函数的初始化列表和重载构造函数的方法,理解其在对象初始化中的作用。
- 封装类的接口与实现:在编写类时,合理设计类的接口与实现,提高代码的可维护性和安全性。
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类的私有成员。
重点与难点分析
重点:
- 访问控制:掌握
public、protected和private访问控制的用法和区别,理解其在类中的应用。 - 封装:理解封装的概念,掌握通过公有成员函数访问私有数据成员的方法。
- 友元:了解友元函数和友元类的定义和用法,理解其在访问私有和受保护成员中的作用。
难点:
- 友元的使用:初学者需要通过实践掌握友元的定义和使用方法,理解友元关系的非传递性和不可继承性。
- 封装的实现:通过实践理解封装的概念,掌握在类中实现封装的方法,提高代码的安全性和可维护性。
练习题解析
- 练习7.5:定义一个
Laptop类,包含私有数据成员brand和price,并实现公有成员函数设置和获取这些成员的值。
-
- 示例代码:
#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;
}
- 练习7.6:编写一个
Box类,包含私有数据成员length、width和height,并实现友元函数计算盒子的体积。
-
- 示例代码:
#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;
}
- 练习7.7:定义一个
Library类,包含私有数据成员name和books(书籍数量),并实现友元类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;
}
- 练习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;
}
总结与提高
本节总结:
- 学习了访问控制的基本概念,掌握了
public、protected和private访问控制的使用方法。 - 理解了封装的概念,掌握了通过公有成员函数访问私有数据成员的方法,提高了代码的安全性和可维护性。
- 通过示例代码和练习题,理解了友元函数和友元类的定义和使用方法,掌握了友元在访问私有和受保护成员中的作用。
提高建议:
- 多练习访问控制的使用:通过编写各种包含不同访问控制级别的类,熟悉
public、protected和private的使用方法,理解其在类设计中的作用。 - 深入理解封装的实现:通过实践掌握封装的概念,合理设计类的接口与实现,提高代码的安全性和可维护性。
- 掌握友元的使用:在编写复杂类时,合理使用友元函数和友元类,提高类之间的协作性和灵活性。
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;
}
在这个示例中,初始化列表用于初始化name和age成员。
成员初始化顺序
成员初始化的顺序按照它们在类中声明的顺序进行,而不是在初始化列表中的顺序。因此,确保在编写初始化列表时遵循声明顺序。
示例代码
#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;
}
尽管初始化列表中b在a之前,实际初始化顺序仍然是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;
}
在这个示例中,setName和setAge函数返回*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类的成员函数design是Building类的友元,可以访问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;
}
在这个示例中,B是A的友元类,可以访问A的私有成员,但继承自B的C不能继承友元关系。
重点与难点分析
重点:
- 类成员再探:掌握初始化列表、成员初始化顺序和常量成员的定义和使用方法。
- 返回
*this的成员函数:理解返回*this的成员函数的用途,掌握链式调用的实现方法。 - 友元再探:理解类之间的友元关系、成员函数作为友元、函数重载和友元、友元声明和作用域的概念和应用。
难点:
- 成员初始化顺序:初学者需要理解成员初始化顺序的重要性,避免初始化列表中的顺序与成员声明顺序不一致。
- 友元的使用:掌握友元函数和友元类的使用方法,理解其在类设计中的作用,避免滥用友元导致类的耦合度过高。
练习题解析
- 练习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;
}
- 练习7.15:编写一个
Book类,包含返回*this的成员函数setTitle和setAuthor,实现链式调用。
-
- 示例代码:
#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;
}
- 练习7.16:定义两个类
Point和Circle,建立它们之间的友元关系,使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 ¢er) {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;
}
- 练习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;
}
- 练习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;
}
总结与提高
本节总结:
- 学习了类成员的高级特性,包括初始化列表、成员初始化顺序和常量成员。
- 掌握了返回
*this的成员函数的定义和使用,理解了链式调用的实现方法。 - 通过友元再探,理解了类之间的友元关系、成员函数作为友元、函数重载和友元、友元声明和作用域的概念和应用。
提高建议:
- 多练习初始化列表和成员初始化顺序:通过编写各种包含初始化列表和成员初始化顺序的类,熟悉这些特性的使用方法。
- 深入理解友元的使用:通过实践掌握友元函数和友元类的使用方法,理解其在类设计中的作用,避免滥用友元导致类的耦合度过高。
- 掌握返回
*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命名空间中,其名字作用域被限制在命名空间内。
重点与难点分析
重点:
- 类作用域:理解类成员名字的作用域,掌握类外部定义成员函数的方法。
- 嵌套类:理解嵌套类的作用域和访问规则,掌握嵌套类的定义和使用。
- 名字查找规则:掌握类作用域中的名字查找规则,理解显式作用域指定的用法。
- 命名空间作用域:理解类的命名空间作用域,掌握在命名空间中定义类的方法。
难点:
- 名字查找规则:初学者需要通过实践掌握名字查找规则,避免名字冲突和作用域错误。
- 嵌套类的访问规则:理解嵌套类与外部类之间的访问规则,避免访问权限错误。
练习题解析
- 练习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;
}
- 练习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;
}
- 练习7.21:定义一个
Shape类及其派生类Circle和Rectangle,并展示多态性的名字查找规则。
-
- 示例代码:
#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;
}
- 练习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;
}
- 练习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;
}
总结与提高
本节总结:
- 学习了类作用域的基本概念,理解了成员名字的作用域和类外部定义成员函数的方法。
- 掌握了嵌套类的定义和使用,理解了嵌套类的作用域和访问规则。
- 理解了类作用域中的名字查找规则,掌握了显式作用域指定的用法。
- 理解了类的命名空间作用域,掌握了在命名空间中定义类的方法。
提高建议:
- 多练习类作用域和名字查找:通过编写各种包含复杂作用域的类,熟悉名字查找规则和显式作用域指定的方法。
- 深入理解嵌套类的访问规则:通过实践掌握嵌套类与外部类之间的访问规则,提高类的设计能力。
- 掌握命名空间作用域:在编写大型程序时,合理使用命名空间,避免名字冲突,提高代码的可读性和可维护性。
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类的构造函数使用初始化列表直接初始化width和height成员。
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关键字防止了从double到Complex的隐式转换,必须显式调用构造函数。
重点与难点分析
重点:
- 构造函数初始化列表:掌握构造函数初始化列表的语法和使用方法,理解其优点。
- 默认构造函数:理解默认构造函数的概念及其生成规则,掌握自定义默认构造函数的方法。
- 委托构造函数:理解委托构造函数的概念,掌握其简化代码的方法。
- 拷贝构造函数:理解拷贝构造函数的作用,掌握其定义和使用方法。
- 移动构造函数:理解移动构造函数的概念,掌握其在资源转移中的作用及使用方法。
- 隐式的类类型转换:理解隐式类类型转换的概念,掌握通过构造函数进行隐式转换的方法,以及使用
explicit关键字防止隐式转换。
难点:
- 初始化列表与成员初始化顺序:初学者需要通过实践掌握初始化列表的使用,并注意成员初始化顺序。
- 拷贝构造函数与移动构造函数的区别:理解这两者的区别及各自的使用场景,避免资源管理问题。
- 隐式转换与显式转换:理解隐式转换的风险,掌握使用
explicit关键字防止不必要的隐式转换。
练习题解析
- 练习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;
}
- 练习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;
}
- 练习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;
}
- 练习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;
}
- 练习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;
}
- 练习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;
}
总结与提高
本节总结:
- 学习了构造函数初始化列表的概念及其应用,掌握了如何通过初始化列表直接初始化成员变量。
- 掌握了默认构造函数和委托构造函数的使用方法,理解了其简化代码的优势。
- 理解了拷贝构造函数和移动构造函数的作用,掌握了它们的定义和使用方法。
- 学习了隐式的类类型转换的概念,掌握了通过构造函数进行隐式转换的方法,以及使用
explicit关键字防止隐式转换的方法。
提高建议:
- 多练习构造函数的使用:通过编写各种包含默认构造函数、初始化列表、委托构造函数、拷贝构造函数和移动构造函数的类,熟悉这些特性的使用方法。
- 深入理解拷贝和移动语义:通过实践掌握拷贝构造函数和移动构造函数的区别及各自的使用场景,避免资源管理问题。
- 合理设计构造函数:在编写类时,合理设计构造函数,确保对象初始化的正确性和效率。
- 掌握隐式转换与显式转换:通过实践理解隐式转换的风险,掌握使用
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是一个静态数据成员,setValue和getValue是静态成员函数,它们用于访问和修改静态数据成员。
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 静态成员的使用场景
静态成员常用于需要在多个对象之间共享数据或在没有对象的情况下调用的功能。例如:
- 计数器:用于统计类的对象创建的数量。
- 配置参数:用于存储在多个对象之间共享的配置信息。
- 工厂方法:用于创建和管理类的实例。
示例代码(工厂方法)
#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类使用静态数据成员和静态成员函数实现了单例模式,确保类的唯一实例。
重点与难点分析
重点:
- 静态数据成员:理解静态数据成员的概念和用途,掌握静态数据成员的定义和初始化方法。
- 静态成员函数:掌握静态成员函数的定义和使用方法,理解静态成员函数的限制。
- 静态成员的初始化:理解静态数据成员必须在类的外部初始化的要求。
难点:
- 静态成员的作用域和生命周期:初学者需要理解静态成员的作用域和生命周期,掌握静态成员在不同对象之间共享数据的特性。
- 静态成员函数的限制:静态成员函数不能访问非静态成员,需要掌握如何在静态成员函数中进行适当的设计和实现。
练习题解析
- 练习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;
}
- 练习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;
}
- 练习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;
}
- 练习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;
}
- 练习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;
}
总结与提高
本节总结:
- 学习了静态数据成员和静态成员函数的定义和使用方法,理解了它们在类中的作用和特性。
- 掌握了静态成员的初始化方法,理解了静态数据成员必须在类的外部进行初始化的要求。
- 通过示例代码和练习题,理解了静态成员在实际编程中的应用场景和设计方法。
提高建议:
- 多练习静态成员的定义与使用:通过编写各种包含静态成员的类,熟悉静态成员的初始化和调用方法,掌握其在不同对象之间共享数据的特性。
- 深入理解静态成员函数的限制:通过实践掌握静态成员函数不能访问非静态成员的限制,理解在静态成员函数中进行设计和实现的方法。
- 合理设计静态成员:在编写类时,合理设计静态成员,确保数据和功能在多个对象之间的共享和管理,提高代码的可维护性和可扩展性。
本主页会定期更新,为了能够及时获得更新,敬请关注我:点击左下角的关注。也可以关注公众号:请在微信上搜索公众号“iShare爱分享”并关注,或者扫描以下公众号二维码关注,以便在内容更新时直接向您推送。
相关文章:
《C++ Primer》导学系列:第 7 章 - 类
7.1 定义抽象数据类型 7.1.1 类的基本概念 在C中,类是用户定义的类型,提供了一种将数据和操作这些数据的函数(成员函数)组合在一起的方法。类定义了对象的属性和行为,通过实例化类来创建对象。 7.1.2 定义类 定义类…...
idea intellij 2023打开微服务项目部分module未在左侧项目目录展示(如何重新自动加载所有maven项目model)
项目场景: springcloud微服务项目,部分模块暂时不需要用到,就在pom.xml文件中注释掉相应的模块,突然有一天打开项目,部分项目module 在idea intellij工具左侧文件夹找不到了,重新file->open本地项目也还是部分模块…...
生成视频 zeroscope_v2_576w 学习笔记
目录 生成视频代码: 维度报错: 解决方法,修改代码: 已开源: 视频生成模型 Zeroscope开源 免费无水印 视频生成模型 Zeroscope_v2_576w 开源 - 腾讯云开发者社区-腾讯云 生成视频代码: import torch fro…...
H3C综合实验
实验拓扑 实验要求 1、按照图示配置IP地址 2、sw1和sw2之间的直连链路配置链路聚合 3、 公司内部业务网段为VLAN10和VLAN20; VLAN 10是市场部,vlan20是技术部,要求对VLAN进行命名以便识别;PC1属于vlan10,PC2属于vlan20…...
QThread 与QObject::moveToThread在UI中的应用
1. QThread的两种用法 第一种用法就是继承QThread,然后覆写 virtual void run(), 这种用法的缺点是不能利用信号槽机制。 第二种用法就是创建一个线程,创建一个对象,再将对象moveToThread, 这种可以充分利用信号槽机制ÿ…...
安卓逆向案例——X酷APP逆向分析
X酷APP逆向分析 这里介绍一下两种不同的挂载证书的方法。 chls.pro/ssl无法在浏览器中下载证书是什么原因解决方法: 法一 1. 挂载系统分区为读写 使用正确的挂载点来挂载系统分区为读写: su mount -o remount,rw /dev/uijISjR/.magisk/block/syste…...
创新案例|星巴克中国市场创新之路: 2025目标9000家店的挑战与策略
星巴克创始人霍华德舒尔茨:“为迎接中国市场的全面消费复苏,星巴克2025年推进9000家门店计划,将外卖、电商以及家享和外出场景咖啡业务纳入中国新一轮增长计划中。”在面临中国市场同店增长大幅下滑29%背景下,星巴克通过DTC用户体…...
计算机网络 MAC地址表管理
一、理论知识 1.MAC地址表:交换机使用MAC地址表来记录各MAC地址对应的端口,用于帧转发的目的。 2.老化机制:交换机会为每一条MAC地址表项设置老化时间,老化时间到期后未收到该MAC地址报文的表项将被删除,释放资源。 …...
【免费API推荐】:各类API资源免费获取【11】
欢迎来到各类API资源的免费获取世界!幂简集成为您提供了一个集合了各种免费API接口的平台。无论您是开发者、数据分析师还是创业者,都可以通过我们的平台轻松免费获取所需的API资源。幂简精心整理了各类API接口,涵盖了不同领域的需求…...
技术驱动会展:展位导航系统的架构与实现
随着会展行业的快速发展,大型会展中心面临着如何提升参展者体验、提高招商效率的挑战。针对客户反馈的展馆面积大、展位查找困难等问题,维小帮提出一套智慧会展导航解决方案,旨在通过先进的室内导航技术提升会展中心的运营效率和参展者的满意…...
适用于轨道交通专用的板卡式网管型工业以太网交换机
是网管型 CompactPCI板卡式冗余环网交换机。前面板带有6个 10/100/1000Base-T(X)M12接口。后面的CPCI接口有 8个10/100/1000Base-T (X) 以太网接口。 是特别为轨道交通行业EN50155标准要求而设计的坚固型交换机。它同时具有以下特性: ● 支持2线以太网距离扩展端口&…...
excel基本操作
excel 若要取消在数据表中进行的所有筛选 步骤操作: 单击“数据”选项卡。在“排序和筛选”组中,找到“清除”按钮。点击“清除”按钮。 图例: 将文本文件的数据导入到Excel工作表中进行数据处理 步骤: 在Excel中,…...
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智慧食堂管理系统是一套完善的信息管理系统,结合java 开发技术和bootstrap完成本系统,对理解JSP java编程开发语言有帮助,系统具有完整的源代码和数据库,系统主要采用B/S模式开发。 前段主要技术 bootstra…...
AI通用大模型不及垂直大模型?各有各的好
AI时代,通用大模型和垂直大模型,两者孰优孰劣,一直众说纷纭。 通用大模型,聚焦基础层,如ChatGPT、百度文心一言,科大讯飞星火大模型等,都归属通用大模型,它们可以解答…...
农产品价格信息系统小程序
一键掌握市场脉动 🌾 引言:为何关注农产品价格? 在当今社会,农产品价格的波动直接关系到农民的收入和消费者的生活成本。因此,及时、准确地掌握农产品价格信息,对于农民合理安排生产、消费者做出购买决策都…...
【LLM-多模态】高效多模态大型语言模型综述
一、结论写在前面 模型规模的庞大及训练和推理成本的高昂,限制了MLLMs在学术界和工业界的广泛应用。因此,研究高效轻量级的MLLMs具有巨大潜力,特别是在边缘计算场景中。 论文深入探讨了高效MLLM文献的领域,提供了一个全面的视角…...
ASP .Net Core创建一个httppost请求并添加证书
ASP .Net Core创建一个httppost请求并添加证书 创建.net Core程序,使用自签名证书,可以处理https的get和post请求。 创建证书 创建自签名证书的流程可以在这里查看: https://blog.csdn.net/GoodCooking/article/details/139815278创建完毕…...
Redis入门篇
目录 传送门一、前言二、NoSQL1、ont only sql,特点:2、NoSQL的四大分类: 三、Redis概念四、五大数据类型: 传送门 SpringMVC的源码解析(精品) Spring6的源码解析(精品) SpringBoot3框架&#…...
变电站智能巡检机器人解决方案
我国拥有庞大的电网体系,变电站数量众多,且近年来快速增长。然而目前我国变电站巡检方式仍以人工为主,存在效率低下、监控不全面等问题。变电站通常是一个封闭的系统空间,设备种类繁多、占地面积广阔,这对巡检人员实时…...
在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:
在 HarmonyOS 应用开发中,手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力,既支持点击、长按、拖拽等基础单一手势的精细控制,也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档,…...
【入坑系列】TiDB 强制索引在不同库下不生效问题
文章目录 背景SQL 优化情况线上SQL运行情况分析怀疑1:执行计划绑定问题?尝试:SHOW WARNINGS 查看警告探索 TiDB 的 USE_INDEX 写法Hint 不生效问题排查解决参考背景 项目中使用 TiDB 数据库,并对 SQL 进行优化了,添加了强制索引。 UAT 环境已经生效,但 PROD 环境强制索…...
java调用dll出现unsatisfiedLinkError以及JNA和JNI的区别
UnsatisfiedLinkError 在对接硬件设备中,我们会遇到使用 java 调用 dll文件 的情况,此时大概率出现UnsatisfiedLinkError链接错误,原因可能有如下几种 类名错误包名错误方法名参数错误使用 JNI 协议调用,结果 dll 未实现 JNI 协…...
基于Docker Compose部署Java微服务项目
一. 创建根项目 根项目(父项目)主要用于依赖管理 一些需要注意的点: 打包方式需要为 pom<modules>里需要注册子模块不要引入maven的打包插件,否则打包时会出问题 <?xml version"1.0" encoding"UTF-8…...
04-初识css
一、css样式引入 1.1.内部样式 <div style"width: 100px;"></div>1.2.外部样式 1.2.1.外部样式1 <style>.aa {width: 100px;} </style> <div class"aa"></div>1.2.2.外部样式2 <!-- rel内表面引入的是style样…...
Python如何给视频添加音频和字幕
在Python中,给视频添加音频和字幕可以使用电影文件处理库MoviePy和字幕处理库Subtitles。下面将详细介绍如何使用这些库来实现视频的音频和字幕添加,包括必要的代码示例和详细解释。 环境准备 在开始之前,需要安装以下Python库:…...
爬虫基础学习day2
# 爬虫设计领域 工商:企查查、天眼查短视频:抖音、快手、西瓜 ---> 飞瓜电商:京东、淘宝、聚美优品、亚马逊 ---> 分析店铺经营决策标题、排名航空:抓取所有航空公司价格 ---> 去哪儿自媒体:采集自媒体数据进…...
Fabric V2.5 通用溯源系统——增加图片上传与下载功能
fabric-trace项目在发布一年后,部署量已突破1000次,为支持更多场景,现新增支持图片信息上链,本文对图片上传、下载功能代码进行梳理,包含智能合约、后端、前端部分。 一、智能合约修改 为了增加图片信息上链溯源,需要对底层数据结构进行修改,在此对智能合约中的农产品数…...
宇树科技,改名了!
提到国内具身智能和机器人领域的代表企业,那宇树科技(Unitree)必须名列其榜。 最近,宇树科技的一项新变动消息在业界引发了不少关注和讨论,即: 宇树向其合作伙伴发布了一封公司名称变更函称,因…...
Ubuntu系统复制(U盘-电脑硬盘)
所需环境 电脑自带硬盘:1块 (1T) U盘1:Ubuntu系统引导盘(用于“U盘2”复制到“电脑自带硬盘”) U盘2:Ubuntu系统盘(1T,用于被复制) !!!建议“电脑…...
