Featured image of post C++

C++

C++和Java一样是基于面向对象的(面向过程和面向对象混编 —— 因为C++兼容C),关注的是类对象以及对象间的关系,将一件事情拆分成不同的对象,靠对象之间的交互完成,体现到代码层面就是类的设计及类的关系

this指针

类的引入

C++兼容了C中结构体的用法,同时struct在C++中也升级成了

C++类和结构体不同的是,除了可以定义变量,还可以定义方法/函数

用结构体定义的学生类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include<string.h>
#include<iostream>
using namespace std;
struct Student {
	//成员变量,加_是为了区分
	char _name[20];
	int _age;
	//成员函数/方法
	void Init(const char* name, int age) {
		strcpy(_name, name);
		_age = age;
	}
	void Print() {
		cout << _name << " " << _age << endl;
	}
};
int main() {
	struct Student s1;	//C++兼容C
	Student s2;		//可以这样写,Student是类名,也是类型
	s1.Init("小边", 19);	//成员函数采用内平栈
	s1.Print();		//函数传参时,会默认传this指针
	s2.Init("王巨龙", 19);
	s2.Print();
	return 0;
}

对于上面结构体的定义,C++中更喜欢用class来代替

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
class Date {
public:
	void Init(int year, int month, int day)	{
		_year = year;
		_month = month;
		_day = day;
	}
	void Print() {
		cout << _year << "/" << _month << "/" << _day << endl;
	}
private:
	// 这⾥只是声明,没有开空间 
	int _year;
	int _month;
	int _day;
};

调用约定*

C++新的调用约定__thiscall

  1. 32位系统(x86)

类成员函数:通常使用 __thiscall 约定

  • this 指针通过 ECX 寄存器 传递
  • 其他参数从右向左压栈
  • 被调用函数负责清理栈
  1. 64位系统(x64)
  • 统一使用 快速调用约定
    • this 指针作为隐含的第一个参数,通过 RCX 寄存器 传递
    • 前4个参数使用寄存器(RCX, RDX, R8, R9),其余压栈
    • 调用者负责清理栈空间

类的访问限定符和封装

封装

众所周知,【面向对象的三大特性】:封装、继承、多态

在类和对象阶段,我们只研究类的封装特性,什么是封装?

  1. 属性和方法都封装到一个类里面
  2. 访问限定符 —— 可以给其他类访问的成员定义成公有,不想被其他类访问的成员定义成私有/保护

访问限定符*

C++实现封装的方式,用类将对象的属性和方法结合在一起,让对象更加完善,通过访问权限选择性的将其接口提供给外部的用户使用

  1. 公有:public修饰的成员所以类均可访问
  2. 私有和保护:private修饰的成员仅类内部访问,protected类内部和子类可访问
  3. class默认访问权限privatestruct的为public(因为struct要兼容C) —— 一般定义类的时候,建议明确访问修饰限定符,不要指着class/struct默认限定符
  4. 访问权限的作用域从这个访问限定符出现的位置开始,到下一个访问限定符出现为止

类的定义*

定义了一个新的作用。类的所有成员都在类的作用域中;在类外定义成员,需要::作用解析符指明成员属于哪个类域

我们可以在头文件中声明一个类,并在.cpp文件中定义类

mystack.h

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
#pragma once
class Stack {
public:
	void stackInit();
	void stackPush(int x);
	void stackPop();
	int stackTop();

private:
	int* _a;
	int _top;
	int _capacity;
};

mytest.cpp

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#include <cassert>
#include <iostream>
#include "mystack.h"
using namespace std;
void Stack::stackInit() {
    _a = (int*)malloc(sizeof(int) * 10);
    _top = _capacity = 0;
}
void Stack::stackPush(int x) {
    _a[_top++] = x;
}
void Stack::stackPop() {
    _top--;
}
int Stack::stackTop() {
    assert(_top > 0);
    return _a[_top - 1];
}
int main() {
    Stack st;	//对象示例化(栈上分配)和Java不同,不需要new
	st.stackInit();
	st.stackPush(1);
	int a = st.stackTop();
	cout << a << endl;
    /*下面是指针实现(堆上分配)
    Stack* st = new Stack;
    st->stackInit();
    st->stackPush(1);
    int a = st->stackTop();
    cout << a << endl;
    */
}

类及对象大小

我们用下面代码查看(64位编译器指针为8B,32位为4B)

1
2
	cout << sizeof(Stack) << endl;
	cout << sizeof(st) << endl;

计算类的对象的大小 —— 只看成员变量,且要考虑内存对齐,C++内存对齐规则和C一样

  • 注意,空类比较特殊,编译器给了空类一个字节来标识这个类。不存储有效数据,只是为了空间占位,标识对象存在

内存对齐

可以通过#pragma pack(n)来设定变量以n字节对齐方式

类对象的存储:和Java一样,C++只保存成员变量成员函数放在公共的代码段

  1. 第一个成员在与结构体偏移量为0的地址处。

  2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。

    注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。(VS中默认的对齐数为8)

  3. 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。

  4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

