【C++】面向对象高级开发 | 内联函数、深拷贝、类型转换及更多特性解析

  各位小伙伴们大家好,本篇博客是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);
}
  1. __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对象,支持链式调用。
  2. 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() constFraction 对象转换为 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 默认是 publicclass 默认是 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; }
};
  1. __list_node<T>
    结构体定义双向链表的节点,包括 prevnext 指针和 data 数据。

  2. __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 表示 Pairfirst 的类型。
  • operator () 接受一个 Pair 类型的参数 x ,并返回 x.first
template <class Pair>
struct select2nd {
    const typename Pair::second_type& operator()(const Pair& x) const 
    { return x.second; }
};

select2ndselect1st 类似,但它用于提取 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>

  • firstsecond 分别存储两个值。

命名空间经验谈

命名空间

命名空间 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>>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

清流君

感恩有您,共创未来,愿美好常伴

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值