一、初始化列表
什么是初始化列表?
初始化列表是C++类构造函数中用于直接初始化成员变量的特殊语法。它以冒号:开头,后面跟着用逗号分隔的成员变量及其初始值。
cpp
class Example {
private:
int a;
double b;
string c;
public:
// 使用初始化列表的构造函数
Example(int x, double y, string z)
: a(x), b(y), c(z) // 初始化列表
{
// 函数体可以为空
}
};
初始化列表 vs 函数体内赋值
cpp
class Student {
private:
int age;
string name;
public:
// 方式1:函数体内赋值(不推荐)
Student(int a, string n) {
age = a; // 这是赋值,不是初始化
name = n; // name先默认初始化,再赋值
}
// 方式2:初始化列表(推荐)
Student(int a, string n)
: age(a), name(n) // 直接初始化
{
}
};
必须使用初始化列表的场景
以下三种成员变量必须在初始化列表中初始化,否则编译错误
cpp
class MustInit {
private:
int &refVar; // 引用类型
const int constVar; // const类型
NoDefaultClass obj; // 没有默认构造函数的类类型
public:
// 必须在初始化列表中初始化
MustInit(int &r, int c, const NoDefaultClass& o)
: refVar(r), constVar(c), obj(o)
{
}
};
class NoDefaultClass {
public:
NoDefaultClass(int x) {} // 有参构造,没有默认构造
};
成员变量缺省值(C++11)
C++11允许在类内声明成员变量时指定缺省值:
cpp
class Person {
private:
string name = "Unknown"; // C++11缺省值
int age = 18; // C++11缺省值
double salary;
public:
// 使用缺省值初始化name和age
Person() = default;
// 显式初始化覆盖缺省值
Person(string n, int a, double s)
: name(n), age(a), salary(s)
{
}
// 部分初始化,未显式初始化的使用缺省值
Person(string n)
: name(n) // age使用缺省值18
{
}
};
初始化顺序陷阱
初始化列表的执行顺序严格按照成员变量在类中声明的顺序,而不是初始化列表中的顺序:
cpp
class Trap {
private:
int a;
int b;
public:
// 危险!看起来用b初始化a,但实际执行顺序由声明顺序决定
Trap(int x)
: b(x), a(b) // 先初始化a,但此时b还未初始化!
{
}
// 正确的做法
Trap(int x)
: a(x), b(x) // 按声明顺序,或保证依赖关系正确
{
}
void print() {
cout << "a=" << a << ", b=" << b << endl;
}
};
// 使用示例
Trap t(10);
t.print(); // 危险版本可能输出:a=随机值,b=10