类成员函数的this指针

先定义一个日期类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Date{
public:	
    void InitDate(int year,int month,int day) {	
        _year = year;	//隐含this指针	
        _month = month;		//相当于this->_month = month
        _day = day;	
    }	
    void PrintDate() {
        cout << _year << "-" << _month << "-" << _day << endl;	
    }
private:	
    int _year;	
    int _month;	
    int _day;
};
int main() {
    Date d1;	
    Date d2;	
    d1.InitDate(2003, 10, 13);	
    d2.InitDate(2005, 06, 07);
    d1.PrintDate();	//这里都会传入Data* const this参数
    d2.PrintDate();	//相当于&d2
    return 0;
}

C++通过引入this指针:C++编译器给每个“非静态的成员函数”增加了一个隐藏的指针参数,让该指针指向当前对象(函数运行时调用该函数的对象),在函数体中所有成员变量的操作,都是通过该指针去访问,只不过所有的操作对用户是透明的,即用户不需要来传递,编译器自动完成this指针本身不能被修改

类的默认成员函数

任何一个类即使是空类,都会自动生成下面6个默认成员函数,这就是C++比较复杂的初始化机制

构造函数*

构造函数特殊的成员函数。注意,构造函数的虽然名称叫构造,但是构造函数的主要任务并不是开空间创建对象**,而是**初始化对象

