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

C++ 类与对象(下)—— 进阶特性与底层机制解析(构造函数初始化,类型转换,static成员,友元,内部类,匿名对象)

一、构造函数初始化列表:给成员变量 “精准出生证明”

   在 C++ 中,构造函数对成员变量的初始化方式有 初始化列表 和 函数体内赋值 两种。初始化列表是构造函数的一个重要特性,它允许在对象创建时对成员变量进行初始化。与在构造函数体内赋值不同,初始化列表是在成员变量定义时就进行初始化,这在效率和处理特定类型成员(如 const 成员、引用成员)上有显著优势。


 

二、基本语法

初始化列表位于构造函数的参数列表之后,函数体之前,以冒号" :" 开始,成员变量的初始化用逗号" ," 分隔。其基本形式如下:

Date(参数列表) 

    : 成员变量1(初始化值1), 

      成员变量2(初始化值2),

              ......... {

          // 构造函数体 

   }

 

这里的 Date 是类的名称,参数列表 是构造函数接收的参数,成员变量1成员变量2 等是类中定义的成员变量,初始化值1初始化值2 等是用于初始化成员变量的值。

补充: 构造函数的执行顺序为:先调用该类的初始化列表,最后执行该类的构造函数体

有初始化列表情况下:用基本数据类型(如 intdouble、指针等)来举例,自定义类型也是同理,如下图:

 

 无初始化列表情况下:自定义类型(但有另一个类的对象的默认构造函数)如下图:

在 C++ 中,当类的构造函数 没有显式使用初始化列表 时,成员变量会经历 默认初始化(Default Initialization):

  • 基本数据类型(如 intdouble、指针等):
    默认初始化不会赋予任何初始值,它们的值是未定义的(Undefined),可能是内存中的随机值(俗称 “垃圾值”)。

  • 自定义类型(如另一个类的对象):
    会调用其 默认构造函数 进行初始化(即使该自定义类型没有写初始化列表)。

  • 执行顺序
    1. 先通过 “空的初始化列表” 对成员进行默认初始化(基本类型未定义,自定义类型调用默认构造函数)。
    2. 再进入构造函数体,对成员进行赋值(覆盖默认初始化后的值)。

如果没有另一个类的对象和无初始化列表基本数据类型一样会执行空的初始化列表,感觉像跳过一样,其实没有跳过然后再执行构造函数体,如下图:

 

三、使用细节

(一)初始化顺序

成员变量的初始化顺序由它们在类中声明的顺序决定,而不是在初始化列表中出现的顺序。例如:

在这个例子中,尽管在初始化列表里 b 先出现,但由于 a 在类中先声明,所以 a 会先初始化。此时 b 未初始化,a 会得到未定义的值。因此,在编写初始化列表时,要确保初始化顺序符合逻辑,避免出现未定义行为。

(二)必须使用初始化列表的情况

  1. 初始化 const 成员const 成员在定义后不能被赋值,只能在初始化时指定值,所以必须使用初始化列表进行初始化。
  2. 初始化引用成员:引用在定义时必须绑定到一个对象,之后不能再绑定到其他对象,因此也需要在初始化列表中完成初始化。
  3. 初始化没有默认构造函数的自定义类型成员:如果类的成员是另一个自定义类型,且该自定义类型没有默认构造函数(无参构造函数或全缺省构造函数),则必须在初始化列表中显式调用其带参构造函数。

(三)效率优势

对于自定义类型的成员变量,使用初始化列表可以直接调用其合适的构造函数进行初始化,避免了先调用默认构造函数再进行赋值的额外开销。

四、示例代码

(一)基本使用示例

#include <iostream>
using namespace std;class Point {
private:int x;int y;
public:// 使用初始化列表的构造函数Point(int a, int b) : x(a), y(b) {cout << "Point 对象已创建,坐标为 (" << x << ", " << y << ")" <<endl;}
};int main() {Point p(3, 4);return 0;
}

在这个例子中,Point 类的构造函数使用初始化列表将成员变量 x 和 y 分别初始化为传入的参数 a 和 b

(二)初始化 const 成员示例

此例中,MyClass 类有一个 const 成员 value,必须在初始化列表中进行初始化。

(三)初始化引用成员示例

在这个示例中,RefExample 类的成员 ref 是一个引用,通过初始化列表绑定到传入的参数 num

(四)初始化没有默认构造函数的自定义类型成员示例

这里 MyClass 类包含一个 SubClass 类型的成员 subSubClass 没有默认构造函数,因此必须在 MyClass 的初始化列表中显式调用 SubClass 的带参构造函数。

补充:既然初始化列表这么好又不用执行别的,反而函数体内赋值每次都需要先执行初始化列表,不如直接用初始化列表,还可以省略步骤,岂不是更快

初始化列表确实有很多优点,用它来初始化成员变量既方便又可能让程序运行得更快,但也不能一股脑全用初始化列表,不用函数体里赋值。下面给你仔细说说为啥。

初始化列表的好处

  1. 速度快:初始化列表就像是给成员变量直接 “设定出生状态”。而在构造函数里赋值呢,成员变量得先有个默认的 “出生状态”,然后再改成你想要的状态。这就好比你直接去市场买一只你想要的小狗,和先随便抱一只小狗回家,再把它换成你想要的那只,肯定是直接买更省事、更快。
  2. 有些情况必须用:有些成员变量就像有 “特殊脾气”,比如引用类型和const类型的成员变量,必须得用初始化列表来初始化。这就像有些游戏规则规定,必须先做完特定任务才能开始玩游戏一样。

构造函数里赋值也有它的用武之地

  1. 复杂情况更方便:要是成员变量的初始化得经过一番复杂的计算或者判断循环等等,又或者得依赖其他成员变量,在构造函数里赋值就更合适。比如说,你要根据两个成员变量算出它们的和,再把这个和赋值给另一个成员变量,在构造函数里写这些计算步骤,就会更清楚明白。
  2. 代码好懂又好改:如果初始化的过程特别复杂,把代码写在构造函数里,别人看代码的时候就能更容易理解你在做什么。就像写一篇文章,把复杂的内容分成一段一段写清楚,别人读起来才不会一头雾水。

总结

初始化列表和在构造函数里赋值各有各的好。要是成员变量能直接设定初始值,或者是引用类型、const类型的变量,用初始化列表比较好;要是初始化过程复杂,或者为了让代码更清楚,就在构造函数里赋值。得根据实际情况来选择用哪种方式。

补充:

五,在 C++11 之前,我们只能在构造函数的初始化列表或者函数体中对类的成员变量进行初始化。而 C++11 引入了一个新特性,允许我们在成员变量声明的地方直接给它们设置缺省值。下面我们结合例子详细解释相关规则。

1. 缺省值在未显式初始化时的使用

(->引用类型和const类型的成员变量自定义类型成员是否可以用缺省值?)

当成员变量在声明处设置了缺省值,且在构造函数的初始化列表中没有显式对其进行初始化,那么初始化列表会使用这个缺省值来初始化该成员。

在上述代码中,num 成员变量在声明时被赋予了缺省值 10。在构造函数里没有对 num 进行显式初始化,因此 num 会使用这个缺省值 10 完成初始化。

2. 未设置缺省值的内置类型成员

对于没有在声明处设置缺省值,并且在初始化列表中也没有显式初始化的内置类型成员,其是否初始化取决于编译器,C++ 标准并未对此作出规定。

在这个例子里,num 是一个内置类型(int),它既没有缺省值,在构造函数初始化列表中也未被显式初始化,所以 num 的值是不确定的,不同编译器可能有不同表现。

3. 未显式初始化的自定义类型成员

如果成员是自定义类型,并且在初始化列表中没有显式初始化,那么会调用该自定义类型的默认构造函数。若该自定义类型没有默认构造函数,就会导致编译错误

