欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 汽车 > 新车 > C++11QT复习 (十二)

C++11QT复习 (十二)

2025/4/3 18:50:11 来源:https://blog.csdn.net/m0_49013185/article/details/146935105  浏览:    关键词:C++11QT复习 (十二)

Day8-1 操作符重载再探(2025.03.31)

一、 操作符重载深入解析

1.1 操作符重载的应用场景
  1. 自定义数学类型(向量、矩阵、复数等)
  2. 实现类对象的自然语义操作
  3. 流操作定制化
1.2 Int类完整实现示例
class Int {friend std::ostream& operator<<(std::ostream& os, Int i);
public:// 构造函数Int() = default;Int(int i) : _i(i) {}// 算术运算符Int operator+(const Int& i) const { return _i + i._i; }Int operator+(int i) const { return _i + i; }// 赋值运算符Int& operator=(const Int& i) { _i = i._i; return *this; }Int& operator=(int i) { _i = i; return *this; }// 类型转换运算符operator int() const { return _i; }private:int _i = 0;
};// 流输出运算符
std::ostream& operator<<(std::ostream& os, Int i) {os << i._i;return os;
}
1.3 操作符重载关键要点
运算符类型返回值类型参数传递备注
算术运算符值类型const引用应保证不修改操作数
赋值运算符左值引用const引用需返回*this
流运算符流引用非const引用通常定义为友元
1.4 字符串操作示例
void stringDemo() {string a = "cpp";string b = a + " java";  // 调用operator+(const char*)string c = "python " + b; // 调用operator+(const char*, string)
}
1.5示例代码
#include <iostream>/*
操作符重载常见的操作符:
1. 数值运算 + - * /
2. 赋值 =
3. 流操作符 << >>
4. 其他,比如取地址&,解引用*(和乘法一样),打开域::,类指针成员访问->,类实例成员访问. 等编译器为内置类型定义了基本的操作符,比如以上的 1 & 2 & 3 项每个操作符只是个函数,即 + 运算符只是个以 operator+ 为函数名的普通函数而已我们需要用到操作符重载的地方:
1. 实现一些自定义的数学类型,比如 向量,矩阵,复数等,这些类也可以进行数值运算,那就需要为这些类定义这些操作符
2. 拷贝赋值运算符和移动赋值运算符是编译器默认生成的两个重要函数,我们理应掌握这两个函数常见地,我们更多需要的是重载数值运算符,赋值和流操作符对于数值运算符,计算的结果是个右值,意思是计算结果的返回值是不可以赋值的,比如
int a = 1;
int b = 2;
(a + b) = 100; // 报错,右值不能赋值
对于自定义类型,比如T
T a;
T b;
要支持 a + b,有两种定义方法,要么定义T类的成员函数,要么定义类外的普通函数
class T
{
public:T operator+(const T& r); // 默认该类本身是左操作数,传入的参数是右操作数
};
或者
T operator(const T& l, const T& r); // 分别传入左操作数和右操作数对于赋值运算符,计算的结果是个左值,比如
int a = 1;
(a = 2) = 3; // 正确,a = 2的返回值就是 a 本身,是个左值,可以继续赋值
对于自定义类型T,比如
T a;
T b;
要支持 a = b,一般就定义拷贝赋值运算符,即
class T
{
public:// 这是个编译器会默认生成的函数,非常重要// 请牢记它的样子,返回类的左值引用,参数是类的常量引用// 返回值需要返回 *this,即它本身,必须是个引用,如果不是引用会发生拷贝,就和当前这个类对象没关系了// 返回值还要支持修改,即左值属性,所以必须是左值引用// 传入参数不应该发生拷贝,即传引用,也不可以修改,所以使用常量引用T& operator=(const T& r);
};流操作符的左边一般是一个输出流对象,右边是某种类型,计算结果仍是流对象本身,比如
int a = 1;
int b = 2;
(std::cout << a) << b; // std名称空间中的cout是个全局变量,cout << a 后返回就是 cout 本身,可以继续 << b
对于自定义类型T,没法定义成员函数,因为左操作数不是T,所以一般定义类外函数
std::ostream& operator<<(std::ostream& os, T r);现在我们定一个自己的int,名为Int,希望它表现得像int
*/
// 第一个版本
namespace v1
{
class Int
{// 由于流运算符要访问 _i,需要声明为友元,友元可以突破访问控制,访问类的 private 成员// 友元声明无所谓访问控制,放在 public / private 下面都可friend std::ostream& operator<<(std::ostream& os, Int r);private:int _i;public:// 可以使用同类型变量构造(特殊函数,拷贝构造函数)// 为了支持 Int a = b; // b也是IntInt(const Int& r) : _i(r._i) {} // 编译器会生成这个版本,可以不实现,或者直接 = default// 也可以使用普通int构造// 为了支持 Int a = 1;Int(const int& r) : _i(r) {}// 它应该支持和同类型变量相加// 让 a + b 可以工作Int operator+(const Int& r) const { return Int(_i + r._i); }// 也应该和普通int相加// 让 a + 1 可以工作Int operator+(const int& r) const { return Int(_i + r); }// - * / 这里就省略了// 可以把同类型变量赋给它(特殊函数,拷贝赋值运算符)// 让 a = b 工作Int& operator=(const Int& r) { _i = r._i; return *this; } // 编译器会生成这个版本,可以不实现,或者直接 = default// 也可以把普通int赋给它// 让 a = 1 工作Int& operator=(const int& r) { _i = r; return *this; }
};// 流运算符
// << 会改变流,所以要传左值引用,把r写入流后,继续把流以左值引用传出去
// 流不可以拷贝,其拷贝构造函数和拷贝赋值运算符被定义为 delete,所以只能以引用传递
std::ostream& operator<<(std::ostream& os, Int r)
{os << r._i;return os;
}
}void demo1()
{using namespace v1;Int a = 1;Int b = 2;b = a;Int c = a + b;std::cout << c << std::endl;
}/*
为了少写点,我们希望 Int 能隐式转换为 int,这样能少写一半成员函数,
即调用 operator+(Int r) 时先把 Int转为 int,再调用 operator+(int r)
为了支持转换,需要定义类型转换操作符,比如想把 T1 转为 T2
class T1
{
public:operator T2() const;
};
*/
//第二个版本
namespace v2
{class Int
{friend std::ostream& operator<<(std::ostream& os, Int r);private:int _i;public:Int(const int& r) : _i(r) {}Int operator+(const int& r) const { return Int(_i + r); }Int& operator=(const int& r) { _i = r; return *this; }// 让 Int 在需要转换的地方自动转为 int,所以以上函数的 Int 参数版本都被删除了operator int() const { return _i; }
};std::ostream& operator<<(std::ostream& os, Int r)
{os << r._i;return os;
}}void demo2()
{using namespace v2;Int a = 1;Int b = 2;b = a;Int c = a + b;std::cout << c << std::endl;// 由于定义类型转换操作符,这句可以编译通过int d = c;
}int main()
{demo1();demo2();return 0;
}