构造函数特点

  1. 函数名和类名相同
  2. 无返回值
  3. 对象实例化时,编译器自动调用对应的构造函数
  4. 构造函数可以重载
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Date {
public:
	//1.无参构造函数
	Date() {
		_year = 0;
		_month = 1;
		_day = 1;
	}
	//2.带参构造函数 - 初始化成指定值
	Date(int year, int month, int day) {
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};

int main() {
	//对象实例化时,自动调用
	Date d1;	//调用无参构造函数
	Date d2(2003, 10, 13);
	return 0;
}

但是如果在类中没有写构造函数,则编译器会默认生成一个无参构造函数

默认构造函数有三种 —— 无参构造函数、全缺省构造函数、编译器默认生成的构造函数,而一个类只能有一个

析构函数*

与构造函数功能相反,对象在销毁时会自动调用析构函数,完成对象的一些资源清理工作

析构函数特点

  1. 析构函数名是在类名前加上字符~
  2. 无参数无返回值
  3. 一个类只能有一个析构函数,不能重载
  4. 对象生命周期结束时,编译器自动调用析构函数

同样以Data类为例:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
class Date {
public:
    Date(int year = 2005, int month = 6, int day = 7) {	//全缺省构造函数
		_year = year;
		_month = month;
		_day = day;
	}
	~Date() {
		cout << "~Date()" << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};
int main() {
	Date d1;
	return 0;
}

当然,该类中的成员都是在栈上的,不需要清理资源;若有数组成员(在堆上分配空间),则需要析构函数了,比如前面实现的栈类

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Stack {
public:
	Stack(int capacity = 4) {	//构造函数
		_a = (int*)malloc(sizeof(int)* capacity);
		if (_a == nullptr) {
			cout << "malloc failed" << endl;
			exit(-1);
		}
		_top = 0;
		_capacity = capacity;
	}
	~Stack() {	//析构函数
		free(_a);
		_a = nullptr;
		_top = _capacity = 0;
	}
private:
	int* _a;
	int _top;
	int _capacity;
};
int main() {
	Stack st1;
	Stack st2(20);
	return 0;
}

注意:若是struct | class这种自定义类型,不需要实现析构函数,编译器默认生成

拷贝构造函数

运算符重载*

运算符重载(Operator Overloading)是 C++ 中允许程序员重新定义已有运算符的行为,使其适用于自定义类型(类或结构体)的特性。这使得自定义类型可以像内置类型一样使用运算符,提高代码的可读性和自然性

运算符重载的基本规则

  1. 不能创建新运算符:只能重载 C++ 中已有的运算符
  2. 不能改变运算符优先级:重载后运算符的优先级不变
  3. 不能改变操作数个数:单目运算符只能有一个操作数,双目运算符两个
  4. 至少有一个操作数是用户定义类型
  5. 部分运算符不能重载::, .*, ., ?:, sizeof, typeid

两种实现方式

  1. 成员函数形式
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class Complex {
    double real, imag;
public:
    Complex(double r, double i) : real(r), imag(i) {}
    
    // 成员函数形式重载+
    Complex operator+(const Complex& other) const {	//隐含参数	this 指针(指向左操作数)
        return Complex(real + other.real, imag + other.imag);
    }
};

Complex a(1, 2), b(3, 4);
Complex c = a + b; // 等价于 a.operator+(b)
  1. 非成员函数形式(常为友元)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
class Complex {
    double real, imag;
public:
    Complex(double r, double i) : real(r), imag(i) {}
    
    // 声明友元函数
    friend Complex operator+(const Complex& a, const Complex& b);
};

// 非成员函数实现
Complex operator+(const Complex& a, const Complex& b) {
    return Complex(a.real + b.real, a.imag + b.imag);
}

Complex c = a + b; // 等价于 operator+(a, b)

常用运算符重载实例

  1. 算术运算符:+, -, *, /
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class Fraction {
    int num, den; // 分子分母
public:
    Fraction(int n, int d) : num(n), den(d) {}
    
    Fraction operator+(const Fraction& other) const {
        return Fraction(num * other.den + other.num * den, den * other.den);
    }
    
    friend Fraction operator*(const Fraction& a, const Fraction& b) {
        return Fraction(a.num * b.num, a.den * b.den);
    }
};
  1. 比较运算符:==, !=, <, >
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
class Date {
    int year, month, day;
public:
    bool operator==(const Date& other) const {
        return year == other.year && month == other.month && day == other.day;
    }
    
    bool operator<(const Date& other) const {
        if (year != other.year) return year < other.year;
        if (month != other.month) return month < other.month;
        return day < other.day;
    }
};
  1. 流运算符:<<, >>
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
class Student {
    std::string name;
    int id;
public:
    friend std::ostream& operator<<(std::ostream& os, const Student& s) {
        return os << "ID: " << s.id << ", Name: " << s.name;
    }
    
    friend std::istream& operator>>(std::istream& is, Student& s) {
        std::cout << "Enter ID: ";
        is >> s.id;
        std::cout << "Enter name: ";
        is >> s.name;
        return is;
    }
};

Student s;
std::cin >> s;
std::cout << s;
  1. 下标运算符:[]
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
class IntArray {
    int* data;
    size_t size;
public:
    IntArray(size_t s) : size(s), data(new int[s]) {}
    ~IntArray() { delete[] data; }
    
    // 可修改版本
    int& operator[](size_t index) {
        if (index >= size) throw std::out_of_range("Index out of range");
        return data[index];
    }
    
    // const版本
    const int& operator[](size_t index) const {
        if (index >= size) throw std::out_of_range("Index out of range");
        return data[index];
    }
};

IntArray arr(10);
arr[3] = 42; // 使用下标运算符
  1. 函数调用运算符:()
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class Multiplier {
    int factor;
public:
    Multiplier(int f) : factor(f) {}
    
    int operator()(int x) const {
        return x * factor;
    }
};

Multiplier times3(3);
int result = times3(7); // 返回 21
  1. 自增/自减运算符:++, --
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Counter {
    int count;
public:
    Counter() : count(0) {}
    
    // 前置++
    Counter& operator++() {
        ++count;
        return *this;
    }
    
    // 后置++ (使用int参数区分)
    Counter operator++(int) {
        Counter temp = *this;
        ++(*this);
        return temp;
    }
};

Counter c;
++c; // 前置
c++; // 后置

继承*

继承(inheritance)机制是面向对象程序设计使代码可以复用的最重要的手段,减少重复代码的编写

子类会继承父类(publicprotected)修饰的成员,因此子类可以赋值给父类

子类的构造函数会调用父类的构造函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include<iostream>
#include<string>
using namespace std;
class Person {	// 父类(基类)
public:
	void Print() {
		cout << "name:" << _name << endl;
		cout << "age:" << _age << endl;
	}
protected:
	string _name = "noname"; //姓名
	int _age = 20; //年龄
};
class Student : public Person {	//子类(派生类)
protected:
	int _stuid; //学号
};
class Teacher : public Person {	//这里继承方式为public
protected:
	int _jobid; //工号
};
int main() {
	Student s;
	Teacher t;
	s.Print();
	t.Print();
	return 0;
}

Java支持多层继承,但不支持多重继承;而C++既支持多层继承,也支持多重继承

从底层角度来看,继承的本质就是复制private成员在继承同样被复制到子类,但是无法直接使用,可以用指针访问

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
struct Base {
public:
    Base(){
        x = 1;
        y = 2;
    }
private:
    int x;
    int y;
}
class Sub:public Base{
public:
    int a;
    int b;
}
int main() {
    Sub s1;
    int* p = (int*)&s1;
    cout << *p << endl;
    cout << *(p + 1) << endl;
}

这里了解即可,以后需要用再学,思想应该和Java差不多

多态*

多态分为两类

  • 静态的多态函数重载,在编译时决定
  • 动态的多态:一个父类引用或指针调用同一个函数,传递不同的对象,会调用不同的函数,即函数重写,如子类重写父类的方法,在运行时决定

多态有两个条件

  • 必须通过基类的指针或者引用调用虚函数,对象没有多态

  • 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写,如:父类的析构函数应定义成虚函数

虚函数:被virtual修饰的类成员函数(准确的说,只有类的非静态成员函数才能是虚函数,其他函数不能成为虚函数)

虚函数*

当类中有虚函数时,会在当前对象this指针最前面多一个属性,是一个4字节地址,指向虚函数表(存储了所有虚函数地址),有几个直接父类就有几个虚函数表

通过对象调用时,virtual函数和普通函数都是直接调用(E8 …)

如果用指针调用虚函数,是间接调用(call [在该地址取值] -> FF …)

打印虚函数表并调用验证

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class myA {
public:
    myA() {
        x = 10;
        y = 13;
    }
    int x;
    int y;
    virtual void fun1() {
        printf("fun1...\n");
    }
    virtual void fun2() {
        printf("fun2...\n");
    }
};
int main(){
    myA m;
    myA* p = &m;
    p->fun1();
    p->fun2();
    int* pm = (int*)&m;
    int pv = *pm;	//虚函数表地址
    int af1 = *(int*)pv;
    int af2 = *((int*)pv + 1);
    printf("pm: 0x%X, pv: 0x%X, af1: 0x%X, af2: 0x%X\n", pm, pv, af1, af2);
    typedef void(*pFunc)(void);	//定义函数指针类型
    pFunc pf1 = (pFunc)af1;
    pFunc pf2 = (pFunc)af2;
    pf1(); pf2();
    return 0;
}

练习:

1.单继承无函数覆盖(重写?)(打印Sub对象的虚函数表)

2.单继承有重写(打印Sub对象的虚函数表)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
struct Base {
public:
    virtual void fun1() {
        printf("Base: fun1...\n");
    }
    virtual void fun2() {
        printf("Base: fun2...\n");
    }
    virtual void fun3() {
        printf("Base: fun3...\n");
    }
};
struct Sub :Base {
public:
    virtual void fun1() {	//父类的fun1、fun2被重写
        printf("Sub: fun1...\n");
    }
    virtual void fun2() {
        printf("Sub: fun2...\n");
    }
    virtual void fun6() {
        printf("Sub: fun6...\n");
    }
};
int main() {
    Sub su;
    printf("Sub的虚函数表地址: 0x%X\n", *(int*)&su);
    typedef void(*pFun)(void);
    pFun pf;
    for (int i = 0;i < 4;i++) {
        pf = (pFun)*((int*)(*(int*)&su) + i);
        pf();
    }
}

单继承有重写

3.多继承无重写

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
struct Base1 {
public:
    virtual void fun1() {
        printf("Base1: fun1...\n");
    }
    virtual void fun2() {
        printf("Base1: fun2...\n");
    }
};
struct Base2 {
public:
    virtual void fun3() {
        printf("Base2: fun3...\n");
    }
    virtual void fun4() {
        printf("Base2: fun4...\n");
    }
};
struct Sub :Base1, Base2 {
public:
    virtual void fun5() {
        printf("Sub: fun5...\n");
    }
    virtual void fun6() {
        printf("Sub: fun6...\n");
    }
};
int main() {
    Sub su;
    cout << "SizeofSub:" << sizeof(su) << endl;
    typedef void(*pFun)(void);
    pFun pf;
    for (int i = 0;i < 2;i++) {	//有两个父类则有两个虚函数表
        int av = *((int*)&su + i);
        printf("Sub的虚函数表[%d]地址: 0x%X\n", i, av);
        for (int j = 0;j < 4;j++) {
            int tmp = *((int*)av + j);
            if (tmp == 0) {
                break;
            }
            pf = (pFun)tmp;
            pf();
        }
    }
}

多继承无重写

4.多继承有重写

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
struct Base1 {
public:
    virtual void fun1() {
        printf("Base1: fun1...\n");
    }
    virtual void fun2() {
        printf("Base1: fun2...\n");
    }
};
struct Base2 {
public:
    virtual void fun3() {
        printf("Base2: fun3...\n");
    }
    virtual void fun4() {
        printf("Base2: fun4...\n");
    }
};
struct Sub :Base1, Base2 {
public:
    virtual void fun1() {	//父类的fun1、fun3被重写
        printf("Sub: fun1...\n");
    }
    virtual void fun3() {
        printf("Sub: fun3...\n");
    }
    virtual void fun5() {
        printf("Sub: fun5...\n");
    }
};

多继承有重写

5.多层继承无重写(类似嵌套单继承,子类重写父类的方法,即覆盖对应虚函数地址)

6.多层继承有重写

重载&重写

绑定(Binding)指的是将标识符(如变量名、函数名)与内存中的实际存储位置或具体实现关联起来的过程绑定本质是建立映射关系,根据发生的时机不同,绑定主要分为两大类:

一、静态绑定

定义:在编译期间确定标识符与具体实现的关联

特点

  • 编译时即确定调用关系
  • 高效但缺乏灵活性
  • 基于变量/指针的声明类型

应用场景

  1. 普通函数调用
  2. 非虚成员函数调用
  3. 重载函数选择
  4. 模板实例化
  5. 运算符重载

二、动态绑定

定义:在运行期间确定标识符与具体实现的关联

特点

  • 运行时动态确定调用关系
  • 通过虚函数机制实现
  • 基于对象的实际类型

C++中,只有virtual函数是动态绑定的,应用:多态、接口实现、设计模式

绑定是多态的实现基础,即重载和重写

重载属于编译时多态,如:

1
2
3
4
5
6
class Calculator {
public:
    // 重载:编译时多态(静态绑定)
    int add(int a, int b) { return a + b; }          // 绑定到add_int
    double add(double a, double b) { return a + b; } // 绑定到add_double
};

特点:

  • 相同作用域(同一个类)
  • 函数名相同,参数不同
  • 编译器根据调用参数静态绑定具体函数

重写属于运行时多态,如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class Animal {
public:
    virtual void speak() { cout << "Animal sound" << endl; } // 虚函数
};

class Cat : public Animal {
public:
    void speak() override { cout << "Meow" << endl; } // 重写(动态绑定)
};

class Dog : public Animal {
public:
    void speak() override { cout << "Woof" << endl; } // 重写(动态绑定)
};

特点:

  • 不同作用域(基类与派生类)
  • 函数签名完全相同
  • 通过虚函数表动态绑定具体实现

多态的实现

模板

C++模板是实现编译时多态的核心机制,它允许开发者编写与类型无关的通用代码,编译器在编译期间根据具体类型生成特化版本

1.函数模板

1
2
3
4
5
6
7
8
// 通用比较函数
template <typename T>
T max(T a, T b) {
    return (a > b) ? a : b;
}
// 编译器自动生成特化版本
int m1 = max(10, 20);           // T = int
double m2 = max(3.14, 2.71);    // T = double

2.类模板

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
template <typename T>
class Stack {
private:
    std::vector<T> elements;
public:
    void push(const T& element) {
        elements.push_back(element);
    }
    T pop() {
        T top = elements.back();
        elements.pop_back();
        return top;
    }
};
// 使用
Stack<int> intStack;
Stack<std::string> strStack;

例1:模板实现通用 Swap 函数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
#include <iostream>
#include <string>
#include <vector>
#include <utility> // 用于std::move
#include <iostream>

// 在全局作用域定义类
class Point {
    int x, y;
public:
    Point(int x, int y) : x(x), y(y) {}

    // 声明友元函数
    friend std::ostream& operator<<(std::ostream& os, const Point& p);
};

// 在全局作用域定义友元函数
std::ostream& operator<<(std::ostream& os, const Point& p) {
    return os << "(" << p.x << ", " << p.y << ")";
}

// 基础模板:使用移动语义实现通用交换
template <typename T>
void my_swap(T& a, T& b) noexcept {
    T temp = std::move(a);
    a = std::move(b);
    b = std::move(temp);
}

// 特化版本:针对数组的高效交换
template <typename T, size_t N>
void my_swap(T(&a)[N], T(&b)[N]) noexcept {
    for (size_t i = 0; i < N; ++i) {
        my_swap(a[i], b[i]);
    }
}

// 测试用例
int main() {
    // 测试1:基本类型
    int x = 10, y = 20;
    std::cout << "交换前: x = " << x << ", y = " << y << std::endl;
    my_swap(x, y);
    std::cout << "交换后: x = " << x << ", y = " << y << std::endl << std::endl;

    // 测试2:字符串
    std::string s1 = "Hello", s2 = "World";
    std::cout << "交换前: s1 = " << s1 << ", s2 = " << s2 << std::endl;
    my_swap(s1, s2);
    std::cout << "交换后: s1 = " << s1 << ", s2 = " << s2 << std::endl << std::endl;

    Point p1(1, 2), p2(3, 4);
    std::cout << "交换前: p1 = " << p1 << ", p2 = " << p2 << std::endl;
    my_swap(p1, p2);
    std::cout << "交换后: p1 = " << p1 << ", p2 = " << p2 << std::endl << std::endl;

    // 测试4:容器
    std::vector<int> v1 = { 1, 2, 3 }, v2 = { 4, 5, 6 };
    std::cout << "交换前: v1 = [";
    for (int n : v1) std::cout << n << " ";
    std::cout << "], v2 = [";
    for (int n : v2) std::cout << n << " ";
    std::cout << "]" << std::endl;

    my_swap(v1, v2);

    std::cout << "交换后: v1 = [";
    for (int n : v1) std::cout << n << " ";
    std::cout << "], v2 = [";
    for (int n : v2) std::cout << n << " ";
    std::cout << "]" << std::endl << std::endl;

    // 测试5:数组
    int arr1[3] = { 10, 20, 30 };
    int arr2[3] = { 40, 50, 60 };

    std::cout << "交换前: arr1 = [";
    for (int n : arr1) std::cout << n << " ";
    std::cout << "], arr2 = [";
    for (int n : arr2) std::cout << n << " ";
    std::cout << "]" << std::endl;

    my_swap(arr1, arr2);

    std::cout << "交换后: arr1 = [";
    for (int n : arr1) std::cout << n << " ";
    std::cout << "], arr2 = [";
    for (int n : arr2) std::cout << n << " ";
    std::cout << "]" << std::endl;

    return 0;
}

例2:模板实现通用二分查找算法

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
#include <iostream>
#include <vector>
#include <functional> // 用于std::function
#include <cctype>    // 用于字符串大小写不敏感比较
#include <algorithm>

// 版本1:使用迭代器的通用二分查找
template <typename Iterator, typename T>
Iterator binary_search_iterator(Iterator first, Iterator last, const T& value) {
    auto low = first;
    auto high = last; // 指向最后一个元素

    while (low < high) {
        auto mid = low + std::distance(low, high) / 2;

        if (*mid == value) {
            return mid; // 找到元素,返回迭代器
        }
        else if (*mid < value) {
            low = mid + 1;
        }
        else {
            high = mid;
        }
    }

    return last; // 未找到,返回结束迭代器
}

// 版本2:支持自定义比较函数的二分查找
template <typename Iterator, typename T, typename Compare>
Iterator binary_search_iterator(Iterator first, Iterator last, const T& value, Compare comp) {
    auto low = first;
    auto high = last;

    while (low < high) {
        auto mid = low + std::distance(low, high) / 2;

        if (comp(*mid, value)) {
            low = mid + 1;
        }
        else if (comp(value, *mid)) {
            high = mid;
        }
        else {
            return mid; // 找到元素
        }
    }

    return last; // 未找到
}

// 版本3:直接处理容器类型的便捷接口
template <typename Container, typename T>
auto binary_search(const Container& container, const T& value)
-> decltype(container.begin()) {
    return binary_search_iterator(container.begin(), container.end(), value);
}

// 版本4:带自定义比较函数的容器接口
template <typename Container, typename T, typename Compare>
auto binary_search(const Container& container, const T& value, Compare comp)
-> decltype(container.begin()) {
    return binary_search_iterator(container.begin(), container.end(), value, comp);
}

// 测试用例:自定义结构体
struct Person {
    int id;
    std::string name;
    double salary;

    // 按ID比较
    bool operator<(const Person& other) const {
        return id < other.id;
    }

    bool operator==(const Person& other) const {
        return id == other.id;
    }
};

// 自定义比较函数:大小写不敏感的字符串比较
bool case_insensitive_compare(const std::string& a, const std::string& b) {
    if (a.size() != b.size()) return false;

    for (size_t i = 0; i < a.size(); ++i) {
        if (std::tolower(a[i]) != std::tolower(b[i])) {
            return false;
        }
    }
    return true;
}

// 自定义比较函数对象:按薪水比较
struct SalaryComparator {
    bool operator()(const Person& a, const Person& b) const {
        return a.salary < b.salary;
    }
};

int main() {
    // 测试1:内置类型数组
    int int_array[] = { 1, 3, 5, 7, 9, 11, 13, 15 };
    int size = sizeof(int_array) / sizeof(int);

    auto int_result = binary_search_iterator(int_array, int_array + size, 7);
    if (int_result != int_array + size) {
        std::cout << "在数组中找到 7: " << *int_result << std::endl;
    }
    else {
        std::cout << "在数组中未找到 7" << std::endl;
    }

    // 测试2:标准库容器
    std::vector<double> double_vec = { 1.1, 2.2, 3.3, 4.4, 5.5, 6.6 };
    auto double_result = binary_search(double_vec, 4.4);
    if (double_result != double_vec.end()) {
        std::cout << "在vector中找到 4.4: " << *double_result << std::endl;
    }

    // 测试3:自定义结构体
    std::vector<Person> people = {
        {101, "Alice", 50000.0},
        {102, "Bob", 60000.0},
        {103, "Charlie", 55000.0},
        {104, "David", 70000.0}
    };

    // 按ID排序和搜索
    std::sort(people.begin(), people.end());

    Person search_person{ 103, "Charlie", 0.0 };
    auto person_result = binary_search(people, search_person);
    if (person_result != people.end()) {
        std::cout << "找到ID为103的人: " << person_result->name << std::endl;
    }

    // 测试4:自定义比较函数(按薪水搜索)
    SalaryComparator salary_comp;
    std::sort(people.begin(), people.end(), salary_comp);

    Person salary_search{ 0, "", 55000.0 };
    auto salary_result = binary_search(people, salary_search, salary_comp);
    if (salary_result != people.end()) {
        std::cout << "找到薪水为55000的人: " << salary_result->name << std::endl;
    }

    // 测试5:大小写不敏感的字符串搜索
    std::vector<std::string> words = { "Apple", "Banana", "Cherry", "Date", "Fig" };
    std::sort(words.begin(), words.end());

    // 使用自定义比较函数
    auto string_result = binary_search_iterator(
        words.begin(), words.end(),
        "cherry", // 注意小写
        [](const std::string& a, const std::string& b) {
            for (size_t i = 0; i < std::min(a.size(), b.size()); ++i) {
                char ca = std::tolower(a[i]);
                char cb = std::tolower(b[i]);
                if (ca != cb) return ca < cb;
            }
            return a.size() < b.size();
        }
    );

    if (string_result != words.end()) {
        std::cout << "找到 'cherry' (大小写不敏感): " << *string_result << std::endl;
    }

    return 0;
}

引用

引用不是新定义一个变量,而是给已存在变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间

  • 引用是变量的别名:引用不是新变量,而是已有变量的另一个名称
  • 引用必须初始化:声明时必须绑定到一个已存在的变量
  • 引用不可重新绑定:一旦初始化后,不能改变其引用的对象
  • 不占用额外内存(通常):引用本身不占用存储空间(编译器实现细节)

引用在函数中的应用

  1. 避免拷贝
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
#include <string>
using namespace std;

// 按值传递(产生拷贝)
void print_by_value(string str) {
    cout << "按值传递: " << str << endl;
}

// 按引用传递(无拷贝)
void print_by_reference(const string& str) {
    cout << "按引用传递: " << str << endl;
}

int main() {
    string long_str = "这是一个很长的字符串,用于演示拷贝开销...";
    
    print_by_value(long_str);    // 产生字符串拷贝
    print_by_reference(long_str); // 无拷贝
    
    return 0;
}
  1. 修改函数参数(替代指针)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
using namespace std;

// 使用引用修改参数
void increment(int& num) {
    num++;
}

// 使用指针的等效版本
void increment_ptr(int* num) {
    (*num)++;
}

int main() {
    int a = 5;
    
    increment(a); // 不需要取地址操作
    cout << "a = " << a << endl; // 输出 6
    
    increment_ptr(&a); // 需要显式取地址
    cout << "a = " << a << endl; // 输出 7
    
    return 0;
}
  1. 返回引用
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <iostream>
using namespace std;

// 返回数组中最大元素的引用
int& find_max(int arr[], int size) {
    int max_index = 0;
    for (int i = 1; i < size; i++) {
        if (arr[i] > arr[max_index]) {
            max_index = i;
        }
    }
    return arr[max_index];
}

int main() {
    int numbers[] = {3, 5, 2, 8, 6};
    int size = sizeof(numbers) / sizeof(numbers[0]);
    
    int& max_ref = find_max(numbers, size);
    cout << "最大值: " << max_ref << endl; // 输出 8
    
    // 通过引用修改数组元素
    max_ref = 100;
    cout << "修改后数组: ";
    for (int i = 0; i < size; i++) {
        cout << numbers[i] << " "; // 输出 3 5 2 100 6
    }
    cout << endl;
    
    return 0;
}

高级引用用法

  1. const 引用(只读访问)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>
using namespace std;

void print_value(const int& num) {
    // num = 10; // 错误:不能通过const引用修改
    cout << "值: " << num << endl;
}

int main() {
    int a = 5;
    const int& const_ref = a;
    
    // const_ref = 10; // 错误:不能通过const引用修改
    
    a = 10; // 但原始变量可以修改
    cout << "const_ref: " << const_ref << endl; // 输出 10
    
    // 绑定到临时值
    const int& temp_ref = 42;
    cout << "临时值引用: " << temp_ref << endl;
    
    return 0;
}
  1. 引用与范围 for 循环
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
#include <vector>
using namespace std;

int main() {
    vector<int> numbers = {1, 2, 3, 4, 5};
    
    // 使用引用修改元素
    for (int& num : numbers) {
        num *= 2; // 每个元素加倍
    }
    
    // 使用const引用只读访问
    for (const int& num : numbers) {
        cout << num << " "; // 输出 2 4 6 8 10
    }
    cout << endl;
    
    return 0;
}
  1. 右值引用(C++11 新特性)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
#include <utility> // for std::move
using namespace std;

void process_value(int& val) {
    cout << "左值引用: " << val << endl;
}

void process_value(int&& val) {
    cout << "右值引用: " << val << endl;
}

int main() {
    int a = 10;
    
    process_value(a);        // 调用左值版本
    process_value(20);       // 调用右值版本
    process_value(move(a));  // 使用 std::move 转为右值
    
    return 0;
}

友元

友元是 C++ 中打破封装性的一种特殊机制,它允许特定的外部函数或类访问另一个类的私有(private)和保护(protected)成员

友元关系的核心特点:

  • 授权访问:被声明为友元的实体可以访问类的私有和保护成员
  • 单向性:友元关系是单向的(A 是 B 的友元 ≠ B 是 A 的友元)
  • 非传递性:友元关系不传递(A 是 B 的友元,B 是 C 的友元 ≠ A 是 C 的友元)
  • 不继承:友元关系不被派生类继承

声明位置

1
2
3
4
5
6
7
8
class MyClass {
public:
    friend void friendFunction(); // 有效
private:
    friend class FriendClass;    // 有效
protected:
    friend void anotherFriend(); // 有效
};

友元的三种形式

  1. 友元函数(没有this指针)
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
using namespace std;

class Box {
private:
    double width;
    
public:
    Box(double w) : width(w) {}
    
    // 声明友元函数
    friend void printWidth(Box box);
};

// 友元函数定义(可以访问私有成员)
void printWidth(Box box) {
    cout << "Box width: " << box.width << endl;
}

int main() {
    Box box(10.0);
    printWidth(box); // 输出: Box width: 10
    return 0;
}
  1. 友元类
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <iostream>
using namespace std;

class Box {
private:
    double width;
    
public:
    Box(double w) : width(w) {}
    
    // 声明友元类
    friend class BoxPrinter;
};

class BoxPrinter {
public:
    void print(Box box) {
        // 可以访问Box的私有成员
        cout << "Box width: " << box.width << endl;
    }
};

int main() {
    Box box(20.0);
    BoxPrinter printer;
    printer.print(box); // 输出: Box width: 20
    return 0;
}
  1. 友元成员函数
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <iostream>
using namespace std;

class Box; // 前向声明

class BoxPrinter {
public:
    void print(Box box); // 成员函数声明
};

class Box {
private:
    double width;
    
public:
    Box(double w) : width(w) {}
    
    // 声明特定成员函数为友元
    friend void BoxPrinter::print(Box box);
};

// 成员函数定义(需要完整Box定义)
void BoxPrinter::print(Box box) {
    // 可以访问Box的私有成员
    cout << "Box width: " << box.width << endl;
}

int main() {
    Box box(30.0);
    BoxPrinter printer;
    printer.print(box); // 输出: Box width: 30
    return 0;
}

友元的使用场景

  1. 运算符重载
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include <iostream>
using namespace std;

class Complex {
private:
    double real;
    double imag;
    
public:
    Complex(double r, double i) : real(r), imag(i) {}
    
    // 声明友元运算符重载
    friend Complex operator+(const Complex& c1, const Complex& c2);
    
    void print() {
        cout << real << " + " << imag << "i" << endl;
    }
};

// 实现友元运算符
Complex operator+(const Complex& c1, const Complex& c2) {
    return Complex(c1.real + c2.real, c1.imag + c2.imag);
}

int main() {
    Complex a(2.5, 3.1);
    Complex b(1.7, 2.3);
    Complex c = a + b;
    c.print(); // 输出: 4.2 + 5.4i
    return 0;
}
  1. 流运算符重载
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
#include <iostream>
using namespace std;

class Point {
private:
    int x;
    int y;
    
public:
    Point(int x, int y) : x(x), y(y) {}
    
    // 声明友元流运算符
    friend ostream& operator<<(ostream& os, const Point& p);
};

// 实现输出运算符
ostream& operator<<(ostream& os, const Point& p) {
    os << "(" << p.x << ", " << p.y << ")";
    return os;
}

int main() {
    Point p(3, 4);
    cout << "Point: " << p << endl; // 输出: Point: (3, 4)
    return 0;
}
  1. 紧密协作的类
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
#include <iostream>
using namespace std;

class Engine; // 前向声明

class Car {
private:
    string model;
    friend class Mechanic; // 授权Mechanic访问
};

class Engine {
private:
    string type;
    friend class Mechanic; // 授权Mechanic访问
};

class Mechanic {
public:
    void repair(Car& car, Engine& engine) {
        cout << "Repairing car model: " << car.model << endl;
        cout << "Fixing engine type: " << engine.type << endl;
        // 可以访问Car和Engine的私有成员
    }
};

int main() {
    Car myCar;
    Engine myEngine;
    Mechanic bob;
    bob.repair(myCar, myEngine);
    return 0;
}

new-delete

newdelete 是 C++ 中用于动态内存管理的核心运算符,它们提供了在程序运行时分配和释放内存的能力。与 C 语言中的 mallocfree 不同,newdelete 是类型安全的,并且会调用对象的构造函数和析构函数

new 运算符

new是在堆中创建对象

  1. 基本用法
1
2
3
4
5
6
// 分配单个对象
int* pInt = new int;       // 未初始化
int* pInt2 = new int(42);  // 初始化为42

// 分配对象数组
int* arr = new int[10];    // 10个整数的数组
  1. 内存分配过程

new-内存分配过程

  1. new 的变体

(1) 定位 new(Placement new)

1
2
3
4
5
6
#include <new> // 必须包含头文件

char buffer[1024]; // 预分配内存

// 在指定内存位置构造对象
int* p = new (buffer) int(100); 

(2) nothrow new

1
2
3
4
5
// 分配失败时不抛出异常,返回 nullptr
int* p = new (std::nothrow) int[1000000000];
if (!p) {
    // 处理内存分配失败
}

delete 运算符

  1. 基本用法
1
2
3
4
5
6
7
// 释放单个对象
delete pInt;   // 释放内存
pInt = nullptr; // 避免悬空指针

// 释放对象数组
delete[] arr;  // 注意使用 []
arr = nullptr;
  1. 内存释放过程

delete-内存释放过程

参考

c++类和对象(上)this指针

【C++】类和对象(中)—— 构造函数 + 析构函数

【C++】多态

Licensed under CC BY-NC-SA 4.0
comments powered by Disqus
人生若只如初见
使用 Hugo 构建
主题 StackJimmy 设计