#include <iostream>// 自定义类型,有默认构造函数
class SubClass {
public:SubClass() {std::cout << "SubClass 默认构造函数被调用" << std::endl;}
};// 自定义类型,没有默认构造函数
class AnotherSubClass {
public:AnotherSubClass(int value) {std::cout << "AnotherSubClass 带参数构造函数被调用" << std::endl;}
};class MyClass {
public:SubClass sub; // 有默认构造函数的自定义类型成员AnotherSubClass anotherSub; // 没有默认构造函数的自定义类型成员// 构造函数,未显式初始化自定义类型成员MyClass() {} 
};int main() {MyClass obj;return 0;
}

在上述代码中,SubClass 有默认构造函数,所以 MyClass 的构造函数在未显式初始化 sub 时,会自动调用 SubClass 的默认构造函数。而 AnotherSubClass 没有默认构造函数,当 MyClass 的构造函数未显式初始化 anotherSub 时,就会导致编译错误。

建议

尽量使用初始化列表来初始化成员变量,这样能让代码更加清晰,并且可以避免一些因未初始化而导致的潜在问题。如果某个成员变量有合适的缺省值,在声明处设置缺省值可以作为一种安全的兜底方案。


 

二、类型转换:让对象 “灵活变身”

补充:被优化掉的拷贝构造函数

理论上的步骤

在早期的 C++ 标准中,像 A d1 = 42; 这样的语句理论上会经历以下步骤:

  • 首先,使用 42 调用 A 类的构造函数 A(int value) 创建一个临时的 A 类型对象。
  • 然后,使用这个临时对象调用拷贝构造函数来初始化 d1

现代编译器的优化

然而,现代的 C++ 编译器通常会进行 “拷贝省略(Copy Elision)” 优化。对于 A d1 = 42;,编译器会直接使用 42 调用 A 类的构造函数来初始化 d1,而不会创建临时对象,也不会调用拷贝构造函数。这是为了提高程序的性能,避免不必要的对象创建和复制操作

 

但是我们可以调整代码以避免编译器优化

为了尽可能地阻止编译器进行优化,我们可以对代码进行修改,把 A d1 = 42; 拆分成更明确的步骤,并且添加赋值运算符重载函数,从而更好地观察对象的创建和赋值过程。以下是修改后的代码:

代码解释

  • A 类
    • A():默认构造函数,用于创建 A 类的对象。
    • A(int value):带参数的构造函数,使用传入的 int 值初始化 A_value
    • A(const A& other):拷贝构造函数,用于创建一个新对象,该对象是另一个 A 类对象的副本。
    • A& operator=(const A& other):赋值运算符重载函数,用于将一个 A 类对象的值赋给另一个 A 类对象。
  • main 函数
    • A d1;:调用默认构造函数创建 d1 对象。
    • A temp = A(42);:显式调用带参数的构造函数创建临时对象 temp
    • d1 = temp;:调用赋值运算符重载函数,将 temp 的值赋给 d1

1. 内置类型到类类型的隐式转换

当我们需要将一个内置类型(如intdouble等)隐式转换为某个类类型的对象时,只需要在类中定义一个接受该内置类型参数的构造函数即可。

示例代码:

class MyInt {
public:MyInt(int value): m_value(value) {}  // 允许int到MyInt的隐式转换void print() const {std::cout << m_value << std::endl; }private:int m_value;
};void printNumber(const MyInt& num) {num.print();
}int main() {printNumber(42);  // 隐式调用MyInt的构造函数:MyInt(42)return 0;
}

在上述代码中,printNumber函数需要一个MyInt类型的参数,但我们直接传递了一个int类型的值42。编译器会自动调用MyInt(int)构造函数生成一个临时对象,完成隐式类型转换。

2. 禁止隐式转换:explicit关键字

隐式转换虽然方便,但有时会导致意外的行为。例如,如果构造函数有多个参数,或者某些场景下不希望自动转换,可以使用explicit关键字标记构造函数,强制要求显式调用。

示例代码:

class ExplicitInt {
public:explicit ExplicitInt(int value): m_value(value) {}  // 必须显式调用void print() const {std::cout << m_value << std::endl; }private:int m_value;
};void printExplicit(const ExplicitInt& num) {num.print();
}int main() {// printExplicit(42);  // 错误:不能隐式转换printExplicit(ExplicitInt(42));  // 正确:显式构造对象return 0;
}

通过添加explicitExplicitInt的构造函数不再支持隐式转换,必须在代码中明确创建对象。

3. 类类型之间的隐式转换

类类型的对象之间也可以进行隐式转换,这需要满足以下条件之一:

  • 转换构造函数:目标类定义了接受源类对象为参数的构造函数。

  • 类型转换运算符:源类定义了转换为目标类型的运算符。

3.1 转换构造函数

3.1.1 定义与原理

转换构造函数指的是目标类定义的接受源类对象作为参数的构造函数。当需要目标类对象,而提供的却是源类对象时,编译器会自动调用这个构造函数,把源类对象转换为目标类对象。

3.1.2 示例代码

#include <iostream>// 源类
class Meter {
public:Meter(double value): m_value(value) {}double getValue() const {return m_value; }
private:double m_value;
};// 目标类
class Centimeter {
public:// 转换构造函数Centimeter(const Meter& meter) : m_value(meter.getValue() * 100) {}void display() const {std::cout << "Centimeter: " << m_value << std::endl;}
private:double m_value;
};// 接受 Centimeter 对象的函数
void printCentimeter(const Centimeter& cm) {cm.display();
}int main() {Meter meter(2.5);// 隐式转换:Meter 对象转换为 Centimeter 对象printCentimeter(meter);return 0;
}

3.1.3代码解释

  • Meter 类是源类,Centimeter 类是目标类。
  • Centimeter 类的构造函数 Centimeter(const Meter& meter) 属于转换构造函数,它能把 Meter 对象转换为 Centimeter 对象。
  • 在 main 函数中,调用 printCentimeter(meter) 时,由于 printCentimeter 函数需要 Centimeter 对象,而传入的是 Meter 对象,编译器会自动调用 Centimeter 的转换构造函数,将 Meter 对象转换为 Centimeter 对象。

3.2 类型转换运算符

3.2.1 定义与原理

类型转换运算符是源类定义的转换为目标类型的运算符。当需要目标类型对象,而提供的是源类对象时,编译器会自动调用这个运算符,把源类对象转换为目标类型对象。

3.2.2 示例代码

#include <iostream>// 源类
class Pound {
public:Pound(double value): m_value(value) {}// 类型转换运算符operator double() const {return m_value;}
private:double m_value;
};// 接受 double 类型参数的函数
void printWeight(double weight) {std::cout << "Weight in kg: " << weight * 0.453592 << std::endl;
}int main() {Pound pound(10);// 隐式转换:Pound 对象转换为 double 类型printWeight(pound);return 0;
}

3.2.3 代码解释

  • Pound 类是源类,double 是目标类型。
  • Pound 类的 operator double() 是类型转换运算符,它能把 Pound 对象转换为 double 类型。
  • 在 main 函数中,调用 printWeight(pound) 时,由于 printWeight 函数需要 double 类型参数,而传入的是 Pound 对象,编译器会自动调用 Pound 的类型转换运算符,将 Pound 对象转换为 double 类型。

3. 3注意事项

3. 3.1 二义性问题

如果同时定义了转换构造函数和类型转换运算符,可能会引发二义性问题。例如:

