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;
}
相关推荐
智者知已应修善业12 小时前
【51单片机按键调节占空比3位数码管显示】2023-8-24
c++·经验分享·笔记·算法·51单片机
韦禾水12 小时前
记录一次项目部署到tomcat的异常
java·tomcat
曦月合一12 小时前
树莓派安装jdk、tomcat、vnc、谷歌浏览器开机自启等环境配置
java·tomcat·树莓派
harder32112 小时前
RMP模式的创新突破
开发语言·学习·ios·swift·策略模式
jinanwuhuaguo13 小时前
OpenClaw工程解剖——RAG、向量织构与“记忆宫殿”的索引拓扑学(第十三篇)
android·开发语言·人工智能·kotlin·拓扑学·openclaw
Rust研习社13 小时前
使用 Axum 构建高性能异步 Web 服务
开发语言·前端·网络·后端·http·rust
此剑之势丶愈斩愈烈13 小时前
openssl 自建证书
java
面汤放盐13 小时前
何时使用以及何时不应使用微服务:没有银弹
java·运维·云计算
0xDevNull13 小时前
Spring Boot 自动装配:从原理到实践
java·spring boot·后端