目录
C++ Learning
基础知识
函数
-
(1)没有规定运算顺序
-
cout << i << " " << ++i << endl; // 可能 0 1 也可能 1 1 但我测试是 0 1,解释为<<没有规定运算顺序 int i = f() * g(); // 可能先计算f(),也可能先计算g()
-
-
(2)传递数组
-
// 传递数组参数的三种方式 // 1. 标记指定长度 void print(const char *cp) { if (cp) while (*cp) cout << *cp++; } // 以0结尾 // 2. 使用标准库的规范 int j[2] = {1, 2}; void print(const int *beg, const int *end) { while (beg != end) cout << *beg++ << endl; } print(begin(j), end(j)); end实现:https://www.cnblogs.com/yuanyb/p/11402831.html // 3. 显示传递数组大小的形参 void print(const int ia[], size_t size) { ... } print(j, end(j) - begin(j)); // 数组引用形参 void print(int (&arr) [10]) // 必须传递10个整数的数组,其余的不行。 (&arr)括号不能去掉,去掉之后,&arr[10]是引用的数组,是数组;原来是10个整数的整形数组的引用,是引用。 // 该引用参数绑定到对应的实参上,也就是绑定到数组上,数组的大小是构成数组类型的一部分,所以被绑定的实参数组必须要是10个整数的数组。 // 题目:下面代码存在问题 void print(const int ia[10]) { for (size_t i = 0; i != 10; ++i) cout << ia[i] << endl; // 数组上写着数组大小是10,但是实际上,传输过来的不一定是10,而循环写了10,可能会越界。 // 而想要传递数组大小为10的数组引用,必须 const int (&ia) [10],记住传递数组引用。 }
-
-
(3)返回值
-
// 1. 返回值 与 参数传递 类似,值传递都会拷贝,引用传递则不拷贝。 // 2. 返回值 不要 引用 或者 指针指向 函数内的局部变量。 // 3. 引用返回左值 char& get_val(string &str, string::size_type idx) { return str[idx]; } string s("a value"); get_val(s, 0) = 'A'; // 可以正确使用 cout << s << endl; // A value
-
-
(4)返回数组指针
-
// 定义返回数组的指针或引用的函数比较繁琐,具体采取措施如下 // 1. 使用类型别名 typedef int arrT[10]; // arrT表示的是一个含有10个整数的数组 using arrT = int[10]; arrT* func(int i); // func返回一个指向含有10个整数数组的指针 // 2. 声明一个返回数组指针的函数 // 在此之前,回顾一下: int arr[10]; // arr是一个含有10个整数的数组 int *p1[10]; // p1是一个含有10个整数指针的数组 // 可见,数组的优先级 [] 高于 指针的优先级 * int (*p2)[10] = &arr; // p2是一个指针,指向10个整数的数组。 // 正题: int (*func(int i)) [10]; // 返回数组指针的函数 // 解释: func(int i ); // 表示一个传int类型的函数 (*func(int i)); // 表示我们可以对返回值解引用 (*func(int i)) [10]; // 表示解引用func的调用将得到一个大小是10的数组 int (*func(int i)) [10]; // 表示数组中的元素是int类型 // 3. 使用尾置返回类型 auto func(int i) -> int(*)[]; // 为了表示使用位置返回类型,在原本放放回类型的地方放入auto,然后再在参数列表后面使用 -> + 返回类型。 // 4. 使用decltype : 仅在知道返回的指针会指向哪个可能的数组 int odd [] = {1,3,5,7,9}; int even [] = {2,4,6,8,10}; decltype(odd) *arrPtr(int i) { return (i % 2) ? &odd : & even; } // 返回一个指向数组的指针。 // 需要注意的是,decltype并不负责把数组类型转换成对应的指针,所以最后还需要加上*
-
const
-
(1)在函数重载中
-
// 形参加顶层const,将被忽略 —— 出自 C++ primer 191 void fcn(int i) {} void fcn(const int i) {} // 以上两个函数的将出现重复定义的错误 // 函数加const,由于函数签名不一致,不会导致重复定义的问题 void fcn(int i) {} void fcn(int i) const {}
-
static在类中
-
(1)可以修饰成员变量,成员函数 和 类本身。
-
(1-1)修饰类本身,与修饰普通变量一致。
-
(1-2)修饰成员变量,可以分private与public,若不使用,应该不分配空间,若要使用,必须先进行初始化(定义);初始化过程:在类外(全局区,函数内不行)定义,([类型] 类名称:: 静态变量名 = ...) ,无论是private还是public都可以这样定义,但是访问的时候,类外只能访问public的静态变量。
-
(1-3)修饰成员函数,则成为静态成员函数,不能访问类的非静态成员(变量+函数)。
-
this指针
-
基本认识:
-
(1)一个对象的this指针并不属于对象自身的一部分,即,对sizeof(对象)没有影响。
-
(2)类的非静态成员函数执行时,this指针将作为隐含形参,指向对象的地址,对类数据成员的访问全部都是通过this指针进行。
-
-
使用方法:
-
(1)在类的非静态成员函数返回对象本身时,直接使用 return *this。
-
(2)当参数名与成员数据名相同时,this -> n = n,不能写成 n = n 。
-
-
this的类型:
-
(1)普通成员函数:A * const this,详细见网址。
-
(2)const成员函数:const A * const this,因为const成员函数只能访问const变量和const函数。注:案列中,成员变量 int age;直接使用 this->age被const函数使用。
-
enum枚举
-
传统做法问题:
-
作用域不受限,容易引起命名冲突。
-
enum color {RED, BLUE}; enum feeling {EXCITED, BLUE}; // BLUE 导致无法通过编译
-
-
会隐式转换成int。
-
表征枚举变量的实际类型不能明确指定,无法支持枚举类型的前向声明。
-
-
经典做法:
-
加前缀 COLOR_BLUE、FEELING_BLUE,或者使用命名空间:
-
namespace Color { enum Type {RED = 15, BLUE, YELLOW}; }; // 调用: 1. Color::Type c = Color::BLUE // 2. using namespace Color; Type c = BLUE
-
-
结构体限定:
-
struct Color { enum Type{RED = 102, YELLOW, BLUE}; }; // 调用:1. Color c; cout << c.RED << endl; --> 102 // 2. Color::Type c = Color::BLUE; cout << c << endl; --> 104
-
-
C++11的枚举类 (未完)
-
新的enum作用域不在全局。
-
不能隐式转换成其他类型。
-
-
抽象类
-
基本认识:
-
(1)存在纯虚函数,通过使用vitrual定义成员函数,并赋值为0。
-
(2)不能创建对象。
-
-
进阶认识:
-
(1)派生类若没有完全覆盖纯虚函数,则派生类也是抽象类,对应基本认识(1)。
-
(2)成员函数可以调用纯虚函数,但构造和析构函数不可以。
-
(3)纯虚函数可以有自己的构造和析构函数。
-
友元类
-
基本认识:
-
(1)友元提供了一种访问类 私有 或者 保护 成员的机制。
-
(1-1)友元函数:普通函数访问某个类中的私有或者保护成员。
-
(1-2)友元类:类A中的成员函数访问类B中的私有或者保护成员。
-
-
(2)友元关系单向性,如下例,B是A的友元,但是A不是B的友元,即无法访问B的私有与保护成员。
-
(3)友元关系没有继承性,如下例,若再有C继承A,B无法直接访问C的私有或保护成员。
-
(4)友元关系没有传递性,如下例,若再有C是B的友元,友元类C无法直接访问A的私有与保护成员。但是还是可以访问:
-
#include <iostream> using namespace std; class A{ public: A(int _a):a(_a){}; friend class B; private: int a; }; class B{ private: int getb(A ca) { return ca.a; }; friend class C; }; class C { public: int getc(B b, A a) { return b.getb(a); } }; int main() { A a(3); B b; C c; cout<<c.getc(b, a)<<endl; return 0; }
-
-
-
使用方式:
-
(1)类中任何区域声明,类外部定义。
-
// 友元函数 class A { int age; friend int GetAge(A &a); } int GetAge(A &a) { return a.age; } // 友元类 class A {int age; friend class B; } class B {int GetAge(A a) {return a.age;} } A a(3); B b; b.GetAge(a);
-
-
虚继承
-
基本认识:
-
(1)多继承,常见为菱形继承,会导致最终派生类的命名冲突和数据冗余。
-
class A { int a; }; class B : public A {int b; }; class C : public A {int c; }; class D : public B, public C {int d; }; // D 拥有两份a, 使用a赋值时,需要指明从哪条路径上来的,例如 B::a,否则产生二义性
-
-
(2)虚继承常见于多继承关系,用于防止最终派生类拥有多份基类数据。
-
(3)一个派生类定义虚继承,表示它承诺共享自己的基类,被共享的基类成为虚基类。
-
class A { int a; }; class B : virtual public A {int b; }; // 定义虚继承的是 中间派生类 class C : virtual public A {int c; }; // 定义虚继承的是 中间派生类 class D : public B, public C {int d; };
-
-
(4)即便使用虚继承,也仍会出现二义性:
-
class A { int a; int x; }; class B : virtual public A {int b; int x; }; class C : virtual public A {int c; }; class D : public B, public C {int d; Get(x)}; // D直接访问x,将访问B的x,若B没有x,则访问A的x // 若C也有x,则出现二义性
-
-
(5)构造顺序从最低的虚基类开始。
-
智能指针
-
基本认识:
-
(1)智能指针主要用于管理堆上的内存分配。
-
(2)它将普通指针封装成栈对象,当程序执行超过栈对象作用域时,会调用析构函数,释放申请的堆内存。
-
-
类型:
-
(1)auto_ptr
-
(2)unique_ptr
-
(2-1)独占式,同一时间只允许有一个智能指针可以指向对象。
-
(2-2)使用std::move可以转移对象的所有权。
-
-
(3)shared_ptr
-
(3-1)有一个引用计数,use_count(),只有当引用计数为0时,程序才会释放对象内存。
-
(3-2)make_shared 返回一个shared_ptr指针。
-
(3-3)还是会引起内存泄露。
-
(3-3-1)将shared_ptr放入容器中,后续可能不需要某些元素,必须使用erase删除那部分元素。
-
(3-3-2)两个shared_ptr指针A,B,各自指向一个内存NEW_A,NEW_B,而内存NEW_A中存在一个指向NEW_B的指针C,内存NEW_B中存在一个指向NEW_A的指针D。此时,NEW_A、NEW_B的引用计数都是2。在A、B生命周期结束时断开A->NEW_A的引用,B->NEW_B的引用,但是因为引用计数不为0,无法调用A,B的析构函数,导致指针C\D存在,无法释放内存;解决方法:将C或者D换成weak_ptr即可。
-
-
// 使用方式,make_shared返回shared_ptr,赋值拷贝函数 shared_ptr<T> i_pt
-
-