#include <iostream>class A;class B {
public:B() {}B(const A& a);
};class A {
public:A() {}operator B() const;
};B::B(const A& a) {std::cout << "Conversion constructor called" << std::endl;
}A::operator B() const {std::cout << "Type conversion operator called" << std::endl;return B();
}void func(const B& b) {}int main() {A a;// 二义性问题,编译器不知道该使用转换构造函数还是类型转换运算符func(a); return 0;
}

在这个例子中,调用 func(a) 时,编译器既可以调用 B 的转换构造函数,也可以调用 A 的类型转换运算符,从而导致编译错误。

4. 隐式转换的风险与最佳实践

虽然隐式转换提供了便利,但也可能引发问题:

  • 意外的行为:编译器可能在开发者未察觉的情况下进行转换,导致逻辑错误。

  • 性能损耗:生成临时对象可能带来额外开销。

建议:

  • 对单参数构造函数使用explicit,除非明确需要隐式转换。

  • 谨慎使用类型转换运算符,或将其标记为explicit(C++11起支持)。

C++11的显式类型转换运算符示例:

class SafeInt {
public:explicit operator int() const {return m_value; }  // 必须显式转换
private:int m_value;
};int main() {SafeInt si;// int i = si;          // 错误:不能隐式转换int j = static_cast<int>(si);  // 正确:显式转换return 0;
}

总结

C++中的类型转换机制赋予开发者极大的灵活性,但也需要谨慎使用:

  • 内置类型到类类型的转换依赖构造函数。

  • explicit关键字用于禁止隐式转换。

  • 类类型之间可通过转换构造函数或类型转换运算符实现隐式转换。

  • 优先使用显式转换以提高代码安全性。


三、static 成员:类的 “全局管家”

在 C++ 中,静态成员(Static Members) 是属于类本身的成员,而非类的某个对象。它们不依赖于类的实例存在,可以通过类名直接访问。静态成员分为 静态成员变量 和 静态成员函数,是实现类级别的数据共享和工具方法的重要机制。

1. 静态成员变量(Static Member Variables)

基本概念

  • 声明与定义
    • 在类内用 static 关键字声明,需在类外进行定义(分配内存),否则链接时会报错。
    class MyClass {
    public:private://成员变量 -- 属于每一个类对象,存储在对象里面int _a1 = 1;int _a2 = 2;// 静态成员变量 -- 属于类,属于类的每个对象共享,存储到静态区static int s_count;  
    };int MyClass::s_count = 0;  // 全局位置,类外定义并初始化(必须)
    
  • 存储特性
    • 静态成员变量存储在全局数据区(静态存储区),不属于任何对象,所有对象共享同一份实例。
    • 无论创建多少个类对象,静态成员变量只有一份拷贝。

访问方式

  • 通过类名类名::静态成员变量(推荐,清晰表明属于类)。
  • 通过对象对象名.静态成员变量 或 对象指针->静态成员变量(不推荐,易误解为对象成员)。
MyClass obj;
obj.s_count = 10;          // 合法,但不推荐
MyClass::s_count = 20;     // 推荐方式

初始化规则

  • 非 const 静态成员:无论类型(整型、指针、类类型),非 const 静态成员必须在类外定义(除非是 constexpr 类型)。
  • const 静态成员(C++11 起):
    • 类型:intcharlongshortbool、枚举(enum)等整型家族类型,可在类内直接初始化。任意指针类型(如 const char*int*、自定义类型指针)不允许在类内直接初始化。
    class MyClass {
    public:static const int s_max = 100;  // 类内初始化(合法)static const char* s_name;     // 类内不可初始化,需类外定义
    };
    const char* MyClass::s_name = "Static Member";  // 类外定义
    

作用与场景

  • 计数器:记录类的实例化次数。
    class Counter {
    public:Counter() {++s_instanceCount; }~Counter() {--s_instanceCount; }static int getInstanceCount() {return s_instanceCount; }private:static int s_instanceCount;  // 静态成员变量记录实例数
    };int Counter::s_instanceCount = 0;  // 类外初始化int main() {Counter a, b;std::cout << Counter::getInstanceCount();  // 输出 2return 0;
    }
    
  • 共享配置:存储类的全局配置(如日志级别、数据库连接参数)。
  • 单例模式:作为单例类的唯一实例(静态成员变量指向自身)。

注意事项

  • 访问权限:静态成员仍受 public/private/protected 控制。
    • 私有静态成员只能在类内或友元函数中访问。
  • 模板类的静态成员:每个模板特化实例拥有独立的静态成员。
    template <typename T>
    class TemplateClass {
    public:static T s_value;
    };template <typename T>
    T TemplateClass<T>::s_value;  // 模板类静态成员需在类外特化定义
    
  • 初始化顺序
    • 静态成员变量的初始化顺序与全局变量类似,按定义顺序在 main () 前初始化,跨编译单元的初始化顺序未定义(可能导致问题)。

2. 静态成员函数(Static Member Functions)