二、 可调用对象详解

2.1 可调用对象类型
  1. 普通函数
  2. 函数指针
  3. 重载operator()的类对象(函数对象)
  4. lambda表达式
  5. std::function对象
2.2 函数对象实现
class Callable {
public:// 函数调用运算符重载void operator()() { cout << "Function call" << endl; }bool operator()(int a, int b) { return a < b; }// 静态成员函数static bool compare(int a, int b) { return a > b; }
};
2.3 实际应用场景

排序算法示例:

int main() {vector<int> v = {5, 3, 7, 1};// 使用函数对象Callable comp;sort(v.begin(), v.end(), comp);// 使用lambda表达式sort(v.begin(), v.end(), [](int a, int b) {return a > b;});// 使用静态成员函数sort(v.begin(), v.end(), Callable::compare);
}

多线程示例:

void threadDemo() {Callable c;// 使用成员函数thread t1(&Callable::sum, &c, 100);// 使用函数对象thread t2(c);// 使用lambdathread t3([](){cout << "Lambda thread" << endl;});
}

附录:测试代码

void testAll() {// 操作符重载测试Int a = 10;Int b = a + 5;cout << "Result: " << b << endl;// 可调用对象测试Callable callable;callable();cout << "Compare: " << callable(3, 5) << endl;
}
2.4 最佳实践建议
  1. 算术运算符应定义为非成员函数以支持对称性
  2. 赋值运算符必须定义为成员函数
  3. 流运算符应定义为友元非成员函数
  4. 函数对象应设计为无状态(或明确管理状态)
  5. lambda表达式适合简单的一次性操作
