0xzhang的博客

C++ 回顾

· 0xzhang

基础语法§

变量§

变量定义§

  1. auto 5;
    auto 2.0f;
    

    由编译器根据上下文确定数据类型。

  2. int *ptr = new int;
    int *array = new int[10];
    delete ptr;
    delete[] array;
    

    指针变量的动态生成与删除。

    new/delete是运算符,C++11包含的关键字,与malloc()/free()不同。

  3. int v0;
    int &v1 = v0;	// Right
    int &v2;	// Wrong
    
    void swap(int &a, int &b)
    {
        a+=b;
        b=a-b;
        a-=b;
    }
    

    左值引用。

    具名变量的别名:类型名 &引用名 = 变量名。因为是已存在变量的别名,因此定义时必须初始化。

    当函数参数为引用类型时,表示函数的形式参数与实际参数是同一个变量,改变形参将改变实参。

    函数返回值可以是引用类型,但不可以是对局部变量的引用。

  4. int && sum = 3+4;
    float && res = ReturnRvalue(f1, f2);
    
    void AcceptRvalueRef(T&& s){...}
    

    右值引用。(C++11)

    匿名变量(临时变量)的别名:类型名 && 引用名 = 表达式。

    应用在函数参数中,能够减少临时变量拷贝的开销。

变量初始化§

初始化列表。

int a[] = {1, 3, 5};
int a[]{1, 3, 5};

int a(3+5);
int a{3+5};
int *i = new int(10);

变量类型推导§

使用decltype对变量或表达式结果的类型进行推导。

函数§

函数重载§

同一名称的函数,有两个以上不同的函数实现,被称为”函数重载“。要求参数不同。

函数参数的缺省值§

有缺省值的函数参数,必须是最后一个参数。

如果有多个带缺省值的函数参数,则这些函数参数只能在没有缺省值的参数后面出现。

void print(char* name,
			int score,
			char* msg = "pass")
{
	cout<<name<<":"<<score<<","<<pass<<endl;
}

main函数§

关于main函数的命令行参数。

  1. 强制交互;

    #include <iostream>
    int main()
    {
    	int a, b;
    	std::cin>>a>>b;
    	std::cout<< a+b <<std::endl;
    	return 0;
    }
    
  2. 参数传参;

    #include <iostream>
    #include <cstdio>	// atoi()
    int main(int argc, char** argv)
    {
    	int a, b;
    	a = atoi(argv[1]);
    	b = atoi(argv[2]);
    	std::cout << a+b << std::endl;
    	return 0;
    }
    

    char**表示字符串的数组,也可用char*[]

    在命令main2 4 5执行后,argc为3,argv[0]为“main2”,argv[1]为“4”,argv[2]为”5“。

  3. 安全性改进。

    #include <iostream>
    #include <cstdio>	// atoi()
    int main(int argc, char** argv)
    {
        if (argc != 3)
        {
            std::cout << "Usage: " << argv[0]
                	<< " op1 op2" << std::endl;
            return 1;
        }
    	int a, b;
    	a = atoi(argv[1]);
    	b = atoi(argv[2]);
    	std::cout << a+b << std::endl;
    	return 0;
    }
    

§

类的定义§

类成员的访问权限§

class Matrix{
public:
	void fill(char dir);
private:
	int data[6][6];
};

class Matrix{
	int data[6][6];		// class成员的缺省属性为private
public:
	void fill(char dir);
};

有时需要允许某些函数访问对象的私有成员,可以通过声明该函数为类的”友元“来实现。

#include <iostream>
using namespace std;

class Test{
	int id;
public:
	friend void print(Test obj);
	...
};
void print(Test obj)
{
	cout << obj.id << endl;
}

友元函数并非是类的成员函数,是一个类外的函数。

静态成员§

常量成员§

对象组合§

构造函数析构函数§

构造函数§

默认构造函数§

构造函数的初始化列表§

析构函数§

拷贝构造函数§

#include <iostream>
using namespace std;

class Test {
public:
	Test() { cout << "Test" << endl; }
	Test(const Test& src) { cout << "Test(const Test&)" << endl; }
	~Test() { cout << "~Test" << endl; }
}

void func1(Test obj) { cout << "func1()..." << endl; }

Test func2() {
	cout << "func2()..." << endl;
	return Test();
}

int main() {
	cout << "main()..." << endl;
	Test t;
	func1(t);
	t = func2();
	return 0;
}

输出:

main()...
Test
Test(const Test&)
func1()...
~Test
func2()...
Test
~Test
~Test

移动构造函数(C++11)§

编译器生成的函数成员§

重载§

赋值运算符重载§