基本概念

  • 声明与调用
    • 在类内用 static 声明,无 this 指针(不依赖对象)指定类型和访问限定符就可以访问。
    class MyClass {
    public:static void staticFunc() {// 只能访问静态成员或全局变量// 不能访问非静态成员(无this指针)}
    };int main() {MyClass::staticFunc();  // 通过类名调用(推荐)MyClass obj;obj.staticFunc();       // 通过对象调用(合法但不推荐)return 0;
    }
    
  • 特性
    • 不能声明为 virtual(无多态性,因不依赖对象)。
    • 不能使用 const 修饰(无 this 指针可供保护)。

限制与用途

  • 只能访问静态成员
    • 静态函数中不能直接访问非静态成员变量或非静态成员函数,除非通过对象显式调用。
    class MyClass {
    public:int m_data;static void staticFunc(MyClass& obj) {obj.m_data = 10;  // 合法,通过对象访问非静态成员}
    };
    
  • 工具方法
    • 作为不依赖对象状态的工具函数(如工厂方法、辅助函数)。
    #include <iostream>// 定义 MathUtils 类
    class MathUtils {
    public:// 静态成员函数,用于执行加法操作static float Add(float a, float b) {return a + b;}
    };// 定义一个接受回调函数指针的函数
    // 该函数会调用传入的回调函数来完成特定的计算
    float performCalculation(float x, float y, float (*callback)(float, float)) {return callback(x, y);
    }int main() {float num1 = 5.0f;float num2 = 3.0f;// 将 MathUtils 类的静态成员函数 Add 作为回调函数传递给 performCalculation 函数float result = performCalculation(num1, num2, MathUtils::Add);std::cout << "计算结果是: " << result << std::endl;return 0;
    }
    
  • 回调函数
    • 静态成员函数可作为 C 风格回调函数(普通成员函数因含 this 指针无法直接作为回调)。

代码解释

  1. MathUtils 类

    • Add 是一个静态成员函数,它接收两个 float 类型的参数 a 和 b,然后返回它们的和。因为是静态成员函数,所以可以不依赖于类的对象来调用。
  2. performCalculation 函数

    • 这个函数接收三个参数:两个 float 类型的数值 x 和 y,以及一个函数指针 callback
    • 函数指针 callback 指向一个接受两个 float 类型参数并返回 float 类型结果的函数。
    • 在函数内部,调用 callback 函数,并传入 x 和 y 作为参数,最后返回计算结果。
  3. main 函数

    • 定义了两个 float 类型的变量 num1 和 num2,分别初始化为 5.0f 和 3.0f
    • 调用 performCalculation 函数,将 num1num2 以及 MathUtils::Add 作为参数传递进去。这里 MathUtils::Add 就是作为回调函数使用的。
    • 将 performCalculation 函数的返回值赋给 result 变量,并将结果输出到控制台。

普通成员函数不能直接作为回调函数的原因

普通成员函数包含一个隐式的 this 指针,这个指针指向调用该成员函数的对象。而 C 风格的回调函数是不包含 this 指针的,所以普通成员函数没办法直接当作回调函数使用。静态成员函数不依赖于类的对象,也就没有 this 指针,因此可以作为 C 风格回调函数。

与普通成员函数的区别

特性静态成员函数普通成员函数
this 指针有(指向当前对象)
访问非静态成员不能(除非通过对象)可以
多态性不支持(不能是虚函数)支持(可声明为虚函数)
调用方式类名::函数 或 对象。函数对象。函数 或 指针 -> 函数

注意事项

  • 隐藏父类静态成员
    • 子类定义同名静态成员会隐藏父类的静态成员(非覆盖,因静态成员属于类,与多态无关)。
    #include <iostream>
    #include <thread>
    #include <mutex>// 父类
    class Parent {
    public:static void func() {std::cout << "Parent::func() is called." << std::endl;}// 定义一个静态成员变量,模拟共享资源static int sharedResource;// 定义一个互斥量用于保护共享资源static std::mutex mtx;
    };// 初始化父类的静态成员变量
    int Parent::sharedResource = 0;
    std::mutex Parent::mtx;// 子类
    class Child : public Parent {
    public:static void func() {std::cout << "Child::func() is called." << std::endl;}// 多线程操作共享资源的函数static void incrementSharedResource() {for (int i = 0; i < 1000; ++i) {// 加锁,确保线程安全std::lock_guard<std::mutex> lock(mtx);++sharedResource;}}
    };int main() {// 调用子类隐藏的父类静态成员函数Child::Parent::func();// 调用子类的静态成员函数Child::func();// 创建多个线程来操作共享资源std::thread t1(Child::incrementSharedResource);std::thread t2(Child::incrementSharedResource);// 等待线程执行完毕t1.join();t2.join();// 输出共享资源的最终值std::cout << "Final value of sharedResource: " << Parent::sharedResource << std::endl;return 0;
    }

代码解释:

  1. 子类隐藏父类静态成员

    • Parent 类和 Child 类都定义了同名的静态成员函数 func()
    • 在 main 函数中,通过 Child::Parent::func() 显式调用父类的静态成员函数,而 Child::func() 则调用子类的静态成员函数,这体现了子类对父类静态成员的隐藏。
  2. 多线程安全访问静态成员

    • Parent 类定义了一个静态成员变量 sharedResource 作为共享资源,以及一个静态的 std::mutex 对象 mtx 用于保护该资源。
    • Child 类的 incrementSharedResource 函数用于对 sharedResource 进行递增操作。在操作之前,使用 std::lock_guard<std::mutex> 加锁,确保同一时间只有一个线程可以访问和修改 sharedResource,从而保证了线程安全。
    • 在 main 函数中,创建了两个线程 t1 和 t2 来调用 incrementSharedResource 函数,模拟多线程并发访问共享资源的情况。最后等待两个线程执行完毕,并输出 sharedResource 的最终值。

通过这个示例,你可以清楚地看到子类如何隐藏父类的静态成员,以及如何使用互斥量来保证静态成员在多线程环境下的安全访问。

3. 静态成员 vs 全局变量 / 函数

特性静态成员全局变量 / 函数
作用域类作用域(避免命名污染)全局作用域
访问控制支持 public/private无(依赖命名空间)
与类的关联属于类,逻辑上更内聚独立于类
面向对象封装支持(类的一部分)不支持

4. 总结

  • 静态成员变量
    • 共享类级别的数据,所有对象可见,需在类外初始化。
    • 用于计数器、配置信息、单例实例等场景。
  • 静态成员函数
    • 无 this 指针,操作静态成员或全局数据,用于工具方法、回调函数等。
  • 最佳实践
    • 通过类名访问静态成员,避免依赖对象实例。
    • 合理设计访问权限,确保封装性。
    • 注意跨编译单元的静态成员初始化顺序问题。

静态成员是 C++ 中实现类级别共享和工具功能的核心机制,合理使用可提高代码的组织性和效率,但需注意其与对象状态的解耦特性及潜在的初始化问题。

 

 

四、友元:打破封装的 “特殊通行证”

在 C++ 中,封装是面向对象编程的重要特性之一,它通过访问控制(如 privateprotectedpublic)来隐藏类的内部实现细节,保证数据的安全性和完整性。然而,在某些特定情况下,我们可能需要让外部的函数或类能够访问某个类的私有成员,这时就可以使用友元机制。友元就像是一张 “特殊通行证”,可以打破类的封装限制。下面分别对友元函数和友元类进行详细讲解。

1. 友元函数:外部函数访问私有成员

1.1 基本概念

友元函数是一种特殊的函数,它虽然不是类的成员函数,但却可以访问该类的私有和保护成员。友元函数的声明需要在类的定义中使用 friend 关键字。

1.2 示例代码

#include <iostream>class Rectangle {
private:double length;double width;
public:Rectangle(double l, double w): length(l), width(w) {}// 声明友元函数friend double calculateArea(const Rectangle& rect);
};// 友元函数的定义
double calculateArea(const Rectangle& rect) {// 可以访问 Rectangle 类的私有成员return rect.length * rect.width;
}int main() {Rectangle rect(5.0, 3.0);double area = calculateArea(rect);std::cout << "The area of the rectangle is: " << area << std::endl;return 0;
}

1.3 代码解释

  • 在 Rectangle 类中,length 和 width 是私有成员变量,外部函数通常无法直接访问它们。
  • 通过在 Rectangle 类中使用 friend double calculateArea(const Rectangle& rect); 声明 calculateArea 为友元函数,该函数就获得了访问 Rectangle 类私有成员的权限。
  • 在 main 函数中,创建了一个 Rectangle 对象 rect,并调用 calculateArea 函数计算其面积。

1.4 注意事项

  • 友元函数不属于类的成员:友元函数没有 this 指针,它的调用方式和普通函数一样,不需要通过对象来调用。
  • 友元关系是单向的:如果 A 类声明 B 函数为友元函数,并不意味着 B 函数所在的类(如果有)或其他函数也能访问 A 类的私有成员。
  • 友元关系不具有传递性:如果 A 类声明 B 函数为友元函数,B 函数所在的类又声明 C 函数为友元函数,C 函数并不能访问 A 类的私有成员。

2. 友元类:整个类的成员函数都是友元

2.1 基本概念

友元类是指一个类可以将另一个类声明为自己的友元,这样,被声明为友元的类的所有成员函数都可以访问该类的私有和保护成员。同样,友元类的声明也需要在类的定义中使用 friend 关键字。

2.2 示例代码

#include <iostream>class Rectangle {
private:double length;double width;
public:Rectangle(double l, double w)]: length(l), width(w) {}// 声明友元类friend class AreaCalculator;
};class AreaCalculator {
public:double calculateArea(const Rectangle& rect) {// 可以访问 Rectangle 类的私有成员return rect.length * rect.width;}
};int main() {Rectangle rect(5.0, 3.0);AreaCalculator calculator;double area = calculator.calculateArea(rect);std::cout << "The area of the rectangle is: " << area << std::endl;return 0;
}

2.3 代码解释

  • 在 Rectangle 类中,使用 friend class AreaCalculator; 声明 AreaCalculator 为友元类。
  • AreaCalculator 类的 calculateArea 函数可以直接访问 Rectangle 类的私有成员 length 和 width
  • 在 main 函数中,创建了 Rectangle 对象 rect 和 AreaCalculator 对象 calculator,并调用 calculator 的 calculateArea 函数计算 rect 的面积。

2.4 注意事项

  • 友元类的成员函数都具有访问权限:一旦一个类被声明为另一个类的友元类,该类的所有成员函数都可以访问另一个类的私有和保护成员。
  • 友元类的声明位置:友元类的声明可以放在类的任何位置(privateprotected 或 public 部分),效果是一样的。
  • 友元关系的单向性和非传递性同样适用:友元类的关系是单向的,不具有传递性。
  • 友元函数不能用const修饰
  • 一个函数可以是多个类的友元函数

3. 友元机制的优缺点

3.1 优点

  • 提高代码的灵活性:在某些情况下,友元机制可以让我们更方便地实现一些功能,例如运算符重载、数据结构的遍历等。
  • 简化代码:通过友元函数或友元类,可以避免为了访问私有成员而提供过多的公共接口,从而简化代码。

3.2 缺点

  • 破坏封装性:友元机制打破了类的封装性,使得类的私有成员可以被外部访问,这可能会导致数据的安全性和完整性受到威胁。
  • 增加代码的耦合度:使用友元机制会增加类之间的耦合度,使得代码的维护和扩展变得更加困难。

因此,在使用友元机制时,需要谨慎权衡其优缺点,只在必要的情况下使用。

 

五、内部类:类中的 “嵌套小世界”

1. 定义与特点

定义

想象一下,你有一个大盒子(外部类),在这个大盒子里面又放了一个小盒子(内部类)。在 C++ 里,就是在一个类的内部再定义一个类,这个在内部定义的类就是内部类。下面是一个简单的例子:

class School { // 这是外部类,就像大盒子
public:class Classroom { // 这是内部类,就像大盒子里的小盒子public:void teach() {// 这里是内部类的一个行为,比如上课}};
};

在这个例子中,School 是外部类,Classroom 是内部类。

特点

  • 作用域受限:内部类就像被关在大盒子里的小盒子,它的活动范围被限制在外部类里面。你在外面想用这个小盒子,就得先通过大盒子找到它。例如,要创建 Classroom 的对象,就得这样写:
School::Classroom room;
room.teach();

这里的 School::Classroom 就是通过大盒子(School)找到了小盒子(Classroom)。

  • 访问权限
    • 内部类和外部类的访问权限是各自独立的。内部类可以直接访问外部类的静态成员,静态成员就像是大盒子里大家都能看到的公共物品。比如:
class School {
private:static int schoolId; // 这是外部类的静态私有成员,像公共物品
public:class Classroom {public:void setSchoolId() {schoolId = 1; // 内部类可以直接访问外部类的静态私有成员}};
};
int School::schoolId = 0;
  • 但是内部类不能直接访问外部类的非静态成员,非静态成员就像是大盒子主人自己的东西,小盒子里的东西不能直接拿。不过,如果内部类拿到了大盒子主人的钥匙(外部类对象),就可以访问了。
  • 封装性好:内部类可以把和外部类紧密相关的功能藏起来,就像把小盒子里的东西藏好,不让外面随便看到,这样能避免和外面的东西搞混,减少麻烦。
  • 独立性:虽然内部类在外部类里面,但它自己是个独立的个体,有自己的成员变量、成员函数,还有自己的访问规则,就像小盒子有自己的东西和管理方式。

2. 访问方式

外部类访问内部类

外部类要访问内部类的东西,就得先把小盒子打开,也就是创建内部类的对象。例如:

class School {
public:class Classroom {public:int studentCount;void startClass() {// 开始上课}};void useClassroom() {Classroom classRoom; // 创建内部类对象,打开小盒子classRoom.studentCount = 30; // 访问内部类的成员变量classRoom.startClass(); // 访问内部类的成员函数}
};

在 School 类的 useClassroom 函数里,创建了 Classroom 对象,然后就可以用它里面的东西了。

内部类访问外部类

内部类可以直接拿大盒子里的公共物品(外部类的静态成员)。要是想拿大盒子主人自己的东西(外部类的非静态成员),就得有主人的钥匙(外部类对象)。例如:

class School {
private:static int schoolYear; // 外部类的静态成员,公共物品int teacherCount; // 外部类的非静态成员,主人自己的东西
public:class Classroom {public:void accessSchool(School& school) {schoolYear = 2024; // 直接访问外部类的静态成员school.teacherCount = 10; // 通过外部类对象访问非静态成员}};
};
int School::schoolYear = 0;int main() {School school;School::Classroom classRoom;classRoom.accessSchool(school);return 0;
}

在 Classroom 类的 accessSchool 函数里,通过传入 School 对象,就可以访问外部类的非静态成员了。

外部访问内部类

在外面想用小盒子里的东西,就得先通过大盒子找到小盒子,也就是用外部类的作用域解析运算符 ::。例如:

class School {
public:class Classroom {public:void showInfo() {std::cout << "This is a classroom." << std::endl;}};
};int main() {School::Classroom classRoom; // 通过大盒子找到小盒子classRoom.showInfo(); // 使用小盒子里的东西return 0;
}

在 main 函数里,通过 School::Classroom 创建了内部类对象,然后调用了它的成员函数。

3. 注意事项

  • 嵌套别太深:内部类可以一层套一层,就像大盒子里有小盒子,小盒子里还能有更小的盒子。但是套太多层,自己都会迷糊,代码的可读性和可维护性就变差了。
  • 生命周期独立:内部类对象的存在和消失和外部类对象没有关系,就像小盒子里的东西什么时候拿走,和大盒子什么时候扔掉没关系。
  • 友元关系:外部类可以给内部类一把特殊的钥匙(友元关系),这样内部类就能随便拿大盒子主人自己的东西(访问外部类的私有成员)了。例如:
class School {
private:int schoolSecret; // 外部类的私有成员,主人藏起来的东西friend class Classroom; // 给 Classroom 类一把特殊钥匙
public:class Classroom {public:void knowSecret(School& school) {school.schoolSecret = 123; // 可以访问外部类的私有成员}};
};

这里 Classroom 类成了 School 类的友元类,就能访问 School 类的私有成员 schoolSecret 了。

  • sizeof(外部类)=外部类,和内部类没有任何关系 除非定义里面的类。列如:

从以上我们可以看出如果没有在A类中定义B类是不会计算B类的大小的。

六、匿名对象:“一次性” 的临时帮手

1. 定义与生命周期

定义

在 C++ 里,匿名对象就是那种没有名字的对象。想象一下,你在生活中有时候需要某个东西,只用一次,用完就扔,没必要专门给它取个名字。在编程里也是,当你只需要临时用一下某个对象,不想给它分配一个变量名来长期保存时,就可以创建匿名对象。

比如下面这个简单的类:

class Dog {
public:Dog() {cout << "给狗取名字" << endl;}void bark() {cout << "汪!汪!" << endl;}~Dog() {cout << "狗到生命结束." << endl;}
};

创建匿名对象的方式就是直接调用类的构造函数,像这样:

Dog();//无名
Dog d1;//有名字

这里的 Dog() 就是创建了一个匿名的 Dog 对象。

生命周期

匿名对象的生命周期非常短暂,它就像一个 “一次性” 的临时帮手,只用一次就会被销毁。具体来说,匿名对象的生命周期从创建开始,到包含它的语句执行结束就结束了。

看下面这个例子:

#include <iostream>
class Dog {
public:Dog() {cout << "给狗取名字" << endl;}void bark() {cout << "汪!汪!" << endl;}~Dog() {cout << "狗到生命结束。" << endl;}
};int main() {Dog().bark();cout << "这一行是在匿名狗狗使用之后。" << endl;return 0;
}

在 main 函数里,Dog().bark() 这一句先创建了一个匿名的 Dog 对象,然后调用它的 bark 方法。当这一句执行完,匿名对象的使命就完成了,它的析构函数会被调用,对象被销毁。所以输出结果会是:

给狗取名字.
汪汪!汪汪
狗到生命结束。
这一行在匿名狗使用之后。

2. 用途

作为函数参数

匿名对象可以直接作为函数的参数传递,这样可以避免创建一个临时的命名对象,让代码更简洁。

假设有一个函数用来喂狗:

void feedDog(const Dog& dog) {cout << "喂狗..." << endl;
}

在调用这个函数时,可以直接传递一个匿名对象:

feedDog(Dog());

这里创建了一个匿名的 Dog 对象并传递给 feedDog 函数,函数执行完后,匿名对象就被销毁了。

初始化对象

匿名对象可以用来初始化其他对象。比如有一个 Cat 类,并且有一个接受 Cat 对象的构造函数:

class Cat {
public:Cat() {std::cout << "给猫取名字" << std::endl;}Cat(const Cat& other) {cout << "猫生下一个跟自己一样的" << endl;}~Cat() {std::cout << "猫到生命结束" << std::endl;}
};int main() {Cat myCat = Cat();return 0;
}

在这个例子中,先创建了一个匿名的 Cat 对象,然后用它来初始化 myCat 对象。这里可能会发生拷贝构造或者编译器的优化(如拷贝省略),但不管怎样,匿名对象在初始化完成后就会被销毁。

调用类的静态成员函数

如果类有静态成员函数,也可以用匿名对象来调用。静态成员函数属于类本身,不依赖于具体的对象。

class MathUtils {
public:static int add(int a, int b) {return a + b;}
};int result = MathUtils().add(3, 5);

这里通过匿名的 MathUtils 对象调用了静态成员函数 add,计算出 3 和 5 的和。

总之,匿名对象在 C++ 里是一种很有用的特性,它能让代码更简洁,在只需要临时使用对象的场景下非常方便。但要注意它的生命周期很短,使用完就会被销毁。

七、对象拷贝优化:编译器的 “偷工减料” 技巧

1. 优化场景

  • 传值传参f1(1)会隐式构造 A 对象,编译器优化为直接构造,省去拷贝。
  • 传值返回:函数返回局部对象时,编译器可能将返回值直接构造到接收对象中,避免临时对象拷贝。

2. 关闭优化(调试用)

  • g++ 编译时加参数-fno-elide-constructors,可查看真实拷贝次数。

总结:进阶特性的 “双刃剑”

  • 初始化列表:必须掌握,尤其是引用 /const/ 非默认构造成员的初始化。
  • static 成员:用于全局统计或工具函数,类外初始化别忘记。
  • 友元与内部类:突破封装但需谨慎,避免滥用破坏代码结构。
  • 匿名对象与拷贝优化:理解生命周期和编译器行为,写出高效代码。

通过这些特性,C++ 在封装性和灵活性之间找到平衡,但记住:合适的才是最好的,复杂特性请按需使用!

 

相关文章:

C++ 类与对象(下)—— 进阶特性与底层机制解析(构造函数初始化,类型转换,static成员,友元,内部类,匿名对象)

一、构造函数初始化列表&#xff1a;给成员变量 “精准出生证明” 在 C 中&#xff0c;构造函数对成员变量的初始化方式有 初始化列表 和 函数体内赋值 两种。初始化列表是构造函数的一个重要特性&#xff0c;它允许在对象创建时对成员变量进行初始化。与在构造函数体内赋值不同…...

项目生成日志链路id,traceId

Trace 1. 注册filter package com.sc.account.config;import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration;Configuration public cla…...

SQL常见误区

查询的顺序 书写顺序 SELECT 字段列表 FROM 表名列表 WHERE 条件列表 GROUP BY 分组字段列表 HAVING 分组后条件列表 ORDER BY 排序字段列表。。他们的加载顺序 逻辑处理实际顺序 常见错误 在 WHERE 中使用 SELECT 的别名 sql – 错误示例&#xff08;WHERE 中不能使用别名…...

android zxing QrCode 库集成转竖屏适配问题

由于zxing 这个库使用比较广泛&#xff0c;所以大家也都遇到这个问题了&#xff0c;甚至最早可以追溯到十年前甚至更早&#xff0c;所以原创是谁已经无法找到&#xff0c;表明转载又需要填原文链接&#xff0c;就腆着脸标个原创了&#xff0c;不过的确不是我的原创&#xff0c;…...

实验4 mySQL查询和视图

一、实验目的 掌握SELECT语句的基本语法多表连接查询GROUP BY的使用方法。ORDER BY的使用方法。 二、实验步骤、内容、结果 实验内容&#xff1a; 实验4.1数据库的查询 目的与要求 (1)掌握SELECT语句的基本语法。 (2)掌握子查询的表示。 (3)掌握连接查询的表示。 (4)掌…...

解决用Deveco device tool无法连接local pc

原文链接&#xff1a;https://kashima19960.github.io/2025/05/05/openharmony/解决用Deveco%20device%20tool无法连接local%20pc/ 问题描述 WindowsUbuntu 环境下DevEco tool upload Hi3681开发 烧录 Local PC 箭头红一下&#xff0c;又绿了 用Deveco device tool进行upload…...

Google-chrome版本升级后sogou输入法不工作了

背景&#xff1a; 笔记本Thinkpad E450&#xff0c;操作系统Ubuntu 24.04.2 LTS&#xff0c;Chrome浏览器版本135.0.7049.114-1&#xff0c;Edge浏览器版本131.0.2903.99-1&#xff0c;输入法Sogou版本4.2.1.145 现象&#xff1a; - **正常场景**&#xff1a;Edge中可通过Ctrl…...

C++ 检查某个点是否存在于圆扇区内(Check whether a point exists in circle sector or not)

我们有一个以原点 (0, 0) 为中心的圆。作为输入&#xff0c;我们给出了圆扇区的起始角度和圆扇区的大小&#xff08;以百分比表示&#xff09;。 例子&#xff1a; 输入&#xff1a;半径 8 起始角 0 百分比 12 x 3 y 4 输出&am…...

电脑怎么分屏操作?

快捷键分屏 &#xff1a; 在打开两个窗口后&#xff0c;选中一个窗口&#xff0c;按下 “Windows 键 →” 键&#xff0c;该窗口会自动移动到屏幕右侧并占据一半空间&#xff0c;再点击需要分屏的窗口&#xff0c;即可完成分屏。若想恢复窗口为全屏&#xff0c;只需再次按下 …...

深度学习:智能助理从技术演进到全民普惠

在数字化浪潮席卷全球的今天&#xff0c;智能助理已成为人们生活与工作中不可或缺的伙伴。从简单的语音应答到如今具备复杂认知与交互能力&#xff0c;深度学习技术的持续突破&#xff0c;正推动智能助理行业迈向全新高度。深入探究其行业发展、现状、技术演进与实践&#xff0…...

哈希算法、搜索算法与二分查找算法在 C# 中的实现与应用

在计算机科学中&#xff0c;哈希算法、搜索算法和二分查找算法是三个非常基础且常用的概念。它们分别在数据存储、数据查找、以及高效检索等场景中起着至关重要的作用。在 C# 中&#xff0c;这些算法的实现和使用也十分简便。本文将详细讲解这三种算法的原理、应用以及 C# 中的…...

优化02-执行计划

Oracle 的执行计划&#xff08;Execution Plan&#xff09;是数据库优化器&#xff08;Optimizer&#xff09;为执行 SQL 语句而选择的操作路径和资源分配方案的详细描述。它记录了数据库如何访问表、索引、连接数据以及执行排序、过滤等操作的步骤。理解执行计划是性能调优的核…...

FreeRTOS菜鸟入门(十一)·信号量·二值、计数、递归以及互斥信号量的区别·优先级翻转以及继承机制详解

目录 1. 信号量的基本概念 2. 分类 2.1 二值信号量 2.2 计数信号量 2.3 互斥信号量 2.4 递归信号量 3. 应用场景 3.1 二值信号量 3.2 计数信号量 3.3 互斥信号量 3.4 递归信号量 4. 运作机制 4.1 二值信号量 4.2 计数信号量 4.3 互斥信号量 4.4…...

C++ -- 内存管理

C --内存管理 1. C/C内存分布2. C中动态内存管理3. C中动态内存管理4. 面对自定义类型5. operator new和operator delete6. new和delete的实现原理6.1 内置类型6.2 自定义类型 7. 定位new&#xff08;placement new&#xff09;7.1 底层机制7.2 本质 1. C/C内存分布 2. C中动态…...

基于muduo库实现高并发服务器

文章目录 一、项目介绍二、HTTP服务器1.概念2.Reactor模型2.1单Reactor单线程&#xff1a;单I/O多路复用业务处理2.2单Reactor多线程&#xff1a;单I/O多路复用线程池&#xff08;业务处理&#xff09;2.3多Reactor多线程&#xff1a;多I/O多路复用线程池&#xff08;业务处理&…...

开源PDF解析工具Marker深度解析

开源PDF解析工具Marker深度解析 检索增强生成&#xff08;RAG&#xff09;系统的第一步就是做 pdf 解析&#xff0c;从复杂多样的 pdf 中提取出干净准确的文本内容。现有的最优秀的开源工具有两个&#xff1a;Marker 和 MinerU。因为 Marker 是个人开发者做的&#xff0c;文档…...

Redis的内存淘汰机制

Redis的内存淘汰机制和过期策略是2个完全不同的机制&#xff0c; 过期策略指的是使用那种策略来删除过期键&#xff0c;Redis的内存淘汰机制是指&#xff1a;当Redis的运行内存已经超过设置的最大运行内存时&#xff0c;采用什么策略来删除符合条件的键值对&#xff0c;以此来保…...

我国“东数西算”工程对数据中心布局的长期影响

首席数据官高鹏律师团队 我国“东数西算”工程作为国家级战略&#xff0c;旨在优化全国算力资源配置&#xff0c;推动数字经济发展&#xff0c;其对数据中心布局的长期影响主要体现在以下几个方面&#xff1a; 1. 区域协调与资源优化配置 东部与西部分工明确&#xff1a;东部…...

CPT204 Advanced Obejct-Oriented Programming 高级面向对象编程 Pt.10 二叉搜索树

文章目录 1.二叉树&#xff08;Binary Trees&#xff09;1.1 二叉搜索树&#xff08;Binary Search Tree&#xff0c;简称BST&#xff09;1.1.1 插入操作1.1.2 搜索操作1.1.3 树的遍历&#xff08;Tree Traversal&#xff09;1.1.3.1 前序遍历&#xff08;Preorder Traversal&a…...

MinIO实现https访问

Windows下实现MinIO的https访问. 首先需要自己解决证书问题, 这里可以是个人证书 也可以是花钱买的证书. 现在使用个人开发者证书举例子。 将证书数据解压到你知道的目录之下 然后直接使用命令启动MinIO start minio.exe server --certs-dir D:\xxxxx\tools\certs …...

查看并升级Docker里面Jenkins的Java17到21版本

随着时间推移&#xff0c;java17将逐渐退出舞台&#xff0c;取而代之的是java21。Jenkins也在逐步升级淘汰java版本&#xff0c;今天教大家升级java版本。 Jenkins问题提示 Java 17 end of life in Jenkins You are running Jenkins on Java 17, support for which will end o…...

【KWDB 创作者计划】KWDB 2.2.0多模融合架构与分布式时序引擎

KWDB介绍 KWDB数据库是由开放原子开源基金会孵化的分布式多模数据库&#xff0c;专为AIoT场景设计&#xff0c;支持时序数据、关系数据和非结构化数据的统一管理。其核心架构采用多模融合引擎&#xff0c;集成列式时序存储、行式关系存储及自适应查询优化器&#xff0c;实现跨模…...

Redis的过期设置和策略

Redis设置过期时间主要有以下几个配置方式 expire key seconds 设置key在多少秒之后过期pexpire key milliseconds 设置key在多少毫秒之后过期expireat key timestamp 设置key在具体某个时间戳&#xff08;timestamp:时间戳 精确到秒&#xff09;过期pexpireat key millisecon…...

2.3 向量组

本章主要考查向量组的线性关系、秩与极大无关组、向量空间等核心内容&#xff0c;是线性代数的重要基础模块。以下从四个核心考点展开系统梳理&#xff1a; 考点一&#xff1a;向量组的线性表示 核心问题&#xff1a;如何用一组向量线性表出另一组向量&#xff1f;如何判断线性…...

协议(消息)生成

目录 协议(消息)生成主要做什么? 知识点二 制作功能前的准备工作 ​编辑​编辑 制作消息生成功能 实现效果 ​总结 上一篇中配置的XML文件可见&#xff1a; https://mpbeta.csdn.net/mp_blog/creation/editor/147647176 协议(消息)生成主要做什么? //协议生成 主要是…...

【PostgreSQL数据分析实战:从数据清洗到可视化全流程】4.5 清洗流程自动化(存储过程/定时任务)

&#x1f449; 点击关注不迷路 &#x1f449; 点击关注不迷路 &#x1f449; 点击关注不迷路 文章大纲 PostgreSQL数据清洗自动化&#xff1a;存储过程与定时任务全攻略4.5 清洗流程自动化&#xff1a;构建智能数据处理管道4.5.1 存储过程&#xff1a;复杂清洗逻辑封装4.5.1.1 …...

Python中有序序列容器的概念及其与可变性的关系

什么是有序序列容器&#xff1f; 有序序列容器是Python中一类重要的数据类型&#xff0c;它们具有以下共同特征&#xff1a; 元素有序排列&#xff1a;元素按照插入顺序存储&#xff0c;可以通过位置&#xff08;索引&#xff09;访问 可迭代&#xff1a;可以使用for循环遍历…...

数据结构实验8.1:图的基本操作

文章目录 一&#xff0c;实验目的二&#xff0c;实验内容三&#xff0c;实验要求四&#xff0c;算法分析五&#xff0c;示例代码8-1.cpp源码graph.h源码 六&#xff0c;操作步骤七&#xff0c;运行结果 一&#xff0c;实验目的 1&#xff0e;掌握图的邻接矩阵、邻接表的表示方…...

PostgreSQL 的 pg_current_wal_lsn 函数

PostgreSQL 的 pg_current_wal_lsn 函数 pg_current_wal_lsn 是 PostgreSQL 中用于获取当前预写式日志(WAL)写入位置的关键函数&#xff0c;对于数据库监控、复制管理和恢复操作至关重要。 一 基本说明 语法 pg_current_wal_lsn() RETURNS pg_lsn功能 返回当前的 WAL 写入…...

P6822 [PA 2012 Finals] Tax 题解

题目大意 可恶,我们老师竟然把紫题放到了模拟赛里。 题目传送门 原题中题意说的很清楚了。 思路 转化问题 首先先新建两条边,使原题点到点的问题转化成边到边的问题。 可以连接一条从 0 0 0 到 1 1 1,长度为 0 0 0 的边,设这条边为 0 0 0 号边。 还可以连接一条…...

Python异步编程入门:从同步到异步的思维转变

引言 作为一名开发者&#xff0c;你可能已经习惯了传统的同步编程模式——代码一行接一行地执行&#xff0c;每个操作都等待前一个操作完成。但在I/O密集型应用中&#xff0c;这种模式会导致大量时间浪费在等待上。今天&#xff0c;我们将探讨Python中的异步编程&#xff0c;这…...

【Python】使用`python-dotenv`模块管理环境变量

最近田辛老师在进行与AI有关的开发。 在开发和部署 Python 应用程序时&#xff08;要么是在某个Python环境&#xff0c;要么是在MaxKB等知识库系统&#xff09;&#xff0c;我常常需要根据不同的环境&#xff08;如开发环境、测试环境、生产环境&#xff09;使用不同的配置信息…...

破局者手册 Ⅰ:测试开发核心基础,解锁未来测试密钥!

目录 一、引入背景 二、软件测试基础概念 2.1 软件测试的定义 2.2 软件测试的重要性 2.3 软件测试的原则 三、测试类型 3.1 功能测试 3.2 接口测试 3.2.1 接口测试的概念 3.2.2 接口测试的重要性 3.2.3 接口测试的要点 3.2.4 接口测试代码示例&#xff08;Python r…...

物联网mqtt和互联网http协议区别

MQTT和HTTP是两种不同的网络协议&#xff0c;它们在以下方面存在区别&#xff1a; 一、连接方式 1.MQTT&#xff1a;基于TCP/IP协议&#xff0c;采用长连接方式。客户端与服务器建立连接后&#xff0c;会保持连接状态&#xff0c;可随时进行数据传输&#xff0c;适用于实时性…...

C++笔记之反射、Qt中的反射系统、虚幻引擎中的反射系统

C++笔记之反射、Qt中的反射系统、虚幻引擎中的反射系统 code review! 目录 C++笔记之反射、Qt中的反射系统、虚幻引擎中的反射系统 目录1. 反射基础概念 1...

提示词压缩方法总结与开源工具包

论文标题 AN EMPIRICAL STUDY ON PROMPT COMPRESSION FOR LARGE LANGUAGE MODELS 论文地址 https://arxiv.org/pdf/2505.00019 开源地址 https://github.com/3DAgentWorld/Toolkit-for-Prompt-Compression 作者背景 香港科技大学广州校区&#xff0c;华南理工大学&#…...

【AI提示词】AARRR 模型执行者

提示说明 具备完整的产品知识和数据分析能力&#xff0c;擅长通过AARRR模型优化用户生命周期管理&#xff0c;提升企业收入和市场拓展。 提示词 # Role: AARRR 模型执行者## Profile - language: 中文 - description: 具备完整的产品知识和数据分析能力&#xff0c;擅长通过…...

深入理解 Redis 的主从、哨兵与集群架构

目录 前言1 Redis 主从架构1.1 架构概述1.2 优点与应用场景1.3 局限性 2 Redis 哨兵架构2.1 架构概述2.2 高可用能力的实现2.3 局限与注意事项 3 Redis 集群架构3.1 架构概述3.2 高性能与高可用的统一3.3 限制与挑战 4 架构对比与选型建议结语 前言 在构建高性能、高可用的数据…...

基于CBOW模型的词向量训练实战:从原理到PyTorch实现

基于CBOW模型的词向量训练实战&#xff1a;从原理到PyTorch实现 在自然语言处理&#xff08;NLP&#xff09;领域&#xff0c;词向量是将单词映射为计算机可处理的数值向量的重要方式。通过词向量&#xff0c;单词之间的语义关系能够以数学形式表达&#xff0c;为后续的文本分…...

【阿里云大模型高级工程师ACP习题集】2.9 大模型应用生产实践(下篇)

练习题 【单选题】在大模型应用备案中,根据《生成式人工智能服务管理暂行办法》,已上架但未完成合规手续的应用应如何处理?( ) A. 继续运营,同时补办手续 B. 下架处理 C. 暂停部分功能,直至完成合规手续 D. 无需处理,等待监管部门通知 【多选题】在应用服务安全的应用部…...

Matlab实现CNN-BiLSTM时间序列预测未来

Matlab实现CNN-BiLSTM时间序列预测未来 目录 Matlab实现CNN-BiLSTM时间序列预测未来效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab实现CNN-BiLSTM时间序列预测未来&#xff1b; 2.运行环境Matlab2023b及以上&#xff0c;data为数据集&#xff0c;单变量时间序…...

互联网大厂Java求职面试:AI大模型与云原生架构设计深度解析

互联网大厂Java求职面试&#xff1a;AI大模型与云原生架构设计深度解析 第一轮提问&#xff1a;AI大模型与系统集成 技术总监&#xff08;张总&#xff09;&#xff1a;郑薪苦&#xff0c;你之前提到过Spring AI&#xff0c;那你能讲讲在实际项目中如何将大模型集成到系统中&…...

GD32F103C8T6多串口DMA空闲中断通信程序

以下是一个完全符合C99标准的GD32F103C8T6多串口DMA通信完整实现&#xff0c;代码经过Keil MDK验证并包含详细注释&#xff1a; #include "gd32f10x.h" #include <string.h>/* 硬件配置宏 */ #define USART_NUM 2 /* 使用2个串口 */ #define R…...

labelimg快捷键

一、核心标注快捷键 ‌W‌&#xff1a;调出标注十字架&#xff0c;开始绘制矩形框&#xff08;最常用功能&#xff09;‌A/D‌&#xff1a;切换上一张(A)或下一张(D)图片&#xff0c;实现快速导航‌Del‌&#xff1a;删除当前选中的标注框 二、文件操作快捷键 ‌CtrlS‌&…...

【深度学习-Day 6】掌握 NumPy:ndarray 创建、索引、运算与性能优化指南

Langchain系列文章目录 01-玩转LangChain&#xff1a;从模型调用到Prompt模板与输出解析的完整指南 02-玩转 LangChain Memory 模块&#xff1a;四种记忆类型详解及应用场景全覆盖 03-全面掌握 LangChain&#xff1a;从核心链条构建到动态任务分配的实战指南 04-玩转 LangChai…...

开元类双端互动组件部署实战全流程教程(第2部分:控制端协议拆解与机器人逻辑调试)

作者&#xff1a;那个写了个机器人结果自己被踢出房间的开发者 游戏逻辑房间结构参考界面 从这张图我们能看出&#xff0c;该组件按功能结构细分为多个房间&#xff0c;每个房间底注、准入标准不同&#xff0c;对应的控制模块也有层级区分。常规来说&#xff0c;一个“互动房间…...

51单片机入门教程——蜂鸣器播放天空之城

前言 本教程基于B站江协科技课程进行个人学习整理&#xff0c;专为拥有C语言基础的零基础入门51单片机新手设计。既帮助解决因时间差导致的设备迭代调试难题&#xff0c;也助力新手快速掌握51单片机核心知识&#xff0c;实现从C语言理论到单片机实践应用的高效过渡 。 目录 …...

linux 历史记录命令

命令方式 #/bin/bash #cd /tmp saveFile"tmp.log" isok"grep HISTTIMEFORMAT /etc/profile|wc -l" if [ $isok -eq 0 ] thenecho -e "#history time\nHISTFILESIZE4000\nHISTSIZE4000\nHISTTIMEFORMAT%F %T \nexport HISTTIMEFORMAT\n" >>…...

手表关于MPU6050中的功能实现

MPU6050 OV-Watch 中的睡眠和唤醒功能实现 OV-Watch 项目为 MPU6050 传感器实施了复杂的电源管理&#xff0c;以优化电池寿命&#xff0c;同时保持手腕检测和计步功能。以下是对睡眠和唤醒机制的详细分析&#xff1a; 内核休眠/唤醒功能实现 MPU6050 有两个主要功能来控制其…...

Qt中数据结构使用自定义类————附带详细示例

文章目录 C对数据结构使用自定义类1 QMap使用自定义类1.1 使用自定义类做key1.2 使用自定义类做value 2 QSet使用自定义类 参考 C对数据结构使用自定义类 1 QMap使用自定义类 1.1 使用自定义类做key QMap<key,value>中数据存入时会对存入key值的数据进行比较&#xff…...