文章目录
各位小伙伴们大家好,本篇博客是C++面向对象高级开发,内容整理自侯捷老师的C++面向对象高级开发(下)的课程,作为博主的学习笔记,分享给大家共同学习。
内联函数
inline complex& __doapl(complex* ths, const complex& r) {
ths->re += r.re;
ths->im += r.im;
return *ths;
}
inline complex& complex::operator += (const complex& r) {
return __doapl(this, r);
}
-
__doapl
函数:inline
关键字:建议编译器将该函数作为内联函数处理,以减少函数调用的开销,提高执行效率。- 函数参数:
complex* ths
:指向complex
对象的指针,表示要进行加法操作的目标对象。const complex& r
:对complex
对象的常量引用,表示加数。使用引用避免复制对象,常量引用确保r
不会被修改。
- 函数体:
ths->re += r.re;
:将r
的实部加到ths
的实部。ths->im += r.im;
:将r
的虚部加到ths
的虚部。return *ths;
:返回修改后的ths
对象,支持链式调用。
-
complex::operator +=
函数:inline
关键字:同样建议编译器将该成员函数作为内联函数处理。- 函数声明:
complex& complex::operator += (const complex& r)
,这是complex
类的成员函数,用于重载+=
运算符。 - 函数体:
return __doapl(this, r);
,调用__doapl
函数,将当前对象的指针this
和参数r
传递进去,返回__doapl
的结果。this
指针指向调用operator +=
的complex
对象。
这两段代码实现了complex
类对象的加法赋值运算。__doapl
函数负责实际的加法操作,而operator +=
函数则通过调用__doapl
来完成+=
运算符的重载。通过使用内联函数、指针和引用,代码在保证功能的同时优化了性能。
__doapl是复数加法赋值的底层实现函数。双下划线开头在C++中属于编译器保留命名(如__FILE__这类系统宏),用户代码通常应避免这种命名方式。
深拷贝三件套
拷贝构造
// 深拷贝构造函数
complex::complex(const complex& other) {
m_data = new double(*other.m_data); // 创建新内存,复制值
}
拷贝赋值
// 安全拷贝赋值运算符
complex& complex::operator=(const complex& other) {
if(this != &other) { // 1. 防止自赋值
delete m_data; // 2. 释放旧内存
m_data = new double(*other.m_data); // 3. 分配新内存
}
return *this;
}
析构函数
// 析构函数
complex::~complex() {
delete m_data; // 安全释放专属内存
}
字符串的拷贝构造
对于字符串的拷贝构造,需要指定大小
class String {
private:
char* m_data; // 用于存储字符串数据的指针
public:
// 构造函数
String(const char* str = "") {
m_data = new char[strlen(str) + 1]; // 分配内存
strcpy(m_data, str); // 拷贝字符串
}
// 析构函数
~String() {
delete[] m_data; // 释放内存
}
// 赋值运算符重载
String& operator=(const String& str) {
if (this == &str) { // 检测自我赋值
return *this;
}
delete[] m_data; // 释放原有内存
m_data = new char[strlen(str.m_data) + 1]; // 分配新内存
strcpy(m_data, str.m_data); // 拷贝字符串
return *this;
}
// 获取字符串的C风格表示
const char* c_str() const {
return m_data;
}
};
new
:先分配内存,再调用构造函数delete
:先调用析构函数,再释放内存
静态成员函数不可访问私有成员变量,不隐含 this
指针。
转换函数
对象的类型之间进行转换
#include <iostream>
class Fraction {
public:
Fraction(int num, int den = 1) : m_numerator(num), m_denominator(den) {}
operator double() const {
return (double)m_numerator / m_denominator;
}
private:
int m_numerator; // 分子
int m_denominator; // 分母
};
int main() {
Fraction f(3, 4);
double d = f;
std::cout << "The double value of the fraction is: " << d << std::endl;
return 0;
}
-
类型转换运算符
operator double() const
将Fraction
对象转换为double
类型,执行浮点数除法以确保精度 -
转换函数,要以
operator
开头,函数名称为需要转成的类型,不可以有参数。前面不需要写返回类型,因为 C++ 会自动返回函数名称这个类型。 -
转换函数通常后面有
const
,即不需要改变数据则要加const
。 -
写好之后,在将
Fraction
对象转成double
的时候,会调用我们写好的转换函数。
非显式单参数构造函数
class Fraction {
public:
// 非显式单参数构造函数
Fraction(int num, int den = 1) : m_numerator(num), m_denominator(den) {}
// 重载加法运算符
Fraction operator+(const Fraction& f) const {
int num = m_numerator * f.m_denominator + f.m_numerator * m_denominator;
int den = m_denominator * f.m_denominator;
return Fraction(num, den);
}
private:
int m_numerator; // 分子
int m_denominator; // 分母
};
这是一个非显式单参数构造函数(non-explicit-one-argument ctor
):支持隐式类型转换,例如将 int
转换为 Fraction
。
-
不是转换函数,而是重载了+操作符
-
重载之后的+是分数+分数,编译器处理 d 2 = f + 4 d 2 = f + 4 d2=f+4 的时候,发现右边不是分数,则看 4 4 4 能否转换成分数。
-
如果需要禁止隐式转换,可以在构造函数前添加
explicit
关键字
注意:如果转换函数和+操作符的重载这两个共存了,编译器就不知道该调用哪个了。(不知道把分数转为 double
还是把 int
转为分数)
显式单参数构造函数
构造函数加上 explicit
之后,表示这个构造函数只能在构造的时候使用,不会在转换类型时使用了。
#include <iostream>
class Fraction {
public:
// 带默认参数的构造函数,explicit 关键字防止隐式转换
explicit Fraction(int num, int den = 1) : m_numerator(num), m_denominator(den) {}
// 类型转换函数,将 Fraction 对象转换为 double 类型
operator double() const {
return static_cast<double>(m_numerator) / m_denominator;
}
// 重载加法运算符,用于两个 Fraction 对象相加
Fraction operator+(const Fraction& f) const {
int new_num = m_numerator * f.m_denominator + f.m_numerator * m_denominator;
int new_den = m_denominator * f.m_denominator;
return Fraction(new_num, new_den);
}
// 打印分数
void print() const {
std::cout << m_numerator << "/" << m_denominator << std::endl;
}
private:
int m_numerator; // 分子
int m_denominator; // 分母
};
int main() {
Fraction f1(1, 2); // 创建分数对象 f1,值为 1/2
Fraction f2(1, 3); // 创建分数对象 f2,值为 1/3
Fraction sum = f1 + f2; // 使用重载的加法运算符相加
double sum_d = sum; // 将结果转换为 double 类型
std::cout << "f1: ";
f1.print();
std::cout << "f2: ";
f2.print();
std::cout << "Sum (as fraction): ";
sum.print();
std::cout << "Sum (as double): " << sum_d << std::endl;
return 0;
}
explicit
关键字:防止隐式类型转换。例如, Fraction f = 5
; 会报错,因为 explicit
禁止将整数
5
5
5 隐式转换为 Fraction ( 5 , 1 )
。
默认参数:分母 den
默认值为
1
1
1 ,因此 Fraction f(3)
; 等价于 Fraction f (3,1)
;。
将 Fraction
对象转换为 double
类型。例如, Fraction f ( 3 , 4 )
; double d = f
; 会将
f
f
f 转换为
0.75
0.75
0.75 。通过将分子强制转换为 double
类型,确保除法结果为浮点数。
运行结果:
f1: 1/2
f2: 1/3
Sum (as fraction): 5/6
Sum (as double): 0.833333
智能指针
其实是设计了一个像指针的类。
#include <iostream>
// 定义一个简单的类用于演示
struct Foo {
void method() {
std::cout << "This is a method of Foo." << std::endl;
}
};
// 简化版的 shared_ptr 实现
template<class T>
class shared_ptr {
public:
// 解引用操作符重载
T& operator*() const {
return *px;
}
// 箭头操作符重载
T* operator->() const {
return px;
}
// 构造函数,接受一个普通指针
shared_ptr(T* p) : px(p) {}
// 析构函数,释放资源
~shared_ptr() {
delete px;
}
private:
T* px; // 指向实际对象的指针
};
// 使用示例
int main() {
shared_ptr<Foo> sp(new Foo); // 创建 shared_ptr 对象,管理一个 Foo 对象
sp->method(); // 通过箭头操作符调用 Foo 的成员函数
return 0;
}
智能指针的概念
shared_ptr
是一种智能指针,用于自动管理动态分配对象的生命周期,完成比指针更多的工作,一般都是包着一层普通指针。- 在简化版实现中,
shared_ptr
通过px
成员变量管理对象的指针,并在析构时释放资源。 - 实际的标准库实现中,
shared_ptr
还会使用引用计数机制来支持多个指针共享同一对象。
操作符重载
*操作符和->操作符都需要重载。
operator*()
:解引用操作符重载。当对shared_ptr
对象使用 * 时(如 *sp
),会返回指向对象的引用,使其行为类似于普通指针。operator->()
:箭头操作符重载。当对shared_ptr
对象使用->
时(如sp->method()
),会返回指向对象的指针,从而可以直接调用对象的成员函数或访问成员变量。
使用箭头操作符重载调用sp->
的时候,实际上内部重载操作符,将内部的普通指针px
返回出来,然后px
可以继续使用->
来完成。相当于这个->
符号用了两次。
结构体
Foo
是一个简单的结构体,包含一个成员函数 method
,用于演示 shared_ptr
如何与自定义类型配合工作。
结构体和类在
C
C
C ++中非常相似,唯一的区别是默认访问权限( struct
默认是 public
, class
默认是 private
)。
迭代器
双向链表节点
template <class T>
struct __list_node {
void* prev;
void* next;
T data;
};
__list_node 结构体定义了一个双向链表的节点:
prev
指向前一个节点(void *
用于通用指针转换)。next
指向下一个节点。data
存储链表中的数据,类型为 T T T 。
双向链表迭代器
template<class T, class Ref, class Ptr>
struct __list_iterator {
typedef __list_iterator<T, Ref, Ptr> self; //当前迭代器的类型。
typedef Ptr pointer; //指针类型(通常是 T*)
typedef Ref reference; //引用类型(通常是 T&)
typedef __list_node<T>* link_type; //指向 __list_node<T> 的指针类型
link_type node; //当前迭代器指向的 __list_node<T>
//迭代器的比较运算,比较两个迭代器是否指向同一个节点
bool operator==(const self& x) const { return node == x.node; }
bool operator!=(const self& x) const { return node != x.node; }
//迭代器的解引用运算
reference operator*() const { return (*node).data; }
//返回当前 node 所指向的节点的 data,相当于 (*node).data
pointer operator->() const { return &(operator*()); }
//返回 data 的地址,利用 operator*() 取值后再取地址
// 迭代器的前后移动
self& operator++() { node = (link_type)((*node).next); return *this; }
self operator++(int) { self tmp = *this; ++*this; return tmp; }
self& operator--() { node = (link_type)((*node).prev); return *this; }
self operator--(int) { self tmp = *this; --*this; return tmp; }
};
-
__list_node<T>
:
结构体定义双向链表的节点,包括prev
、next
指针和data
数据。 -
__list_iterator<T, Ref, Ptr>
:
作用类似std::list<T>::iterator
,用于遍历__list_node<T>
组成的双向链表。
这个 __list_iterator
结构体本质上是 指针封装类,用于操作 __list_node
组成的双向链表,类似 std::list <T>::iterator
。
示例
list<Foo> ite;
*ite; // 调用 operator*(),获得一个Foo object
ite->method(); // 调用 operator->(),即Foo::method()
ite->method();
相当于 (*ite).method();
,还相当于(&(*ite))->method();
仿函数
identity 结构体
template <class T>
struct identity {
const T& operator()(const T& x) const { return x; }
};
-
identity
是一个 仿函数(functor
),也称为 函数对象,它的operator
() 仅仅返回传入的参数。 -
它的作用类似于 恒等函数(
Identity
Function
),在泛型编程(如STL
算法)中常用于默认的值提取操作。
示例
identity<int> id;
int a = 42;
std::cout << id(a); // 输出 42
select1st 与 select2nd 结构体
template <class Pair>
struct select1st {
const typename Pair::first_type& operator()(const Pair& x) const
{ return x.first; }
};
select1st
是一个 函数对象,用于从Pair
类型对象中提取first
成员。Pair
::first_type
表示Pair
中first
的类型。operator
() 接受一个Pair
类型的参数x
,并返回x.first
。
template <class Pair>
struct select2nd {
const typename Pair::second_type& operator()(const Pair& x) const
{ return x.second; }
};
select2nd
和 select1st
类似,但它用于提取 second
成员。
示例
std::pair<int, double> p(10, 3.14);
select1st<std::pair<int, double>> s1;
select2nd<std::pair<int, double>> s2;
std::cout << s1(p); // 输出 10
std::cout << s2(p); // 输出 3.14
pair 结构体
template <class T1, class T2>
struct pair {
T1 first;
T2 second;
pair() : first(T1()), second(T2()) {} //默认构造函数
pair(const T1& a, const T2& b) : first(a), second(b) {} //带参数构造函数
// 省略其余代码
};
-
pair
结构体定义了一个简单的 二元组(Tuple
),类似于std::pair<T1, T2>
。 -
first
和second
分别存储两个值。
命名空间经验谈
命名空间
命名空间 namespace
可以将函数、类、变量等组织到不同的作用域中,防止命名冲突。
通过 namespace xx { ... }
定义,使用 xx::something
调用命名空间中的成员。
#include <iostream>
#include <memory> // 包含智能指针(std::shared_ptr, std::unique_ptr 等)
using namespace std; //将标准库中的标识符(如 cout、string 等)直接暴露到全局命名空间中。
// 一般在大型项目中不推荐在全局使用 using namespace std;
// 这里仅作演示
//----------------------------------------------------------
// 命名空间 jj01
namespace jj01 {
// 演示一个函数:test_member_template
void test_member_template() {
// ... (测试 member template 的代码或示例)
cout << "[jj01::test_member_template()] is called.\n";
}
}
//----------------------------------------------------------
// 命名空间 jj02
namespace jj02 {
// template 别名(type alias template)
template <typename T>
using Lst = list<T, allocator<T>>;// 将 list<T, allocator<T>> 简写成 Lst<T>
// 演示一个函数:test_template_template_param
void test_template_template_param() {
// ... (测试 template template parameter 的代码或示例)
cout << "[jj02::test_template_template_param()] is called.\n";
}
}
//----------------------------------------------------------
int main(int argc, char** argv) {
// 调用 jj01 命名空间下的函数
jj01::test_member_template();
// 调用 jj02 命名空间下的函数
jj02::test_template_template_param();
return 0;
}
使用 :: 作用域运算符来调用特定命名空间下的函数。
模板别名
模板别名( Template
Alias
)是
C
C
C ++
11
11
11 引入的新特性,通过 using
关键字将一个复杂的模板类型重命名为一个更简短、更直观的名字。
在命名空间 jj02
中,首先定义了一个 模板别名 Lst
:
template <typename T>
using Lst = list<T, allocator<T>>;
这表示:当我们写 Lst<int>
时,就等价于 list<int, allocator<int>>
。