ClassName& operator= (const ClassName& right)
{
	if(this != &right) {	// 避免赋值给自己
		// 将right对象中的内容复制到当前对象中	
	}
	return *this;	// 注意返回内容
}

流运算符重载§

istream& operator>> (istream& in, Test& dst);
ostream& operator<< (ostream& out, const Test& src);

可以将流运算符函数声明为类的友元,可访问对象的私有成员。

函数运算符重载§

函数运算符()也能重载,它使对象看上去像是一个函数名。重载的对象可完成一些操作,看上去像一个函数,故也称“函数对象”。有状态有记忆的操作。

ReturnType operator() (Parameters) {
	...
}

ClassName Obj;
Obj(real_parameters);
// -> Obj.operator() (real_parameters);

数组下标运算符重载§

重载前缀和后缀运算符§

智能指针(C++11)§

对内存回收提供一定支持。

  1. unique_ptr

    不允许多个指针共享资源,可以用标准库中的move函数转移指针

  2. shared_ptr

    多个指针共享资源

  3. weak_ptr

    可复制shared_ptr,但其构造或释放对资源不产生影响

面向对象§

继承§

继承基类构造函数§

派生类中的私有成员§

重写§

派生类重写基类成员函数§

虚函数§

类型转换§

对象类型转换§

自定义类型转换§

  1. 在源类中定义“目标类型转换运算符“;

    class Src {
    ...
    	operator Dst() {
    		return Dst();
    	}
    ...
    }
    
  2. 在目标类中定义”源类对象作参数的构造函数“,本身有作为构造函数直接构造的作用,也能够完成自动类型转换的工作;

    class Dst {
    ...
    	Dst(const &Src s) {}
    ...
    }
    
  3. 禁用自动类型转换,使用explicit关键字放于上述两种方法定义的前一行;也可以在函数定义末尾加上= delete表示不能调用。

强制类型转换§

自动类型转换为隐式转换,强制类型转换被称为显式转换。

模板§

template <typename T>

类模板 -实例化-> 类 -实例化-> 对象

类模板的成员函数,也可有额外的模板参数。

模板参数的特化§

有时,有些类型并不适用,则需要对模板进行特殊化处理。

对函数模板,如果有多个模板参数,则特化时必须提供所有参数的特例类型,不能部分特化。

template<typename T>
T sum(T a, T b)
{
	return a + b;
}

template<>
char* sum(char* a, char* b)
{
	char* p = new char[strlen(a) + strlen(b) + 1];
	strcpy(p, a);
	strcat(p, b);
	return p;
}

对于类模板。

template <typename T>
class Sum {
    T a, b;
public:
    Sum(T op1, T op2) : a(op1), b(op2) {}
    T DoIt() { return a + b ;}
};

template <>
class Sum<char*> {
    char *str1, *str2;
public:
    Sum(char* s1, char* s2) : str1(s1), str2(s2) {}
    char* DoIt() {
        char* tmp = new char[strlen(str1) + strlen(str2) + 1];
        strcpy(tmp, str1);
        strcat(tmp, str2);
        return tmp;
    }
};

UML类图§

类名:Calculator

实现:-_applePrice : float

​ -_bananaPrice : float

接口:+calTotal(appleWeight : float, bananaWeight : float) : float

....

面向对象编程§

针对接口而不是针对实现编程§

  1. 通过抽象出“抽象概念”,设计出描述这个抽象概念的抽象类,或称接口类,这个类有一系列的纯虚函数,描述了这个类的接口;
  2. 对这个接口类进行继承并实现这些纯虚函数,从而形成这个抽象概念的实现类——实现可以有很多种以适应变化;
  3. 在使用这个概念时,我们使用接口类来引用这个概念,而不直接使用实现类,从而避免实现类的改变造成整个程序的大规模修改。

单一责任原则§

  1. 类的功能应该是内聚的,一个类只承担一项功能;
  2. 表现为:修改/派生一个类只应该有一个理由,只能够由单个变化因素引起。

容器、迭代与算法§

运算符重载就是在新的数据类型上还原运算符的本质。

内联函数§

泛型编程§

”变“与”不变“§

STL标准模板库§

使用继承实现接口类,重写所有虚函数。

设计模式§

策略模式§

如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。

Bridge模式§

把抽象部分与实现部分分离,使它们都可以独立变化。

适配器模式§

功能不变,接口变化。

使用组合实现适配,称作对象Adapter。

代理模式§

接口不变,功能变化。

用于对被代理对象进行控制,如引用计数控制、权限控制、延迟初始化等。

装饰器模式§

这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。

对比策略模式:

责任链模式§

有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定。

让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。

参考资料§

[1] 面向对象程序设计(C++)(2019春)- 学堂在线 - 徐明星

[2] 设计模式 | 菜鸟教程