C++ Review基础语法变量函数类构造函数析构函数重载智能指针(C++11)面向对象继承重写类型转换模板UML类图面向对象编程设计模式策略模式Bridge模式适配器模式代理模式装饰器模式责任链模式参考资料
变量定义:
1auto 5;
2auto 2.0f;
由编译器根据上下文确定数据类型。
xxxxxxxxxx
41int *ptr = new int;
2int *array = new int[10];
3delete ptr;
4delete[] array;
指针变量的动态生成与删除。
new/delete是运算符,C++11包含的关键字,与malloc()/free()不同。
x1int v0;
2int &v1 = v0; // Right
3int &v2; // Wrong
4
5void swap(int &a, int &b)
6{
7 a+=b;
8 b=a-b;
9 a-=b;
10}
左值引用。
具名变量的别名:类型名 &引用名 = 变量名。因为是已存在变量的别名,因此定义时必须初始化。
当函数参数为引用类型时,表示函数的形式参数与实际参数是同一个变量,改变形参将改变实参。
函数返回值可以是引用类型,但不可以是对局部变量的引用。
xxxxxxxxxx
41int && sum = 3+4;
2float && res = ReturnRvalue(f1, f2);
3
4void AcceptRvalueRef(T&& s){...}
右值引用。(C++11)
匿名变量(临时变量)的别名:类型名 && 引用名 = 表达式。
应用在函数参数中,能够减少临时变量拷贝的开销。
变量初始化:
初始化列表。
xxxxxxxxxx
61int a[] = {1, 3, 5};
2int a[]{1, 3, 5};
3
4int a(3+5);
5int a{3+5};
6int *i = new int(10);
变量类型推导:
使用decltype对变量或表达式结果的类型进行推导。
函数重载:
同一名称的函数,有两个以上不同的函数实现,被称为”函数重载“。要求参数不同。
函数参数的缺省值:
有缺省值的函数参数,必须是最后一个参数。
如果有多个带缺省值的函数参数,则这些函数参数只能在没有缺省值的参数后面出现。
xxxxxxxxxx
61void print(char* name,
2 int score,
3 char* msg = "pass")
4{
5 cout<<name<<":"<<score<<","<<pass<<endl;
6}
main函数:
关于main函数的命令行参数。
强制交互;
xxxxxxxxxx
81
2int main()
3{
4 int a, b;
5 std::cin>>a>>b;
6 std::cout<< a+b <<std::endl;
7 return 0;
8}
参数传参;
xxxxxxxxxx
101
2// atoi()
3int main(int argc, char** argv)
4{
5 int a, b;
6 a = atoi(argv[1]);
7 b = atoi(argv[2]);
8 std::cout << a+b << std::endl;
9 return 0;
10}
char**
表示字符串的数组,也可用char*[]
。
在命令main2 4 5
执行后,argc
为3,argv[0]
为“main2”,argv[1]
为“4”,argv[2]
为”5“。
安全性改进。
xxxxxxxxxx
161
2// atoi()
3int main(int argc, char** argv)
4{
5 if (argc != 3)
6 {
7 std::cout << "Usage: " << argv[0]
8 << " op1 op2" << std::endl;
9 return 1;
10 }
11 int a, b;
12 a = atoi(argv[1]);
13 b = atoi(argv[2]);
14 std::cout << a+b << std::endl;
15 return 0;
16}
类的定义:
类成员的访问权限:
xxxxxxxxxx
121class Matrix{
2public:
3 void fill(char dir);
4private:
5 int data[6][6];
6};
7
8class Matrix{
9 int data[6][6]; // class成员的缺省属性为private
10public:
11 void fill(char dir);
12};
有时需要允许某些函数访问对象的私有成员,可以通过声明该函数为类的”友元“来实现。
xxxxxxxxxx
131
2using namespace std;
3
4class Test{
5 int id;
6public:
7 friend void print(Test obj);
8 ...
9};
10void print(Test obj)
11{
12 cout << obj.id << endl;
13}
友元函数并非是类的成员函数,是一个类外的函数。
静态成员:
在类型前面加上static修饰的数据成员,是隶属于类的,称为类的静态数据成员,也称“类变量”;
返回值类型前面加上static修饰的成员函数,称为静态成员函数,它们不能调用非静态成员函数(不具有隐含参数this);
类的静态成员既可以通过对象来访问,也可以通过类名来访问;
注意如果未定义拷贝构造函数等,类与对象静态成员的变化可能不一致。
常量成员:
使用const修饰的数据成员,称为类的常量数据成员,在对象的整个生命周期里不可更改;
若用const修饰成员函数,则该成员函数在实现时不能修改类的数据成员,函数体中不允许有改变对象状态的语句。
对象组合:
可以在类中使用其他类来定义数据成员,这种包含关系可嵌套;
子对象的初始化需要在初始化列表中完成,除非子对象能使用默认的构造函数;
对象的构造与析构次序:
构造函数:
默认构造函数:
不带任何参数的构造函数,被称为”默认构造函数“,也称”缺省构造函数“;
使用默认构造函数(无参数)来生成对象时,对象定义的格式为:
ClassName obj;
不能使用ClassName obj();
。
构造函数的初始化列表:
位于参数列表后,以冒号作开头,使用“数据成员(初始值)”的形式。
xxxxxxxxxx
71class Student{
2 long ID;
3...
4public:
5 Student(long id) : ID(id) {}
6...
7}
....
析构函数:
拷贝构造函数:
A a; A b = a;
,等号处即调用拷贝构造函数,并非赋值。xxxxxxxxxx
241
2using namespace std;
3
4class Test {
5public:
6 Test() { cout << "Test" << endl; }
7 Test(const Test& src) { cout << "Test(const Test&)" << endl; }
8 ~Test() { cout << "~Test" << endl; }
9}
10
11void func1(Test obj) { cout << "func1()..." << endl; }
12
13Test func2() {
14 cout << "func2()..." << endl;
15 return Test();
16}
17
18int main() {
19 cout << "main()..." << endl;
20 Test t;
21 func1(t);
22 t = func2();
23 return 0;
24}
输出:
xxxxxxxxxx
91main()...
2Test
3Test(const Test&)
4func1()...
5~Test
6func2()...
7Test
8~Test
9~Test
移动构造函数(C++11):
语法
xxxxxxxxxx
11ClassName(ClassName&&);
目的
xxxxxxxxxx
41Test(Test&& t) : buf(t.buf) {
2 cout << "Test(Test&&) called. this->buf @ " << hex << buf << endl;
3 t.buf = nullptr;
4}
注意:编译器不要对返回值优化。-fno-elide-constructors
编译器生成的函数成员:
默认构造函数 -- 空函数;
析构函数 -- 空函数;
拷贝构造函数 -- 按位复制对象所占内存,对指针不正确;
移动构造函数 -- 与默认拷贝构造函数相同;
赋值运算符重载 -- 与默认拷贝构造函数相同;
如果用户定义,编译器即不再生成;
显示缺省:
xxxxxxxxxx
31T() = default;
2...
3T::T() = default;
赋值运算符重载:
xxxxxxxxxx
71ClassName& operator= (const ClassName& right)
2{
3 if(this != &right) { // 避免赋值给自己
4 // 将right对象中的内容复制到当前对象中
5 }
6 return *this; // 注意返回内容
7}
流运算符重载:
xxxxxxxxxx
21istream& operator>> (istream& in, Test& dst);
2ostream& operator<< (ostream& out, const Test& src);
可以将流运算符函数声明为类的友元,可访问对象的私有成员。
函数运算符重载:
函数运算符()也能重载,它使对象看上去像是一个函数名。重载的对象可完成一些操作,看上去像一个函数,故也称“函数对象”。有状态有记忆的操作。
xxxxxxxxxx
71ReturnType operator() (Parameters) {
2 ...
3}
4
5ClassName Obj;
6Obj(real_parameters);
7// -> Obj.operator() (real_parameters);
数组下标运算符重载:
函数声明形式:
xxxxxxxxxx
11返回类型 operator[] (参数)
如果返回类型是引用,数组运算符调用可以作左值,否则只能作右值。
xxxxxxxxxx
21Obj[index] = value; // 返回值为引用
2Var = Obj[index];
重载前缀和后缀:
前缀运算符重载声明
xxxxxxxxxx
21ReturnType operator++();
2ReturnType operator--();
后缀运算符重载
xxxxxxxxxx
21ReturnType operator++(int dummy);
2ReturnType operator--(int dummy);
通过在函数参数中的哑元参数dummy来区分前缀与后缀的同名重载。哑元:在函数体语句中没有使用该参数。
对内存回收提供一定支持。
unique_ptr
不允许多个指针共享资源,可以用标准库中的move函数转移指针
shared_ptr
多个指针共享资源
weak_ptr
可复制shared_ptr,但其构造或释放对资源不产生影响
在已有类的基础上,可以通过继承来定义新的类,实现对已有代码的复用;
继承方式,
xxxxxxxxxx
21class Derived : [private] Base {...}
2class Derived : public Base {...}
缺省继承方式为private继承。
被继承的已有类,被称为基类,也称父类;
通过继承得到的新类,被称为派生类,也称子类、扩展类。
基类中的数据成员通过继承称为派生类对象的一部分,需要在构造派生类对象的过程中调用基类构造函数来正确初始化。
先执行基类构造函数初始化继承到的数据,再执行派生类构造函数。
对象析构时,先执行派生类析构函数,再执行由编译器自动调用的基类的析构函数。
继承基类构造函数:
using Base::Base()
来继承基类的构造函数,相当于给派生类定义了相应参数的构造函数;派生类中的私有成员:
基类中的私有成员,不允许在派生类对象和成员函数中被访问;
基类中的公有成员:
派生类重写基类成员函数:
虚函数:
对于被派生类重写的成员函数,若它在基类中被定义为虚函数,则通过基类指针或引用调用该成员函数时,编译器将根据对象实际类型决定是调用基类中的函数还是派生类重写的函数;
只有重写函数定义为虚函数,当派生类对象转换为基类对象作用时,可以根据对象实际类型调用对应重写的函数,实现多态的效果;
若某成员函数在基类中声明为虚函数,当派生类重写它时,无论是否声明为虚函数,该成员函数都仍然是虚函数。
虚析构函数使得派生对象析构时会先调用派生类的析构函数,再调用基类的析构函数,而若析构函数非虚函数,则代码运行只会按照要求参数的类型执行调用,当派生对象类型转换为基类对象,调用析构函数时会只调用基类析构函数。
一句话:指向派生类的基类指针若调用虚函数,则实际执行的是派生类的函数定义。
禁止重写的虚函数(C++11),使用final关键字修饰的虚函数,派生类不可对它进行重写;
final可以在继承关系链的“中途”进行设定,禁止后续派生类对指定虚函数重写;
纯虚函数,将函数的实现推迟到子类
xxxxxxxxxx
21public:
2 virtual void fun() = 0;
在虚函数声明最后加上“=0”,使得类称为抽象类,包含纯虚函数的类不可以定义对象,所以只能做基类,这种抽象类也称为接口类,只用来规定接口。
对象类型转换:
自定义类型转换:
在源类中定义“目标类型转换运算符“;
xxxxxxxxxx
71class Src {
2...
3 operator Dst() {
4 return Dst();
5 }
6...
7}
在目标类中定义”源类对象作参数的构造函数“,本身有作为构造函数直接构造的作用,也能够完成自动类型转换的工作;
xxxxxxxxxx
51class Dst {
2...
3 Dst(const &Src s) {}
4...
5}
禁用自动类型转换,使用explicit
关键字放于上述两种方法定义的前一行;也可以在函数定义末尾加上= delete
表示不能调用。
强制类型转换:
自动类型转换为隐式转换,强制类型转换被称为显式转换。
dynamic_cast<Dst_Type>(Src_var)
static_cast<Dst_Type>(Src_var)
template <typename T>
类模板 -实例化-> 类 -实例化-> 对象
类模板的成员函数,也可有额外的模板参数。
模板参数的特化:
有时,有些类型并不适用,则需要对模板进行特殊化处理。
对函数模板,如果有多个模板参数,则特化时必须提供所有参数的特例类型,不能部分特化。
xxxxxxxxxx
141template<typename T>
2T sum(T a, T b)
3{
4 return a + b;
5}
6
7template<>
8char* sum(char* a, char* b)
9{
10 char* p = new char[strlen(a) + strlen(b) + 1];
11 strcpy(p, a);
12 strcat(p, b);
13 return p;
14}
对于类模板。
xxxxxxxxxx
201template <typename T>
2class Sum {
3 T a, b;
4public:
5 Sum(T op1, T op2) : a(op1), b(op2) {}
6 T DoIt() { return a + b ;}
7};
8
9template <>
10class Sum<char*> {
11 char *str1, *str2;
12public:
13 Sum(char* s1, char* s2) : str1(s1), str2(s2) {}
14 char* DoIt() {
15 char* tmp = new char[strlen(str1) + strlen(str2) + 1];
16 strcpy(tmp, str1);
17 strcat(tmp, str2);
18 return tmp;
19 }
20};
类名:Calculator
实现:-_applePrice : float
-_bananaPrice : float
接口:+calTotal(appleWeight : float, bananaWeight : float) : float
....
针对接口而不是针对实现编程:
单一责任原则:
容器、迭代与算法:
运算符重载就是在新的数据类型上还原运算符的本质。
内联函数:
泛型编程:
”变“与”不变“:
STL标准模板库:
使用继承实现接口类,重写所有虚函数。
如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。
把抽象部分与实现部分分离,使它们都可以独立变化。
功能不变,接口变化。
使用组合实现适配,称作对象Adapter。
接口不变,功能变化。
用于对被代理对象进行控制,如引用计数控制、权限控制、延迟初始化等。
这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。
对比策略模式:
相同点
不同点
有多个对象可以处理同一个请求,具体哪个对象处理该请求由运行时刻自动确定。
让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。
[1] 面向对象程序设计(C++)(2019春)- 学堂在线 - 徐明星
[2] 设计模式 | 菜鸟教程
→back
Contact me
Please contact me about any suggestion(s): aliceb0b@hotmail.com.