C++篇之类和对象下

一、初始化列表

什么是初始化列表?

初始化列表是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;
}

使用建议

友元机制在某些场景下确实提供了便利,比如运算符重载、迭代器模式等。但它破坏了类的封装性,增加了代码耦合度。因此建议:

  1. 谨慎使用,只有在确实需要突破封装时才考虑

  2. 优先考虑是否可以通过类的公有接口实现功能

  3. 尽量限制友元的数量和访问范围

五、内部类

内部类是指定义在另一个类内部的类。它虽然位于外部类的作用域内,但本质上是一个独立的类,与定义在全局的类相比,主要区别在于:

  • 受外部类作用域和访问限定符的限制

  • 外部类的对象中不包含内部类的成员(即内部类不会成为外部类的成员变量)

核心特性

  1. 友元关系

    内部类默认是外部类的友元类,即内部类可以直接访问外部类的所有成员(包括私有和保护成员)。但外部类不能直接访问内部类的私有成员。

  2. 封装增强

    内部类本质是一种封装手段。当两个类紧密关联,且一个类主要为另一个类服务时,可以将前者设计为后者的内部类。

  • 如果将内部类定义在外部类的privateprotected区域,则它成为外部类的专属内部类,外部无法使用。
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;
}
相关推荐
Mr_WangAndy1 小时前
C++数据结构与算法_排序算法
c++·排序算法·基础排序·高级排序
水月wwww1 小时前
Rust的安装与卸载 | windows
开发语言·windows·rust
SouthRosefinch1 小时前
一、HTML简介与开发环境
开发语言·前端·html
€8112 小时前
Java入门级教程27——ActiveMQ的下载与应用
java·开发语言·activemq·点对点文本消息发送·点对点对象消息发送·mysql+redis·序列化对象消息传输
Irissgwe2 小时前
C&C++内存管理
c语言·开发语言·c++·c++内存管理
雾岛听蓝2 小时前
C文件操作与系统IO
linux·c语言·开发语言·经验分享·笔记·算法
夫唯不争,故无尤也2 小时前
HTTP方法详解:GET、POST、PUT、DELETE
开发语言·windows·python
Joker Zxc2 小时前
【前端基础(Javascript部分)】4、JavaScript的分支语句
开发语言·前端·javascript
小钻风33662 小时前
Optional:告别NullPointerException的优雅方案
开发语言·python