二、类型转换
核心概念
C++ 允许内置类型隐式转换为类类型对象,前提是该类定义了以该内置类型为参数的构造函数。这种构造函数充当了"转换器"的角色,告诉编译器如何将原始类型包装成类对象。
cpp
class MyClass {
private:
int value;
public:
// 接受int类型的构造函数,支持隐式转换
MyClass(int x) : value(x) {
cout << "构造函数被调用,value = " << value << endl;
}
void print() const {
cout << "value = " << value << endl;
}
};
void fun(MyClass obj) {
obj.print();
}
int main() {
MyClass obj1 = 10; // 隐式转换:int -> MyClass
// 等价于 MyClass obj1(10);
fun(20); // 隐式转换:int -> MyClass
// 等价于 fun(MyClass(20));
return 0;
}
explicit 关键字的作用
如果构造函数前加上 explicit 关键字,编译器将不再允许隐式类型转换。这可以防止意外的类型转换,提高代码安全性。
cpp
class SafeNumber {
int value;
public:
explicit SafeNumber(int x) : value(x) {} // explicit 构造函数
};
void processNumber(SafeNumber n) {}
int main() {
// processNumber(100); ❌ 错误!explicit阻止了隐式转换
processNumber(SafeNumber(100)); // ✅ 必须显式构造
return 0;
}
类类型之间的隐式转换
类类型的对象之间也可以发生隐式转换,前提是存在适当的构造函数支持。
cpp
class Meters {
double length;
public:
Meters(double m) : length(m) {}
};
class Kilometers {
double length;
public:
// 允许从Meters隐式转换为Kilometers
Kilometers(Meters m) : length(m.length / 1000.0) {}
// explicit版本
// explicit Kilometers(Meters m) : length(m.length / 1000.0) {}
};
int main() {
Meters m(5000);
Kilometers km = m; // ✅ 隐式转换:Meters → Kilometers
// 如果构造函数是explicit:
// Kilometers km2(m); // 必须显式构造
return 0;
}
隐式转换的注意事项
隐式转换虽然方便,但可能带来意想不到的行为:
cpp
class String {
public:
String(const char* str) {} // const char* → String
String(int size) {} // int → String
};
int main() {
String s1 = "hello"; // ✅ const char* 隐式转换
String s2 = 10; // ✅ int 隐式转换
// 但可能不是程序员想要的:本意是创建大小为10的字符串
// 实际上可能创建一个包含字符10的字符串
// 使用explicit可以避免这种混淆
return 0;
}
三、static成员
静态成员变量
-
用static 修饰的成员变量,称之为静态成员变量,静态成员变量必须在类外进行初始化(定义)。
-
静态成员变量为所有类对象所共享 ,不属于某个具体的对象,不存在对象中,而是存放在静态存储区。
-
静态成员变量不能在声明位置给缺省值初始化,因为缺省值是给构造函数初始化列表使用的,而静态成员变量不属于某个对象,不走构造函数初始化列表。
静态成员函数
-
用static 修饰的成员函数,称之为静态成员函数,静态成员函数没有this指针。
-
静态成员函数中只能访问静态成员(包括静态成员变量和静态成员函数),不能访问非静态成员,因为没有this指针指向具体对象。
-
非静态成员函数可以访问任意静态成员(变量和函数),不受限制。
访问方式与权限
-
突破类域即可访问静态成员,可以通过**
类名::静态成员** 或对象.静态成员来访问。 -
静态成员也是类的成员,同样受**访问限定符(public、protected、private)**的限制
cpp
#include <iostream>
using namespace std;
class Student {
private:
static int totalStudents; // 静态成员变量声明
string name; // 非静态成员变量
public:
Student(string n) : name(n) {
totalStudents++; // 非静态成员函数访问静态成员
}
// 静态成员函数
static int getTotal() {
// return name; // 错误:静态成员函数不能访问非静态成员
return totalStudents; // 静态成员函数访问静态成员
}
// 非静态成员函数
void showInfo() {
cout << "姓名:" << name << ",当前学生总数:" << totalStudents << endl;
// 非静态成员函数可以访问静态成员函数
cout << "通过函数获取总数:" << getTotal() << endl;
}
};
// 静态成员变量必须在类外初始化
int Student::totalStudents = 0;
int main() {
cout << "初始学生人数:" << Student::getTotal() << endl; // 通过类名访问
Student s1("张三");
Student s2("李四");
cout << "通过对象访问:" << s1.getTotal() << endl; // 通过对象访问
s1.showInfo(); // 非静态成员函数访问静态和非静态成员
// Student::totalStudents = 10; // 错误:private成员不能在类外直接访问
return 0;
}
四、友元
友元是C++中一种打破类封装性的机制,它允许特定的函数或其他类访问当前类的私有(private)和保护(protected)成员。友元主要分为两类:友元函数 和友元类。
友元函数
友元函数是在类内部用friend关键字声明的非成员函数,它可以访问类的私有和保护成员。
cpp
#include <iostream>
using namespace std;
// 前置声明
class Point;
// 计算两点距离的函数(将被声明为友元)
double distance(const Point& p1, const Point& p2);
class Point {
private:
int x, y;
public:
Point(int x_val, int y_val) : x(x_val), y(y_val) {}
// 在类的任何位置声明友元函数(不受访问限定符影响)
friend double distance(const Point& p1, const Point& p2);
void display() {
cout << "(" << x << ", " << y << ")" << endl;
}
};
// 友元函数定义 - 可以访问Point的私有成员x和y
double distance(const Point& p1, const Point& p2) {
int dx = p1.x - p2.x;
int dy = p1.y - p2.y;
return sqrt(dx * dx + dy * dy);
}
int main() {
Point p1(1, 2), p2(4, 6);
p1.display();
p2.display();
cout << "距离: " << distance(p1, p2) << endl;
return 0;
}
一个函数作为多个类的友元
cpp
#include <iostream>
using namespace std;
class Rectangle; // 前置声明
class Circle {
private:
double radius;
public:
Circle(double r) : radius(r) {}
// 声明displayInfo为友元函数
friend void displayInfo(const Circle& c, const Rectangle& r);
};
class Rectangle {
private:
double width, height;
public:
Rectangle(double w, double h) : width(w), height(h) {}
// 同样声明displayInfo为友元函数
friend void displayInfo(const Circle& c, const Rectangle& r);
};
// 一个函数同时是两个类的友元
void displayInfo(const Circle& c, const Rectangle& r) {
cout << "圆半径: " << c.radius << endl;
cout << "矩形: " << r.width << " x " << r.height << endl;
}
int main() {
Circle c(5);
Rectangle r(3, 4);
displayInfo(c, r);
return 0;
}
友元类
当一个类被声明为另一个类的友元时,它的所有成员函数都可以访问另一个类的私有和保护成员。
cpp
#include <iostream>
using namespace std;
class Account {
private:
double balance;
public:
Account(double b) : balance(b) {}
// 声明Manager为友元类
friend class Manager;
};
class Manager {
public:
// Manager的所有成员函数都可以访问Account的私有成员
void showBalance(const Account& acc) {
cout << "账户余额: " << acc.balance << endl;
}
void deposit(Account& acc, double amount) {
if (amount > 0) {
acc.balance += amount;
cout << "存款成功,当前余额: " << acc.balance << endl;
}
}
void withdraw(Account& acc, double amount) {
if (amount > 0 && amount <= acc.balance) {
acc.balance -= amount;
cout << "取款成功,当前余额: " << acc.balance << endl;
}
}
};
int main() {
Account myAcc(1000);
Manager mgr;
mgr.showBalance(myAcc);
mgr.deposit(myAcc, 500);
mgr.withdraw(myAcc, 200);
return 0;
}
友元关系的重要特性
单向性:如果A是B的友元,A可以访问B的私有成员,但B不能访问A的私有成员。
cpp
class B; // 前置声明
class A {
private:
int secretA = 10;
public:
// A是B的友元
friend class B;
void accessB(B& b); // 声明但不定义
};
class B {
private:
int secretB = 20;
public:
void accessA(A& a) {
// B是A的友元吗?不是!所以这行会编译错误
// cout << a.secretA; // 错误!B不是A的友元
}
void showOwnSecret() {
cout << "B的私有数据: " << secretB << endl;
}
};
// A的成员函数可以访问B的私有成员,因为A是B的友元
void A::accessB(B& b) {
cout << "通过A访问B的私有数据: " << b.secretB << endl;
}
int main() {
A a;
B b;
a.accessB(b); // 成功
// b.accessA(a); // 编译错误
return 0;
}
不传递性:如果A是B的友元,B是C的友元,A不自动成为C的友元。
cpp
class C;
class B {
private:
int secretB = 20;
public:
friend class A; // A是B的友元
friend class C; // C是B的友元
};
class A {
public:
void accessB(B& b) {
cout << "A访问B: " << b.secretB << endl; // 允许
}
// 不能访问C的私有成员,即使B是C的友元
// void accessC(C& c) { ... } // 错误!
};
class C {
private:
int secretC = 30;
public:
void accessB(B& b) {
cout << "C访问B: " << b.secretB << endl; // 允许,C是B的友元
}
};
int main() {
A a;
B b;
C c;
a.accessB(b); // 成功
c.accessB(b); // 成功
return 0;
}
使用建议
友元机制在某些场景下确实提供了便利,比如运算符重载、迭代器模式等。但它破坏了类的封装性,增加了代码耦合度。因此建议:
-
谨慎使用,只有在确实需要突破封装时才考虑
-
优先考虑是否可以通过类的公有接口实现功能
-
尽量限制友元的数量和访问范围
五、内部类
内部类是指定义在另一个类内部的类。它虽然位于外部类的作用域内,但本质上是一个独立的类,与定义在全局的类相比,主要区别在于:
-
受外部类作用域和访问限定符的限制
-
外部类的对象中不包含内部类的成员(即内部类不会成为外部类的成员变量)
核心特性
-
友元关系
内部类默认是外部类的友元类,即内部类可以直接访问外部类的所有成员(包括私有和保护成员)。但外部类不能直接访问内部类的私有成员。
-
封装增强
内部类本质是一种封装手段。当两个类紧密关联,且一个类主要为另一个类服务时,可以将前者设计为后者的内部类。
- 如果将内部类定义在外部类的
private或protected区域,则它成为外部类的专属内部类,外部无法使用。
cpp
#include <iostream>
using namespace std;
class A
{
private:
static int _k;
int _h = 1;
public:
class B
{
public :
void foo(const A& a)
{
cout << _k << endl;
cout << a._h << endl;
}
int _b1;
};
};
int A::_k = 1;
int main()
{
cout << sizeof(A) << endl;
A::B b;
A aa;
b.foo(aa);
return 0;
}
六、匿名对象
用类型(实参)定义出来的对象叫做匿名对象,相比之前我们定义的 类型 对象名(实参) 定义出来的叫有名参数。
匿名对象生命周期只在当前一行,一般临时定义一个对象当前用一下即可,就可以定义匿名对象。
cpp
#include <iostream>
using namespace std;
class A
{
public:
A(int _a = 0)
:_a(0)
{
cout << "A(int a)" << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
};
class solution
{
public:
int Sum_solution(int n)
{
//...
return n;
}
};
int main()
{
A aa;
//不能这样定义对象,因为编译器无法识别下面是一个函数声明,还是对象定义
//A aa1()
//但是可以这样定义匿名对象,匿名对象的特点不用取名字,
//但是它的生命周期只有这一行,我们可以看到下一行他就会自动调用析构函数
A();
A(1);
A aa2(2);
solution().Sum_solution(10);
return 0;
}