2.5完整示例代码
/*
可调用对象就是可以用括号并传参数调用的东西
函数就是可调用对象
类对象重载括号运算符 operator() 也可以成为可调用对象
*/#include <iostream>
#include <algorithm>
#include <thread>// 关于算法库和线程库的细节,后面再说class CallableClass
{
public:void test(){std::cout << "test" << std::endl;}// 重载 operator() 使得该类成为可调用对象void operator()(){std::cout << "Callable" << std::endl;}bool operator()(int l, int r){return l < r;}static bool compare(int l, int r){return l > r;}void operator()(int start){int sum = 0;for (int i = start; i <= start + 100; ++i){sum += i;}std::cout << sum << std::endl;}void sum(int start){int sum = 0;for (int i = start; i <= start + 100; ++i){sum += i;}std::cout << sum << std::endl;}
};void thread_func(int start)
{int sum = 0;for (int i = start; i <= start + 100; ++i){sum += i;}std::cout << sum << std::endl;
}bool compare(int l, int r)
{// 左边的数大于右边的数是排序的依据,只有满足这个要求,函数才返回 truereturn l > r;
}int main()
{CallableClass c;c.test();c(); // 调用 operator()// 使用 auto 自动推断类型,因为 lambda 表达式的类型比较复杂// 本质上是一个重载了 operator() 的类auto lambda_compare = [](int l, int r) -> bool{return l > r;};int array[10] { 2, 4, 5, 6, 9, 8, 1, 3, 7, 0 };// std::sort// 第一个参数是数组的首元素的地址// 第二个参数是数组最后一个元素的下一个位置的地址// 第三个参数是一个可调用对象,接受两个参数,参数类型是要排序的元素的类型,返回bool值// 使用普通函数作为可调用对象std::sort(array, array + 10, compare);// 使用重载了operator()的类实例作为可调用对象std::sort(array, array + 10, c);// 使用类的静态函数作为可调用对象std::sort(array, array + 10, CallableClass::compare);// 使用 lambda 表达式(左值)作为可调用对象std::sort(array, array + 10, lambda_compare);// 就地写一个 lambda 表达式(右值)作为可调用对象std::sort(array, array + 10, [](int l, int r) { return l > r; });// C++11新语法 范围forfor (int i : array){std::cout << i << ' ';}std::cout << std::endl;// 使用类实例和非静态成员函数作为可调用对象std::thread t1(&CallableClass::sum, c, 1);t1.join();// 使用重载了operator()的类实例作为可调用对象std::thread t2(c, 1);t2.join();// 使用普通函数作为可调用对象std::thread t3(thread_func, 1);t3.join();// 同样,可以使用 lambda 表达式作为可调用对象return 0;
}

Day8-2 拓展:C++ 中创建线程并传入类类型的正确写法

在 C++ 中创建线程并传入类类型有几种正确的方式,主要取决于你想如何传递类实例以及如何使用它。以下是几种常见的正确写法:

1. 传递成员函数(使用对象指针)

#include <iostream>
#include <thread>class MyClass {
public:void memberFunction(int x) {std::cout << "Member function called with " << x << " (thread ID: " << std::this_thread::get_id() << ")\n";}
};int main() {MyClass obj;std::thread t(&MyClass::memberFunction, &obj, 10);t.join();return 0;
}

2. 传递 lambda 表达式捕获对象

#include <iostream>
#include <thread>class MyClass {
public:void operator()(int x) const {std::cout << "Functor called with " << x << " (thread ID: " << std::this_thread::get_id() << ")\n";}
};int main() {MyClass obj;std::thread t([&obj]() { obj(20); });t.join();return 0;
}

3. 传递临时对象(移动语义)

#include <iostream>
#include <thread>
#include <memory>class MyClass {
public:void operator()() const {std::cout << "Temporary object called"<< " (thread ID: " << std::this_thread::get_id() << ")\n";}
};int main() {std::thread t(MyClass());  // 注意:这里可能会被解析为函数声明// 更好的写法:// std::thread t{MyClass()};// 或// std::thread t((MyClass()));t.join();return 0;
}

4. 使用 std::ref 传递引用

#include <iostream>
#include <thread>
#include <functional>class MyClass {
public:void operator()() {std::cout << "Object called by reference"<< " (thread ID: " << std::this_thread::get_id() << ")\n";}
};int main() {MyClass obj;std::thread t(std::ref(obj));t.join();return 0;
}

5. 使用智能指针

#include <iostream>
#include <thread>
#include <memory>class MyClass {
public:void operator()() {std::cout << "Shared object called"<< " (thread ID: " << std::this_thread::get_id() << ")\n";}
};int main() {auto obj = std::make_shared<MyClass>();std::thread t([obj]() { (*obj)(); });t.join();return 0;
}

注意事项

  1. 线程的生命周期必须长于或等于它所引用的对象
  2. 如果需要共享对象,确保线程安全性(使用互斥锁等)
  3. 避免悬垂引用(不要传递局部变量的引用给可能比它生命周期长的线程)
  4. 考虑使用 join() 或 detach() 明确线程的结束方式

选择哪种方式取决于你的具体需求,如对象生命周期管理、是否需要修改对象状态等。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

热搜词