This commit is contained in:
Light-City
2019-11-05 16:56:07 +08:00
parent 3a495d7fa2
commit 8edbbbc5a2
179 changed files with 428 additions and 26 deletions

BIN
basic_content/::/:: Executable file

Binary file not shown.

20
basic_content/::/::.cpp Normal file
View File

@@ -0,0 +1,20 @@
#include <iostream>
using namespace std;
int count=0; // 全局(::)的count
class A {
public:
static int count; // 类A的count (A::count)
};
// 静态变量必须在此处定义
int A::count;
int main() {
::count=1; // 设置全局的count为1
A::count=5; // 设置类A的count为2
cout<<A::count<<endl;
// int count=3; // 局部count
// count=4; // 设置局部的count为4
return 0;
}

View File

@@ -0,0 +1,14 @@
# :: 范围解析运算符那些事
## 关于作者:
个人公众号:
![](../img/wechat.jpg)
- 全局作用域符(::name用于类型名称类、类成员、成员函数、变量等表示作用域为全局命名空间
- 类作用域符class::name用于表示指定类型的作用域范围是具体某个类的
- 命名空间作用域符namespace::name:用于表示指定类型的作用域范围是具体某个命名空间的
具体代码见:[::.cpp](::.cpp)

View File

@@ -0,0 +1,58 @@
# 纯虚函数和抽象类
## 关于作者:
个人公众号:
![](../img/wechat.jpg)
## 1.纯虚函数与抽象类
C++中的纯虚函数(或抽象函数)是我们没有实现的虚函数!我们只需声明它!通过声明中赋值0来声明纯虚函数
对应的代码:[test.cpp](./test.cpp)
* 纯虚函数:没有函数体的虚函数
* 抽象类:包含纯虚函数的类
对应的代码:[pure_virtual.cpp](./pure_virtual.cpp)
抽象类只能作为基类来派生新类使用,不能创建抽象类的对象,抽象类的指针和引用->由抽象类派生出来的类的对象!
## 2.实现抽象类
抽象类中:在成员函数内可以调用纯虚函数,在构造函数/析构函数内部不能使用纯虚函数。
如果一个类从抽象类派生而来,它必须实现了基类中的所有纯虚函数,才能成为非抽象类。
对应的代码:[abstract.cpp](./abstract.cpp)
## 3.重要点
- 纯虚函数使一个类变成抽象类
对应的代码:[interesting_facts1.cpp](./interesting_facts1.cpp)
- 抽象类类型的指针和引用
对应的代码:[interesting_facts2.cpp](./interesting_facts2.cpp)
- 如果我们不在派生类中覆盖纯虚函数,那么派生类也会变成抽象类。
对应的代码:[interesting_facts3.cpp](./interesting_facts3.cpp)
- 抽象类可以有构造函数
对应的代码:[interesting_facts4.cpp](./interesting_facts4.cpp)
- 构造函数不能是虚函数,而析构函数可以是虚析构函数。
对应的代码:[interesting_facts5.cpp](./interesting_facts5.cpp)
当基类指针指向派生类对象并删除对象时,我们可能希望调用适当的析构函数。如果析构函数不是虚拟的,则只能调用基类析构函数。
## 4.完整实例
抽象类由派生类继承实现!
对应的代码:[derived_full.cpp](./derived_full.cpp)

View File

@@ -0,0 +1,31 @@
/**
* @file abstract.cpp
* @brief 抽象类中:在成员函数内可以调用纯虚函数,在构造函数/析构函数内部不能使用纯虚函数
* 如果一个类从抽象类派生而来,它必须实现了基类中的所有纯虚函数,才能成为非抽象类
* @author 光城
* @version v1
* @date 2019-07-20
*/
#include<iostream>
using namespace std;
class A {
public:
virtual void f() = 0;
void g(){ this->f(); }
A(){}
};
class B:public A{
public:
void f(){ cout<<"B:f()"<<endl;}
};
int main(){
B b;
b.g();
return 0;
}

View File

@@ -0,0 +1,32 @@
/**
* @file derived_full.cpp
* @brief 完整示例!抽象类由派生类继承实现!
* @author 光城
* @version v1
* @date 2019-07-20
*/
#include<iostream>
using namespace std;
class Base
{
int x;
public:
virtual void fun() = 0;
int getX() { return x; }
};
class Derived: public Base
{
int y;
public:
void fun() { cout << "fun() called"; } ///< 实现了fun()函数
};
int main(void)
{
Derived d;
d.fun();
return 0;
}

View File

@@ -0,0 +1,29 @@
/**
* @file interesting_facts1.cpp
* @brief 纯虚函数使一个类变成抽象类
* @author 光城
* @version v1
* @date 2019-07-20
*/
#include<iostream>
using namespace std;
/**
* @brief 抽象类至少包含一个纯虚函数
*/
class Test
{
int x;
public:
virtual void show() = 0;
int getX() { return x; }
};
int main(void)
{
Test t; //error! 不能创建抽象类的对象
return 0;
}

View File

@@ -0,0 +1,38 @@
/**
* @file interesting_facts2.cpp
* @brief 抽象类类型的指针和引用
* @author 光城
* @version v1
* @date 2019-07-20
*/
#include<iostream>
using namespace std;
/**
* @brief 抽象类至少包含一个纯虚函数
*/
class Base
{
int x;
public:
virtual void show() = 0;
int getX() { return x; }
};
class Derived: public Base
{
public:
void show() { cout << "In Derived \n"; }
Derived(){}
};
int main(void)
{
//Base b; //error! 不能创建抽象类的对象
//Base *b = new Base(); error!
Base *bp = new Derived(); // 抽象类的指针和引用 -> 由抽象类派生出来的类的对象
bp->show();
return 0;
}

View File

@@ -0,0 +1,30 @@
/**
* @file interesting_facts3.cpp
* @brief 如果我们不在派生类中覆盖纯虚函数,那么派生类也会变成抽象类。
* @author 光城
* @version v1
* @date 2019-07-20
*/
#include<iostream>
using namespace std;
class Base
{
int x;
public:
virtual void show() = 0;
int getX() { return x; }
};
class Derived: public Base
{
public:
// void show() { }
};
int main(void)
{
Derived d; //error! 派生类没有实现纯虚函数,那么派生类也会变为抽象类,不能创建抽象类的对象
return 0;
}

View File

@@ -0,0 +1,35 @@
/**
* @file interesting_facts4.cpp
* @brief 抽象类可以有构造函数
* @author 光城
* @version v1
* @date 2019-07-20
*/
#include<iostream>
using namespace std;
// An abstract class with constructor
class Base
{
protected:
int x;
public:
virtual void fun() = 0;
Base(int i) { x = i; }
};
class Derived: public Base
{
int y;
public:
Derived(int i, int j):Base(i) { y = j; }
void fun() { cout << "x = " << x << ", y = " << y; }
};
int main(void)
{
Derived d(4, 5);
d.fun();
return 0;
}

View File

@@ -0,0 +1,30 @@
/**
* @file interesting_facts5.cpp
* @brief 构造函数不能是虚函数,而析构函数可以是虚析构函数。
* 例如:当基类指针指向派生类对象并删除对象时,我们可能希望调用适当的析构函数。如果析构函数不是虚拟的,则只能调用基类析构函数。
* @author 光城
* @version v1
* @date 2019-07-20
*/
#include<iostream>
using namespace std;
class Base {
public:
Base() { cout << "Constructor: Base" << endl; }
virtual ~Base() { cout << "Destructor : Base" << endl; }
};
class Derived: public Base {
public:
Derived() { cout << "Constructor: Derived" << endl; }
~Derived() { cout << "Destructor : Derived" << endl; }
};
int main() {
Base *Var = new Derived();
delete Var;
return 0;
}

View File

@@ -0,0 +1,29 @@
/**
* @file pure_virtual.cpp
* @brief 纯虚函数:没有函数体的虚函数
* 抽象类:包含纯虚函数的类
*
* @author 光城
* @version v1
* @date 2019-07-20
*/
#include<iostream>
using namespace std;
class A
{
private:
int a;
public:
virtual void show()=0; //< 纯虚函数
};
int main()
{
// 抽象类只能作为基类来派生新类使用,不能创建抽象类的对象,抽象类的指针和引用->由抽象类派生出来的类的对象!
// A a; // error 抽象类,不能创建对象
A *a1; // ok 可以定义抽象类的指针
// A *a2 = new A; // error,A是抽象类不能创建对象
}

View File

@@ -0,0 +1,23 @@
/**
* @file test.cpp
* @brief C++中的纯虚函数(或抽象函数)是我们没有实现的虚函数!我们只需声明它!通过声明中赋值0来声明纯虚函数
* 纯虚函数:没有函数体的虚函数
* @author 光城
* @version v1
* @date 2019-07-20
*/
/**
* @brief 抽象类
*/
class Test
{
// Data members of class
public:
// Pure Virtual Function
virtual void show() = 0;
/* Other members */
};

View File

@@ -0,0 +1,56 @@
# assert那些事
## 关于作者:
个人公众号:
![](../img/wechat.jpg)
## 1.第一个断言案例
断言,**是宏,而非函数**。assert 宏的原型定义在 <assert.h>C<cassert>C++)中,其作用是如果它的条件返回错误,则终止程序执行。可以通过定义 NDEBUG 来关闭 assert但是需要在源代码的开头include <assert.h> 之前。
```c
void assert(int expression);
```
对应代码:[assert.c](./assert.c)
```c
#include <stdio.h>
#include <assert.h>
int main()
{
int x = 7;
/* Some big code in between and let's say x
is accidentally changed to 9 */
x = 9;
// Programmer assumes x to be 7 in rest of the code
assert(x==7);
/* Rest of the code */
return 0;
}
```
输出:
```c
assert: assert.c:13: main: Assertion `x==7' failed.
```
可以看到输出会把源码文件,行号错误位置,提示出来!
## 2.断言与正常错误处理
断言主要用于检查逻辑上不可能的情况。例如,它们可用于检查代码在开始运行之前所期望的状态,或者在运行完成后检查状态。与正常的错误处理不同,断言通常在运行时被禁用。
忽略断言:
在代码开头加上:
```c++
#define NDEBUG // 加上这行,则 assert 不可用
```
对应学习的代码:[ignore_assert.c](./ignore_assert.c)

BIN
basic_content/assert/assert Executable file

Binary file not shown.

View File

@@ -0,0 +1,18 @@
#include <stdio.h>
#include <assert.h>
int main()
{
int x = 7;
/* Some big code in between and let's say x
* is accidentally changed to 9 */
x = 9;
// Programmer assumes x to be 7 in rest of the code
assert(x==7);
/* Rest of the code */
return 0;
}

BIN
basic_content/assert/iga Executable file

Binary file not shown.

View File

@@ -0,0 +1,19 @@
/**
* @file ignore_assert.c
* @brief 忽略断言
* @author 光城
* @version v1
* @date 2019-07-25
*/
# define NDEBUG
#include<assert.h>
int main(){
int x=7;
assert(x==5);
return 0;
}

179
basic_content/bit/README.md Normal file
View File

@@ -0,0 +1,179 @@
## 关于作者:
个人公众号:
![](../img/wechat.jpg)
## Bit field 是什么?
“ 位域 “ 或 “ 位段 “(Bit field)为一种数据结构,可以把数据以位的形式紧凑的储存,并允许程序员对此结构的位进行操作。这种数据结构的一个好处是它可以使数据单元节省储存空间,当程序需要成千上万个数据单元时,这种方法就显得尤为重要。第二个好处是位段可以很方便的访问一个整数值的部分内容从而可以简化程序源代码。而这种数据结构的缺点在于,位段实现依赖于具体的机器和系统,在不同的平台可能有不同的结果,这导致了位段在本质上是不可移植的。
- 位域在内存中的布局是与机器有关的
- 位域的类型必须是整型或枚举类型,带符号类型中的位域的行为将因具体实现而定
- 取地址运算符(&)不能作用于位域,任何指针都无法指向类的位域
## 位域使用
位域通常使用结构体声明, 该结构声明为每个位域成员设置名称,并决定其宽度:
```
struct bit_field_name
{
type member_name : width;
};
```
| Elements | Description |
| -------------- | ------------------------------------------------------------ |
| bit_field_name | 位域结构名 |
| type | 位域成员的类型,必须为 int、signed int 或者 unsigned int 类型 |
| member_name | 位域成员名 |
| width | 规定成员所占的位数 |
例如声明如下一个位域:
```
struct _PRCODE
{
unsigned int code1: 2;
unsigned int cdde2: 2;
unsigned int code3: 8;
};
struct _PRCODE prcode;
```
该定义使 `prcode`包含 2 个 2 Bits 位域和 1 个 8 Bits 位域,我们可以使用结构体的成员运算符对其进行赋值
```
prcode.code1 = 0;
prcode.code2 = 3;
procde.code3 = 102;
```
赋值时要注意值的大小不能超过位域成员的容量,例如 prcode.code3 为 8 Bits 的位域成员,其容量为 2^8 = 256即赋值范围应为 [0,255]。
## 位域的大小和对齐
### 位域的大小
例如以下位域:
```
struct box
{
unsigned int a: 1;
unsigned int : 3;
unsigned int b: 4;
};
```
该位域结构体中间有一个未命名的位域,占据 3 Bits仅起填充作用并无实际意义。 填充使得该结构总共使用了 8 Bits。但 C 语言使用 unsigned int 作为位域的基本单位,即使一个结构的唯一成员为 1 Bit 的位域,该结构大小也和一个 unsigned int 大小相同。 有些系统中unsigned int 为 16 Bits在 x86 系统中为 32 Bits。文章以下均默认 unsigned int 为 32 Bits。
### 位域的对齐
一个位域成员不允许跨越两个 unsigned int 的边界,如果成员声明的总位数超过了一个 unsigned int 的大小, 那么编辑器会自动移位位域成员,使其按照 unsigned int 的边界对齐。
例如:
```
struct stuff
{
unsigned int field1: 30;
unsigned int field2: 4;
unsigned int field3: 3;
};
```
`field1` + `field2` = 34 Bits超出 32 Bits, 编译器会将`field2`移位至下一个 unsigned int 单元存放, stuff.field1 和 stuff.field2 之间会留下一个 2 Bits 的空隙, stuff.field3 紧跟在 stuff.field2 之后,该结构现在大小为 2 * 32 = 64 Bits。
这个空洞可以用之前提到的未命名的位域成员填充,我们也可以使用一个宽度为 0 的未命名位域成员令下一位域成员与下一个整数对齐。
例如:
```
struct stuff
{
unsigned int field1: 30;
unsigned int : 2;
unsigned int field2: 4;
unsigned int : 0;
unsigned int field3: 3;
};
```
这里 stuff.field1 与 stuff.field2 之间有一个 2 Bits 的空隙stuff.field3 则存储在下一个 unsigned int 中,该结构现在大小为 3 * 32 = 96 Bits。
学习代码见:[learn.cpp](learn.cpp)
## 位域的初始化和位的重映射
### 初始化
位域的初始化与普通结构体初始化的方法相同,这里列举两种,如下:
```
struct stuff s1= {20,8,6};
```
或者直接为位域成员赋值
```
struct stuff s1;
s1.field1 = 20;
s1.field2 = 8;
s1.field3 = 4;
```
### 位域的重映射 (Re-mapping)
声明一个 大小为 32 Bits 的位域
```
struct box {
unsigned int ready: 2;
unsigned int error: 2;
unsigned int command: 4;
unsigned int sector_no: 24;
}b1;
```
#### 利用重映射将位域归零
```
int* p = (int *) &b1; // 将 "位域结构体的地址" 映射至 "整形int*) 的地址"
*p = 0; // 清除 s1将各成员归零
```
#### 利用联合 (union) 将 32 Bits 位域 重映射至 unsigned int 型
先简单介绍一下联合
> “联合” 是一种特殊的类,也是一种构造类型的数据结构。在一个 “联合” 内可以定义多种不同的数据类型, 一个被说明为该 “联合” 类型的变量中,允许装入该 “联合” 所定义的任何一种数据,这些数据共享同一段内存,以达到节省空间的目的
>
> “联合” 与 “结构” 有一些相似之处。但两者有本质上的不同。在结构中各成员有各自的内存空间, 一个结构变量的总长度是各成员长度之和(空结构除外,同时不考虑边界调整)。而在 “联合” 中,各成员共享一段内存空间, 一个联合变量的长度等于各成员中最长的长度。应该说明的是, 这里所谓的共享不是指把多个成员同时装入一个联合变量内, 而是指该联合变量可被赋予任一成员值,但每次只能赋一种值, 赋入新值则冲去旧值。
我们可以声明以下联合:
```
union u_box {
struct box st_box;
unsigned int ui_box;
};
```
x86 系统中 unsigned int 和 box 都为 32 Bits, 通过该联合使 st_box 和 ui_box 共享一块内存。具体位域中哪一位与 unsigned int 哪一位相对应,取决于编译器和硬件。
利用联合将位域归零,代码如下:
```
union u_box u;
u.ui_box = 0;
```
> 学习参考自:<http://www.yuan-ji.me/C-C-%E4%BD%8D%E5%9F%9F-Bit-fields-%E5%AD%A6%E4%B9%A0%E5%BF%83%E5%BE%97/>

View File

@@ -0,0 +1,19 @@
#include<iostream>
using namespace std;
struct stuff
{
unsigned int field1: 30;
unsigned int : 2;
unsigned int field2: 4;
unsigned int : 0;
unsigned int field3: 3;
};
int main(){
struct stuff s={1,3,5};
cout<<s.field1<<endl;
cout<<s.field2<<endl;
cout<<s.field3<<endl;
cout<<sizeof(s)<<endl;
return 0;
}

View File

@@ -0,0 +1,41 @@
# C实现C++的面向对象特性
## 关于作者:
个人公众号:
![](../img/wechat.jpg)
## 1.C++实现案例
C++中的多态:在C++中会维护一张虚函数表,根据赋值兼容规则,我们知道父类的指针或者引用是可以指向子类对象的。
如果一个父类的指针或者引用调用父类的虚函数则该父类的指针会在自己的虚函数表中查找自己的函数地址,如果该父类对象的指针或者引用指向的是子类的对象,而且该子类已经重写了父类的虚函数,则该指针会调用子类的已经重写的虚函数。
学习案例代码见:[c++_examp.cpp](./c++_examp.cpp)
## 2.C实现
- 封装
C语言中是没有class类这个概念的但是有struct结构体我们可以考虑使用struct来模拟
使用函数指针把属性与方法封装到结构体中。
- 继承
结构体嵌套
- 多态
类与子类方法的函数指针不同
在C语言的结构体内部是没有成员函数的如果实现这个父结构体和子结构体共有的函数呢我们可以考虑使用函数指针来模拟。但是这样处理存在一个缺陷就是父子各自的函数指针之间指向的不是类似C++中维护的虚函数表而是一块物理内存,如果模拟的函数过多的话就会不容易维护了。
模拟多态,必须保持函数指针变量对齐(在内容上完全一致,而且变量对齐上也完全一致)。否则父类指针指向子类对象,运行崩溃!
学习案例代码见:[c_examp.c](./c_examp.c)

BIN
basic_content/c_poly/c++_examp Executable file

Binary file not shown.

View File

@@ -0,0 +1,42 @@
/**
* @file c++_examp.cpp
* @brief c++中的多态
* @author 光城
* @version v1
* @date 2019-08-06
*/
#include <iostream>
using namespace std;
class A
{
public:
virtual void f()//虚函数实现
{
cout << "Base A::f() " << endl;
}
};
class B:public A // 必须为共有继承否则后面调用不到class默认为私有继承
{
public:
virtual void f()//虚函数实现,子类中virtual关键字可以没有
{
cout << "Derived B::f() " << endl;
}
};
int main()
{
A a;//基类对象
B b;//派生类对象
A* pa = &a;//父类指针指向父类对象
pa->f();//调用父类的函数
pa = &b; //父类指针指向子类对象,多态实现
pa->f();//调用派生类同名函数
return 0;
}

BIN
basic_content/c_poly/c_examp Executable file

Binary file not shown.

View File

@@ -0,0 +1,55 @@
/**
* @file c_examp.c
* @brief C实现多态
* @author 光城
* @version v1
* @date 2019-08-06
*/
#include <stdio.h>
/// 重定义一个函数指针类型
typedef void (*pf) ();
/**
* @brief 父类
*/
typedef struct _A
{
pf _f;
}A;
/**
* @brief 子类
*/
typedef struct _B
{
A _b; ///< 在子类中定义一个基类的对象即可实现对父类的继承。
}B;
void FunA()
{
printf("%s\n","Base A::fun()");
}
void FunB()
{
printf("%s\n","Derived B::fun()");
}
int main()
{
A a;
B b;
a._f = FunA;
b._b._f = FunB;
A *pa = &a;
pa->_f();
pa = (A *)&b; /// 让父类指针指向子类的对象,由于类型不匹配所以要进行强转
pa->_f();
return 0;
}

View File

@@ -0,0 +1,405 @@
## 关于作者
微信公众号:
![](../img/wechat.jpg)
## 1.const含义
常类型是指使用类型修饰符**const**说明的类型,常类型的变量或对象的值是不能被更新的。
## 2.const作用
1可以定义常量
```c++
const int a=100;
```
2类型检查
const常量与#define宏定义常量的区别const常量具有类型编译器可以进行安全检查#define宏定义没有数据类型只是简单的字符串替换不能进行安全检查。
3防止修改起保护作用增加程序健壮性
```c++
void f(const int i){
i++; //error!
}
```
4可以节省空间避免不必要的内存分配
const定义常量从汇编的角度来看只是给出了对应的内存地址而不是像#define一样给出的是立即数所以const定义的常量在程序运行过程中只有一份拷贝而#define定义的常量在内存中有若干个拷贝。
## 3.const对象默认为文件局部变量
<p><font style="color:red">注意非const变量默认为extern。要使const变量能够在其他文件中访问必须在文件中显式地指定它为extern。</font></p>
> 未被const修饰的变量在不同文件的访问
```c++
// file1.cpp
int ext
// file2.cpp
#include<iostream>
/**
* by 光城
* compile: g++ -o file file2.cpp file1.cpp
* execute: ./file
*/
extern int ext;
int main(){
std::cout<<(ext+10)<<std::endl;
}
```
> const常量在不同文件的访问
```c++
//extern_file1.cpp
extern const int ext=12;
//extern_file2.cpp
#include<iostream>
/**
* by 光城
* compile: g++ -o file const_file2.cpp const_file1.cpp
* execute: ./file
*/
extern const int ext;
int main(){
std::cout<<ext<<std::endl;
}
```
<p><font style="color:red">小结可以发现未被const修饰的变量不需要extern显示声明而const常量需要显示声明extern并且需要做初始化因为常量在定义后就不能被修改所以定义时必须初始化。</font></p>
## 4.定义常量
```c++
const int b = 10;
b = 0; // error: assignment of read-only variable b
const string s = "helloworld";
const int i,j=0 // error: uninitialized const i
```
上述有两个错误第一b为常量不可更改第二i为常量必须进行初始化(因为常量在定义后就不能被修改,所以定义时必须初始化。)
## 5.指针与const
与指针相关的const有四种
```c++
const char * a; //指向const对象的指针或者说指向常量的指针。
char const * a; //同上
char * const a; //指向类型对象的const指针。或者说常指针、const指针。
const char * const a; //指向const对象的const指针。
```
小结:如果*const*位于`*`的左侧则const就是用来修饰指针所指向的变量即指针指向为常量如果const位于`*`的右侧,*const*就是修饰指针本身,即指针本身是常量。
具体使用如下:
1指向常量的指针
```c++
const int *ptr;
*ptr = 10; //error
```
ptr是一个指向int类型const对象的指针const定义的是int类型也就是ptr所指向的对象类型而不是ptr本身所以ptr可以不用赋初始值。但是不能通过ptr去修改所指对象的值。
除此之外也不能使用void`*`指针保存const对象的地址必须使用const void`*`类型的指针保存const对象的地址。
```c++
const int p = 10;
const void * vp = &p;
void *vp = &p; //error
```
另外一个重点是:**允许把非const对象的地址赋给指向const对象的指针**。
将非const对象的地址赋给const对象的指针:
```c++
const int *ptr;
int val = 3;
ptr = &val; //ok
```
我们不能通过ptr指针来修改val的值即使它指向的是非const对象!
我们不能使用指向const对象的指针修改基础对象然而如果该指针指向了非const对象可用其他方式修改其所指的对象。可以修改const指针所指向的值的但是不能通过const对象指针来进行而已如下修改
```c++
int *ptr1 = &val;
*ptr1=4;
cout<<*ptr<<endl;
```
<p><font style="color:red">小结:对于指向常量的指针,不能通过指针来修改对象的值。<br/>也不能使用void`*`指针保存const对象的地址必须使用const void`*`类型的指针保存const对象的地址。<br/>允许把非const对象的地址赋值给const对象的指针如果要修改指针所指向的对象值必须通过其他方式修改不能直接通过当前指针直接修改。</font></p>
2常指针
const指针必须进行初始化且const指针的值不能修改。
```c++
#include<iostream>
using namespace std;
int main(){
int num=0;
int * const ptr=&num; //const指针必须初始化且const指针的值不能修改
int * t = &num;
*t = 1;
cout<<*ptr<<endl;
}
```
上述修改ptr指针所指向的值可以通过非const指针来修改。
最后当把一个const常量的地址赋值给ptr时候由于ptr指向的是一个变量而不是const常量所以会报错出现const int* -> int *错误!
```c++
#include<iostream>
using namespace std;
int main(){
const int num=0;
int * const ptr=&num; //error! const int* -> int*
cout<<*ptr<<endl;
}
```
上述若改为 const int *ptr或者改为const int *const ptr都可以正常
3指向常量的常指针
理解完前两种情况,下面这个情况就比较好理解了:
```c++
const int p = 3;
const int * const ptr = &p;
```
ptr是一个const指针然后指向了一个int 类型的const对象。
## 6.函数中使用const
> cost修饰函数返回值
这个跟const修饰普通变量以及指针的含义基本相同
1const int
```c++
const int func1();
```
这个本身无意义,因为参数返回本身就是赋值给其他的变量!
2const int*
```c++
const int* func2();
```
指针指向的内容不变。
3int *const
```c++
int *const func2();
```
指针本身不可变。
> const修饰函数参数
1传递过来的参数及指针本身在函数内不可变无意义
```c++
void func(const int var); // 传递过来的参数不可变
void func(int *const var); // 指针本身不可变
```
表明参数在函数体内不能被修改但此处没有任何意义var本身就是形参在函数内不会改变。包括传入的形参是指针也是一样。
输入参数采用“值传递”由于函数将自动产生临时变量用于复制该参数该输入参数本来就无需保护所以不要加const 修饰。
2参数指针所指内容为常量不可变
```c++
void StringCopy(char *dst, const char *src);
```
其中src 是输入参数dst 是输出参数。给src加上const修饰后如果函数体内的语句试图改动src的内容编译器将指出错误。这就是加了const的作用之一。
3参数为引用为了增加效率同时防止修改。
```c++
void func(const A &a)
```
对于非内部数据类型的参数而言象void func(A a) 这样声明的函数注定效率比较底。因为函数体内将产生A 类型
的临时对象用于复制参数a而临时对象的构造、复制、析构过程都将消耗时间。
为了提高效率可以将函数声明改为void func(A &a),因为“引用传递”仅借用一下参数的别名而已,不需要产生临
时对象。但是函数void func(A &a) 存在一个缺点:
“引用传递”有可能改变参数a这是我们不期望的。解决这个问题很容易加const修饰即可因此函数最终成为
void func(const A &a)。
以此类推是否应将void func(int x) 改写为void func(const int &x),以便提高效率?完全没有必要,因为内部数
据类型的参数不存在构造、析构的过程,而复制也非常快,“值传递”和“引用传递”的效率几乎相当。
<p><font style="color:red">小结对于非内部数据类型的输入参数应该将“值传递”的方式改为“const 引用传递”目的是提高效率。例如将void func(A a) 改为void func(const A &a)。<br/>对于内部数据类型的输入参数不要将“值传递”的方式改为“const 引用传递”。否则既达不到提高效率的目的又降低了函数的可理解性。例如void func(int x) 不应该改为void func(const int &x)。</font></p>
以上解决了两个面试问题:
1如果函数需要传入一个指针是否需要为该指针加上const把const加在指针不同的位置有什么区别
2如果写的函数需要传入的参数是一个复杂类型的实例传入值参数或者引用参数有什么区别什么时候需要为传入的引用参数加上const。
## 7.类中使用const
在一个类中任何不会修改数据成员的函数都应该声明为const类型。如果在编写const成员函数时不慎修改
数据成员或者调用了其它非const成员函数编译器将指出错误这无疑会提高程序的健壮性。使用const关
字进行说明的成员函数称为常成员函数。只有常成员函数才有资格操作常量或常对象没有使用const关键字
明的成员函数不能用来操作常对象。
对于类中的const成员变量必须通过初始化列表进行初始化如下所示
```c++
class Apple
{
private:
int people[100];
public:
Apple(int i);
const int apple_number;
};
Apple::Apple(int i):apple_number(i)
{
}
```
const对象只能访问const成员函数,而非const对象可以访问任意的成员函数,包括const成员函数.
例如:
```c++
//apple.cpp
class Apple
{
private:
int people[100];
public:
Apple(int i);
const int apple_number;
void take(int num) const;
int add(int num);
int add(int num) const;
int getCount() const;
};
//main.cpp
#include<iostream>
#include"apple.cpp"
using namespace std;
Apple::Apple(int i):apple_number(i)
{
}
int Apple::add(int num){
take(num);
}
int Apple::add(int num) const{
take(num);
}
void Apple::take(int num) const
{
cout<<"take func "<<num<<endl;
}
int Apple::getCount() const
{
take(1);
// add(); //error
return apple_number;
}
int main(){
Apple a(2);
cout<<a.getCount()<<endl;
a.add(10);
const Apple b(3);
b.add(100);
return 0;
}
//编译: g++ -o main main.cpp apple.cpp
//结果
take func 1
2
take func 10
take func 100
```
上面getCount()方法中调用了一个add方法而add方法并非const修饰所以运行报错。也就是说const对象只能访问const成员函数。
而add方法又调用了const修饰的take方法证明了非const对象可以访问任意的成员函数,包括const成员函数。
除此之外我们也看到add的一个重载函数也输出了两个结果说明const对象默认调用const成员函数。
我们除了上述的初始化const常量用初始化列表方式外也可以通过下面方法
第一将常量定义与static结合也就是
```c++
static const int apple_number
```
第二:在外面初始化:
```c++
const int Apple::apple_number=10;
```
当然如果你使用c++11进行编译直接可以在定义出初始化可以直接写成
```c++
static const int apple_number=10;
或者
const int apple_number=10;
```
这两种都在c++11中支持
编译的时候加上`-std=c++11`即可!
这里提到了static下面简单的说一下
在C++中static静态成员变量不能在类的内部初始化。在类的内部只是声明定义必须在类定义体的外部通常在类的实现文件中初始化。
在类中声明:
```c++
static int ap;
```
在类实现文件中使用:
```c++
int Apple::ap=666
```
对于此项c++11不能进行声明并初始化也就是上述使用方法。

View File

@@ -0,0 +1,15 @@
class Apple
{
private:
int people[100];
public:
Apple(int i);
//使用c++11标准编译
static const int apple_number=10;
//const int apple_number=10;
void take(int num) const;
int add(int num);
int add(int num) const;
int getCount() const;
};

Binary file not shown.

View File

@@ -0,0 +1,31 @@
#include<iostream>
#include"apple.cpp"
using namespace std;
Apple::Apple(int i)
{
}
int Apple::add(int num){
take(num);
}
int Apple::add(int num) const{
take(num);
}
void Apple::take(int num) const
{
cout<<"take func "<<num<<endl;
}
int Apple::getCount() const
{
take(1);
// add(); //error
return apple_number;
}
int main(){
Apple a(2);
cout<<a.getCount()<<endl;
a.add(10);
const Apple b(3);
b.add(100);
return 0;
}

View File

@@ -0,0 +1,13 @@
class Apple
{
private:
int people[100];
public:
Apple(int i);
const int apple_number;
void take(int num) const;
int add(int num);
int add(int num) const;
int getCount() const;
};

View File

@@ -0,0 +1,32 @@
#include<iostream>
#include"apple.cpp"
using namespace std;
Apple::Apple(int i):apple_number(i)
{
}
int Apple::add(int num){
take(num);
}
int Apple::add(int num) const{
take(num);
}
void Apple::take(int num) const
{
cout<<"take func "<<num<<endl;
}
int Apple::getCount() const
{
take(1);
// add(); //error
return apple_number;
}
int main(){
Apple a(2);
cout<<a.getCount()<<endl;
a.add(10);
const Apple b(3);
b.add(100);
return 0;
}

View File

@@ -0,0 +1,13 @@
class Apple
{
private:
int people[100];
public:
Apple(int i);
static const int apple_number;
void take(int num) const;
int add(int num);
int add(int num) const;
int getCount() const;
};

Binary file not shown.

View File

@@ -0,0 +1,32 @@
#include<iostream>
#include"apple.cpp"
using namespace std;
const int Apple::apple_number=10;
Apple::Apple(int i)
{
}
int Apple::add(int num){
take(num);
}
int Apple::add(int num) const{
take(num);
}
void Apple::take(int num) const
{
cout<<"take func "<<num<<endl;
}
int Apple::getCount() const
{
take(1);
// add(); //error
return apple_number;
}
int main(){
Apple a(2);
cout<<a.getCount()<<endl;
a.add(10);
const Apple b(3);
b.add(100);
return 0;
}

View File

@@ -0,0 +1,14 @@
class Apple
{
private:
int people[100];
public:
Apple(int i);
static int ap; //在类实现文件中定义并初始化
static const int apple_number;
void take(int num) const;
int add(int num);
int add(int num) const;
int getCount() const;
};

Binary file not shown.

View File

@@ -0,0 +1,34 @@
#include<iostream>
#include"apple.cpp"
using namespace std;
const int Apple::apple_number=10;
int Apple::ap=666;
Apple::Apple(int i)
{
}
int Apple::add(int num){
take(num);
}
int Apple::add(int num) const{
take(num);
}
void Apple::take(int num) const
{
cout<<"take func "<<num<<endl;
}
int Apple::getCount() const
{
take(1);
// add(); //error
return apple_number;
}
int main(){
Apple a(2);
cout<<a.getCount()<<endl;
cout<<a.ap<<endl;
a.add(10);
const Apple b(3);
b.add(100);
return 0;
}

View File

@@ -0,0 +1,11 @@
#include<iostream>
using namespace std;
void f(const int i){
i=10; // error: assignment of read-only parameter i
cout<<i<<endl;
}
int main(){
f(1);
}

View File

@@ -0,0 +1,8 @@
#include<iostream>
using namespace std;
int main(){
const int b = 10;
b = 0; //error
const string s = "helloworld";
const int i,j=0;
}

View File

@@ -0,0 +1 @@
extern const int ext=12;

View File

@@ -0,0 +1,11 @@
#include<iostream>
/**
* by 光城
* compile: g++ -o file const_file2.cpp const_file1.cpp
* execute: ./file
*/
extern const int ext;
int main(){
std::cout<<ext<<std::endl;
}

View File

@@ -0,0 +1 @@
int ext;

View File

@@ -0,0 +1,11 @@
#include<iostream>
/**
* by 光城
* compile: g++ -o file file2.cpp file1.cpp
* execute: ./file
*/
extern int ext;
int main(){
std::cout<<(ext+10)<<std::endl;
}

View File

@@ -0,0 +1,8 @@
#include<iostream>
using namespace std;
int main(){
const int *ptr;
*ptr=10; //error
}

View File

@@ -0,0 +1,9 @@
#include<iostream>
using namespace std;
int main(){
const int p = 10;
const void *vp = &p;
void *vp = &p; //error
}

View File

@@ -0,0 +1,12 @@
#include<iostream>
using namespace std;
int main(){
const int *ptr;
int val = 3;
ptr = &val; //ok
int *ptr1 = &val;
*ptr1=4;
cout<<*ptr<<endl;
}

Binary file not shown.

View File

@@ -0,0 +1,10 @@
#include<iostream>
using namespace std;
int main(){
int num=0;
int * const ptr=&num; //const指针必须初始化且const指针的值不能修改
int * t = &num;
*t = 1;
cout<<*ptr<<endl;
}

View File

@@ -0,0 +1,8 @@
#include<iostream>
using namespace std;
int main(){
const int num=0;
int * const ptr=&num; //error! const int* -> int*
cout<<*ptr<<endl;
}

Binary file not shown.

View File

@@ -0,0 +1,8 @@
#include<iostream>
using namespace std;
int main(){
const int num=10;
const int * const ptr=&num; //error! const int* -> int*
cout<<*ptr<<endl;
}

Binary file not shown.

View File

@@ -0,0 +1,10 @@
#include<iostream>
using namespace std;
int main(){
const int p = 3;
const int * const ptr = &p;
cout<<*ptr<<endl;
}

View File

@@ -0,0 +1,138 @@
# decltype那些事
## 关于作者:
个人公众号:
![](../img/wechat.jpg)
## 1.基本使用
decltype的语法是:
```
decltype (expression)
```
这里的括号是必不可少的,decltype的作用是“查询表达式的类型”因此上面语句的效果是返回 expression 表达式的类型。注意decltype 仅仅“查询”表达式的类型,并不会对表达式进行“求值”。
### 1.1 推导出表达式类型
```
int i = 4;
decltype(i) a; //推导结果为int。a的类型为int。
```
### 1.2 与using/typedef合用用于定义类型。
```c++
using size_t = decltype(sizeof(0));//sizeof(a)的返回值为size_t类型
using ptrdiff_t = decltype((int*)0 - (int*)0);
using nullptr_t = decltype(nullptr);
vector<int >vec;
typedef decltype(vec.begin()) vectype;
for (vectype i = vec.begin; i != vec.end(); i++)
{
//...
}
```
这样和auto一样也提高了代码的可读性。
### 1.3 重用匿名类型
在C++中,我们有时候会遇上一些匿名类型,如:
```c++
struct
{
int d ;
doubel b;
}anon_s;
```
而借助decltype我们可以重新使用这个匿名的结构体
```c++
decltype(anon_s) as ;//定义了一个上面匿名的结构体
```
### 1.4 泛型编程中结合auto用于追踪函数的返回值类型
这也是decltype最大的用途了。
```c++
template <typename T>
auto multiply(T x, T y)->decltype(x*y)
{
return x*y;
}
```
完整代码见:[decltype.cpp](decltype.cpp)
## 2.判别规则
对于decltype(e)而言,其判别结果受以下条件的影响:
如果e是一个没有带括号的标记符表达式或者类成员访问表达式那么的decltypee就是e所命名的实体的类型。此外如果e是一个被重载的函数则会导致编译错误。
否则 假设e的类型是T如果e是一个将亡值那么decltypee为T&&
否则假设e的类型是T如果e是一个左值那么decltypee为T&。
否则假设e的类型是T则decltypee为T。
标记符指的是除去关键字、字面量等编译器需要使用的标记之外的程序员自己定义的标记,而单个标记符对应的表达式即为标记符表达式。例如:
```c++
int arr[4]
```
则arr为一个标记符表达式而arr[3]+0不是。
举例如下:
```c++
int i = 4;
int arr[5] = { 0 };
int *ptr = arr;
struct S{ double d; }s ;
void Overloaded(int);
void Overloaded(char);//重载的函数
int && RvalRef();
const bool Func(int);
//规则一:推导为其类型
decltype (arr) var1; //int 标记符表达式
decltype (ptr) var2;//int * 标记符表达式
decltype(s.d) var3;//doubel 成员访问表达式
//decltype(Overloaded) var4;//重载函数。编译错误。
//规则二:将亡值。推导为类型的右值引用。
decltype (RvalRef()) var5 = 1;
//规则三:左值,推导为类型的引用。
decltype ((i))var6 = i; //int&
decltype (true ? i : i) var7 = i; //int& 条件表达式返回左值。
decltype (++i) var8 = i; //int& ++i返回i的左值。
decltype(arr[5]) var9 = i;//int&. []操作返回左值
decltype(*ptr)var10 = i;//int& *操作返回左值
decltype("hello")var11 = "hello"; //const char(&)[9] 字符串字面常量为左值且为const左值。
//规则四:以上都不是,则推导为本类型
decltype(1) var12;//const int
decltype(Func(1)) var13=true;//const bool
decltype(i++) var14 = i;//int i++返回右值
```
学习参考https://www.cnblogs.com/QG-whz/p/4952980.html

BIN
basic_content/decltype/decltype Executable file

Binary file not shown.

View File

@@ -0,0 +1,60 @@
/**
* @file decltype.cpp
* @brief g++ -o decltype decltype.cpp -std=c++11
* @author 光城
* @version v1
* @date 2019-08-08
*/
#include <iostream>
#include <vector>
using namespace std;
/**
* 泛型编程中结合auto用于追踪函数的返回值类型
*/
template <typename T>
auto multiply(T x, T y)->decltype(x*y)
{
return x*y;
}
int main()
{
int nums[] = {1,2,3,4};
vector<int> vec(nums,nums+4);
vector<int>::iterator it;
for(it=vec.begin();it!=vec.end();it++)
cout<<*it<<" ";
cout<<endl;
using nullptr_t = decltype(nullptr);
nullptr_t nu;
int * p =NULL;
if(p==nu)
cout<<"NULL"<<endl;
typedef decltype(vec.begin()) vectype;
for(vectype i=vec.begin();i!=vec.end();i++)
cout<<*i<<" ";
cout<<endl;
/**
* 匿名结构体
*/
struct
{
int d ;
double b;
}anon_s;
decltype(anon_s) as; // 定义了一个上面匿名的结构体
cout<<multiply(11,2)<<endl;
return 0;
}

View File

@@ -0,0 +1,105 @@
# 从初级到高级的enum那些事
## 关于作者:
个人公众号:
![](../img/wechat.jpg)
## 传统行为
枚举有如下问题:
- 作用域不受限,会容易引起命名冲突。例如下面无法编译通过的:
```c++
#include <iostream>
using namespace std;
enum Color {RED,BLUE};
enum Feeling {EXCITED,BLUE};
int main()
{
return 0;
}
```
- 会隐式转换为int
- 用来表征枚举变量的实际类型不能明确指定,从而无法支持枚举类型的前向声明。
具体实现见:[tradition_color.cpp](tradition_color.cpp)
## 经典做法
解决作用域不受限带来的命名冲突问题的一个简单方法是,给枚举变量命名时加前缀,如上面例子改成 COLOR_BLUE 以及 FEELING_BLUE。
一般说来为了一致性我们会把所有常量统一加上前缀。但是这样定义枚举变量的代码就显得累赘。C 程序中可能不得不这样做。不过 C++ 程序员恐怕都不喜欢这种方法。替代方案是命名空间:
```c++
namespace Color
{
enum Type
{
RED=15,
YELLOW,
BLUE
};
};
```
这样之后就可以用 `Color::Type c = Color::RED;` 来定义新的枚举变量了。如果 `using namespace Color` 后,前缀还可以省去,使得代码简化。不过,因为命名空间是可以随后被扩充内容的,所以它提供的作用域封闭性不高。在大项目中,还是有可能不同人给不同的东西起同样的枚举类型名。
更“有效”的办法是用一个类或结构体来限定其作用域,例如:定义新变量的方法和上面命名空间的相同。不过这样就不用担心类在别处被修改内容。这里用结构体而非类,一是因为本身希望这些常量可以公开访问,二是因为它只包含数据没有成员函数。
```c++
struct Color1
{
enum Type
{
RED=102,
YELLOW,
BLUE
};
};
```
具体实现见:[classic_practice.cpp](classic_practice.cpp)
## C++11 的枚举类
上面的做法解决了第一个问题但对于后两个仍无能为力。庆幸的是C++11 标准中引入了“枚举类”(enum class),可以较好地解决上述问题。
- 新的enum的作用域不在是全局的
- 不能隐式转换成其他类型
```c++
/**
* @brief C++11的枚举类
* 下面等价于enum class Color2:int
*/
enum class Color2
{
RED=2,
YELLOW,
BLUE
};
r2 c2 = Color2::RED;
cout << static_cast<int>(c2) << endl; //必须转!
```
- 可以指定用特定的类型来存储enum
```c++
enum class Color3:char; // 前向声明
// 定义
enum class Color3:char
{
RED='r',
BLUE
};
char c3 = static_cast<char>(Color3::RED);
```
具体实现见:[classic_practice.cpp](classic_practice.cpp)

Binary file not shown.

View File

@@ -0,0 +1,87 @@
/**
* @file classic_practice.cpp
* @brief g++ -o classic_practice classic_practice.cpp -std=c++11
* @author 光城
* @version v1
* @date 2019-08-07
*/
#include <iostream>
using namespace std;
/**
* @brief namespace解决作用域不受限
*/
namespace Color
{
enum Type
{
RED=15,
YELLOW,
BLUE
};
};
/**
* @brief 上述如果 using namespace Color 后,前缀还可以省去,使得代码简化。
* 不过,因为命名空间是可以随后被扩充内容的,所以它提供的作用域封闭性不高。
* 在大项目中,还是有可能不同人给不同的东西起同样的枚举类型名。
* 更“有效”的办法是用一个类或结构体来限定其作用域。
*
* 定义新变量的方法和上面命名空间的相同。
* 不过这样就不用担心类在别处被修改内容。
* 这里用结构体而非类,一是因为本身希望这些常量可以公开访问,
* 二是因为它只包含数据没有成员函数。
*/
struct Color1
{
enum Type
{
RED=102,
YELLOW,
BLUE
};
};
/**
* @brief C++11的枚举类
* 下面等价于enum class Color2:int
*/
enum class Color2
{
RED=2,
YELLOW,
BLUE
};
enum class Color3:char; // 前向声明
// 定义
enum class Color3:char
{
RED='r',
BLUE
};
int main()
{
// 定义新的枚举变量
Color::Type c = Color::RED;
cout<<c<<endl;
/**
* 上述的另一种方法:
* using namespace Color; // 定义新的枚举变量
* Type c = RED;
*/
Color1 c1;
cout<<c1.RED<<endl;
Color1::Type c11 = Color1::BLUE;
cout<<c11<<endl;
Color2 c2 = Color2::RED;
cout << static_cast<int>(c2) << endl;
char c3 = static_cast<char>(Color3::RED);
cout<<c3<<endl;
return 0;
}

View File

@@ -0,0 +1,10 @@
#include <iostream>
using namespace std;
enum Color {RED,BLUE};
enum Feeling {EXCITED,BLUE};
int main()
{
return 0;
}

View File

@@ -0,0 +1,16 @@
# explicit(显式)关键字那些事
## 关于作者:
个人公众号:
![](../img/wechat.jpg)
- explicit 修饰构造函数时,可以防止隐式转换和复制初始化
- explicit 修饰转换函数时,可以防止隐式转换,但按语境转换除外
代码参见:[.explicit.cpp](./explicit.cpp)
参考链接:
> https://stackoverflow.com/questions/4600295/what-is-the-meaning-of-operator-bool-const

BIN
basic_content/explicit/explicit Executable file

Binary file not shown.

View File

@@ -0,0 +1,46 @@
#include <iostream>
using namespace std;
struct A
{
A(int) { }
operator bool() const { return true; }
};
struct B
{
explicit B(int) {}
explicit operator bool() const { return true; }
};
void doA(A a) {}
void doB(B b) {}
int main()
{
A a1(1); // OK直接初始化
A a2 = 1; // OK复制初始化
A a3{ 1 }; // OK直接列表初始化
A a4 = { 1 }; // OK复制列表初始化
A a5 = (A)1; // OK允许 static_cast 的显式转换
doA(1); // OK允许从 int 到 A 的隐式转换
if (a1); // OK使用转换函数 A::operator bool() 的从 A 到 bool 的隐式转换
bool a6(a1); // OK使用转换函数 A::operator bool() 的从 A 到 bool 的隐式转换
bool a7 = a1; // OK使用转换函数 A::operator bool() 的从 A 到 bool 的隐式转换
bool a8 = static_cast<bool>(a1); // OK static_cast 进行直接初始化
B b1(1); // OK直接初始化
// B b2 = 1; // 错误:被 explicit 修饰构造函数的对象不可以复制初始化
B b3{ 1 }; // OK直接列表初始化
// B b4 = { 1 }; // 错误:被 explicit 修饰构造函数的对象不可以复制列表初始化
B b5 = (B)1; // OK允许 static_cast 的显式转换
// doB(1); // 错误:被 explicit 修饰构造函数的对象不可以从 int 到 B 的隐式转换
if (b1); // OK被 explicit 修饰转换函数 B::operator bool() 的对象可以从 B 到 bool 的按语境转换
bool b6(b1); // OK被 explicit 修饰转换函数 B::operator bool() 的对象可以从 B 到 bool 的按语境转换
// bool b7 = b1; // 错误:被 explicit 修饰转换函数 B::operator bool() 的对象不可以隐式转换
bool b8 = static_cast<bool>(b1); // OKstatic_cast 进行直接初始化
return 0;
}

193
basic_content/extern/README.md vendored Normal file
View File

@@ -0,0 +1,193 @@
# extern "C"
## 关于作者:
个人公众号:
![](../img/wechat.jpg)
## 1.C++与C编译区别
在C++中常在头文件见到extern "C"修饰函数,那有什么作用呢? 是用于C++链接在C语言模块中定义的函数。
C++虽然兼容C但C++文件中函数编译后生成的符号与C语言生成的不同。因为C++支持函数重载C++函数编译后生成的符时带有函数参数类型的信息而C则没有。
例如`int add(int a, int b)`函数经过C++编译器生成.o文件后`add`会变成形如`add_int_int`之类的, 而C的话则会是形如`_add`, 就是说相同的函数在C和C++中,编译后生成的符号不同。
这就导致一个问题如果C++中使用C语言实现的函数在编译链接的时候会出错提示找不到对应的符号。此时`extern "C"`就起作用了:告诉链接器去寻找`_add`这类的C语言符号而不是经过C++修饰的符号。
## 2.C++调用C函数
C++调用C函数的例子: 引用C的头文件时需要加`extern "C"`
```c++
//add.h
#ifndef ADD_H
#define ADD_H
int add(int x,int y);
#endif
//add.c
#include "add.h"
int add(int x,int y) {
return x+y;
}
//add.cpp
#include <iostream>
#include "add.h"
using namespace std;
int main() {
add(2,3);
return 0;
}
```
编译:
```
//Generate add.o file
gcc -c add.c
```
链接:
```
g++ add.cpp add.o -o main
```
没有添加extern "C" 报错:
```c++
> g++ add.cpp add.o -o main
add.o在函数main
add.cpp:(.text+0x0): `main'被多次定义
/tmp/ccH65yQF.o:add.cpp:(.text+0x0):第一次在此定义
/tmp/ccH65yQF.o在函数main
add.cpp:(.text+0xf)add(int, int)’未定义的引用
add.o在函数main
add.cpp:(.text+0xf)add(int, int)’未定义的引用
collect2: error: ld returned 1 exit status
```
添加extern "C"后:
`add.cpp`
```c++
#include <iostream>
using namespace std;
extern "C" {
#include "add.h"
}
int main() {
add(2,3);
return 0;
}
```
编译的时候一定要注意先通过gcc生成中间文件add.o。
```
gcc -c add.c
```
然后编译:
```
g++ add.cpp add.o -o main
```
上述案例源代码见:
- [add.h](extern_c++/add.h)
- [add.c](extern_c++/add.c)
- [add.cpp](extern_c++/add.cpp)
## 2.C中调用C++函数
`extern "C"`在C中是语法错误需要放在C++头文件中。
```c
// add.h
#ifndef ADD_H
#define ADD_H
extern "C" {
int add(int x,int y);
}
#endif
// add.cpp
#include "add.h"
int add(int x,int y) {
return x+y;
}
// add.c
extern int add(int x,int y);
int main() {
add(2,3);
return 0;
}
```
编译:
```c
g++ -c add.cpp
```
链接:
```
gcc add.c add.o -o main
```
上述案例源代码见:
- [add.h](extern_c/add.h)
- [add.c](extern_c/add.c)
- [add.cpp](extern_c/add.cpp)
综上总结出使用方法在C语言的头文件中对其外部函数只能指定为extern类型C语言中不支持extern "C"声明,在.c文件中包含了extern "C"时会出现编译语法错误。所以使用extern "C"全部都放在于cpp程序相关文件或其头文件中。
总结出如下形式:
1C++调用C函数
```c++
//xx.h
extern int add(...)
//xx.c
int add(){
}
//xx.cpp
extern "C" {
#include "xx.h"
}
```
2C调用C++函数
```c
//xx.h
extern "C"{
int add();
}
//xx.cpp
int add(){
}
//xx.c
extern int add();
```

5
basic_content/extern/extern_c++/add.c vendored Normal file
View File

@@ -0,0 +1,5 @@
#include "add.h"
int add(int x,int y) {
return x+y;
}

View File

@@ -0,0 +1,9 @@
#include <iostream>
using namespace std;
extern "C" {
#include "add.h"
}
int main() {
add(2,3);
return 0;
}

4
basic_content/extern/extern_c++/add.h vendored Normal file
View File

@@ -0,0 +1,4 @@
#ifndef ADD_H
#define ADD_H
extern int add(int x,int y);
#endif

BIN
basic_content/extern/extern_c++/add.o vendored Normal file

Binary file not shown.

BIN
basic_content/extern/extern_c++/main vendored Executable file

Binary file not shown.

5
basic_content/extern/extern_c/add.c vendored Normal file
View File

@@ -0,0 +1,5 @@
extern int add(int x,int y);
int main() {
add(2,3);
return 0;
}

5
basic_content/extern/extern_c/add.cpp vendored Normal file
View File

@@ -0,0 +1,5 @@
#include "add.h"
int add(int x,int y) {
return x+y;
}

6
basic_content/extern/extern_c/add.h vendored Normal file
View File

@@ -0,0 +1,6 @@
#ifndef ADD_H
#define ADD_H
extern "C" {
int add(int x,int y);
}
#endif

BIN
basic_content/extern/extern_c/add.o vendored Normal file

Binary file not shown.

BIN
basic_content/extern/extern_c/main vendored Executable file

Binary file not shown.

View File

@@ -0,0 +1,115 @@
# 友元函数与友元类
## 关于作者:
个人公众号:
![](../img/wechat.jpg)
## 0.概述
友元提供了一种 普通函数或者类成员函数 访问另一个类中的私有或保护成员 的机制。也就是说有两种形式的友元:
1友元函数普通函数对一个访问某个类中的私有或保护成员。
2友元类类A中的成员函数访问类B中的私有或保护成员
优点:提高了程序的运行效率。
缺点:破坏了类的封装性和数据的透明性。
总结:
- 能访问私有成员
- 破坏封装性
- 友元关系不可传递
- 友元关系的单向性
- 友元声明的形式及数量不受限制
## 1.友元函数
在类声明的任何区域中声明,而定义则在类的外部。
```
friend <类型><友元函数名>(<参数表>);
```
注意,友元函数只是一个普通函数,并不是该类的类成员函数,它可以在任何地方调用,友元函数中通过对象名来访问该类的私有或保护成员。
具体代码见:[friend_func.cpp](friend_func.cpp)
```c++
#include <iostream>
using namespace std;
class A
{
public:
A(int _a):a(_a){};
friend int geta(A &ca); ///< 友元函数
private:
int a;
};
int geta(A &ca)
{
return ca.a;
}
int main()
{
A a(3);
cout<<geta(a)<<endl;
return 0;
}
```
## 2.友元类
友元类的声明在该类的声明中,而实现在该类外。
```
friend class <友元类名>;
```
类B是类A的友元那么类B可以直接访问A的私有成员。
具体代码见:[friend_class.cpp](friend_class.cpp)
```c++
#include <iostream>
using namespace std;
class A
{
public:
A(int _a):a(_a){};
friend class B;
private:
int a;
};
class B
{
public:
int getb(A ca) {
return ca.a;
};
};
int main()
{
A a(3);
B b;
cout<<b.getb(a)<<endl;
return 0;
}
```
## 3.注意
- 友元关系没有继承性
假如类B是类A的友元类C继承于类A那么友元类B是没办法直接访问类C的私有或保护成员。
- 友元关系没有传递性
假如类B是类A的友元类C是类B的友元那么友元类C是没办法直接访问类A的私有或保护成员也就是不存在“友元的友元”这种关系。

BIN
basic_content/friend/friend_class Executable file

Binary file not shown.

View File

@@ -0,0 +1,28 @@
#include <iostream>
using namespace std;
class A
{
public:
A(int _a):a(_a){};
friend class B;
private:
int a;
};
class B
{
public:
int getb(A ca) {
return ca.a;
};
};
int main()
{
A a(3);
B b;
cout<<b.getb(a)<<endl;
return 0;
}

BIN
basic_content/friend/friend_func Executable file

Binary file not shown.

View File

@@ -0,0 +1,33 @@
/**
* @file friend_func.cpp
* @brief 友元函数
* @author 光城
* @version v1
* @date 2019-08-06
*/
#include <iostream>
using namespace std;
class A
{
public:
A(int _a):a(_a){};
friend int geta(A &ca); ///< 友元函数
private:
int a;
};
int geta(A &ca)
{
return ca.a;
}
int main()
{
A a(3);
cout<<geta(a)<<endl;
return 0;
}

BIN
basic_content/func_pointer/func1 Executable file

Binary file not shown.

View File

@@ -0,0 +1,33 @@
/**
* @file func_pointer.cpp
* @brief 函数指针的使用!
* @author 光城
* @version v1
* @date 2019-07-20
*/
#include<iostream>
using namespace std;
/**
* @brief 定义了一个变量pFun这个变量是个指针指向返回值和参数都是空的函数的指针
*/
void (*pFun)(int);
/**
* @brief 代表一种新类型不是变量所以与上述的pFun不一样
*/
typedef void (*func)(void);
void myfunc(void)
{
cout<<"asda"<<endl;
}
void glFun(int a){ cout<<a<<endl;}
int main(){
func pfun = myfunc;/*赋值*/
pfun();/*调用*/
pFun = glFun;
(*pFun)(2);
}

BIN
basic_content/img/wechat.jpg Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

View File

@@ -0,0 +1,137 @@
# inline那些事
## 关于作者:
个人公众号:
![](../img/wechat.jpg)
## 1.类中内联
头文件中声明方法
```c++
class A
{
public:
void f1(int x);
/**
* @brief 类中定义了的函数是隐式内联函数,声明要想成为内联函数,必须在实现处(定义处)加inline关键字。
*
* @param x
* @param y
*/
void Foo(int x,int y) ///< 定义即隐式内联函数!
{
};
void f1(int x); ///< 声明后要想成为内联函数必须在定义处加inline关键字。
};
```
实现文件中定义内联函数:
```c++
#include <iostream>
#include "inline.h"
using namespace std;
/**
* @brief inline要起作用,inline要与函数定义放在一起,inline是一种“用于实现的关键字,而不是用于声明的关键字”
*
* @param x
* @param y
*
* @return
*/
int Foo(int x,int y); // 函数声明
inline int Foo(int x,int y) // 函数定义
{
return x+y;
}
// 定义处加inline关键字推荐这种写法
inline void A::f1(int x){
}
int main()
{
cout<<Foo(1,2)<<endl;
}
/**
* 编译器对 inline 函数的处理步骤
* 将 inline 函数体复制到 inline 函数调用点处;
* 为所用 inline 函数中的局部变量分配内存空间;
* 将 inline 函数的的输入参数和返回值映射到调用方法的局部变量空间中;
* 如果 inline 函数有多个返回点,将其转变为 inline 函数代码块末尾的分支(使用 GOTO
*/
```
内联能提高函数效率,但并不是所有的函数都定义成内联函数!内联是以代码膨胀(复制)为代价,仅仅省去了函数调用的开销,从而提高函数的执行效率。
- 如果执行函数体内代码的时间相比于函数调用的开销较大,那么效率的收货会更少!
- 另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。
以下情况不宜用内联:
1如果函数体内的代码比较长使得内联将导致内存消耗代价比较高。
2如果函数体内出现循环那么执行函数体内代码的时间要比函数调用的开销大。
## 2.虚函数virtual可以是内联函数inline
- 虚函数可以是内联函数,内联是可以修饰虚函数的,但是当虚函数表现多态性的时候不能内联。
- 内联是在编译器建议编译器内联,而虚函数的多态性在运行期,编译器无法知道运行期调用哪个代码,因此虚函数表现为多态性时(运行期)不可以内联。
- `inline virtual` 唯一可以内联的时候是:编译器知道所调用的对象是哪个类(如 `Base::who()`),这只有在编译器具有实际对象而不是对象的指针或引用时才会发生。
```c++
#include <iostream>
using namespace std;
class Base
{
public:
inline virtual void who()
{
cout << "I am Base\n";
}
virtual ~Base() {}
};
class Derived : public Base
{
public:
inline void who() // 不写inline时隐式内联
{
cout << "I am Derived\n";
}
};
int main()
{
// 此处的虚函数 who()是通过类Base的具体对象b来调用的编译期间就能确定了所以它可以是内联的但最终是否内联取决于编译器。
Base b;
b.who();
// 此处的虚函数是通过指针调用的,呈现多态性,需要在运行时期间才能确定,所以不能为内联。
Base *ptr = new Derived();
ptr->who();
// 因为Base有虚析构函数virtual ~Base() {}),所以 delete 时会先调用派生类Derived析构函数再调用基类Base析构函数防止内存泄漏。
delete ptr;
ptr = nullptr;
system("pause");
return 0;
}
```

BIN
basic_content/inline/inline Executable file

Binary file not shown.

View File

@@ -0,0 +1,57 @@
#include <iostream>
#include "inline.h"
using namespace std;
/**
* @brief inline要起作用,inline要与函数定义放在一起,inline是一种“用于实现的关键字,而不是用于声明的关键字”
*
* @param x
* @param y
*
* @return
*/
int Foo(int x,int y); // 函数声明
inline int Foo(int x,int y) // 函数定义
{
return x+y;
}
// 定义处加inline关键字推荐这种写法
inline void A::f1(int x){
}
/**
* @brief 内联能提高函数效率,但并不是所有的函数都定义成内联函数!内联是以代码膨胀(复制)为代价,仅仅省去了函数调用的开销,从而提高函数的执行效率。
* 如果执行函数体内代码的时间相比于函数调用的开销较大,那么效率的收货会更少!另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。
* 以下情况不宜用内联:
* 1 如果函数体内的代码比较长,使得内联将导致内存消耗代价比较高。
* (2) 如果函数体内出现循环,那么执行函数体内代码的时间要比函数调用的开销大。
*
* @return
*/
int main()
{
cout<<Foo(1,2)<<endl;
}
/**
* 编译器对 inline 函数的处理步骤
* 将 inline 函数体复制到 inline 函数调用点处;
* 为所用 inline 函数中的局部变量分配内存空间;
* 将 inline 函数的的输入参数和返回值映射到调用方法的局部变量空间中;
* 如果 inline 函数有多个返回点,将其转变为 inline 函数代码块末尾的分支(使用 GOTO
*/

View File

@@ -0,0 +1,19 @@
class A
{
public:
void f1(int x);
/**
* @brief 类中定义了的函数是隐式内联函数,声明要想成为内联函数,必须在实现处(定义处)加inline关键字。
*
* @param x
* @param y
*/
void Foo(int x,int y) ///< 定义即隐式内联函数!
{
};
void f1(int x); ///< 声明后要想成为内联函数必须在定义处加inline关键字。
};

View File

@@ -0,0 +1,35 @@
#include <iostream>
using namespace std;
class Base
{
public:
inline virtual void who()
{
cout << "I am Base\n";
}
virtual ~Base() {}
};
class Derived : public Base
{
public:
inline void who() // 不写inline时隐式内联
{
cout << "I am Derived\n";
}
};
int main()
{
// 此处的虚函数 who()是通过类Base的具体对象b来调用的编译期间就能确定了所以它可以是内联的但最终是否内联取决于编译器。
Base b;
b.who();
// 此处的虚函数是通过指针调用的,呈现多态性,需要在运行时期间才能确定,所以不能为内联。
Base *ptr = new Derived();
ptr->who();
// 因为Base有虚析构函数virtual ~Base() {}),所以 delete 时会先调用派生类Derived析构函数再调用基类Base析构函数防止内存泄漏。
delete ptr;
return 0;
}

BIN
basic_content/inline/iv Executable file

Binary file not shown.

View File

@@ -0,0 +1,232 @@
# 宏那些事
## 关于作者:
个人公众号:
![](../img/wechat.jpg)
## 1.宏中包含特殊符号
分为几种:`#``##``\`
### 1.1 字符串化操作符(#
**在一个宏中的参数前面使用一个#,预处理器会把这个参数转换为一个字符数组**,换言之就是:**#是“字符串化”的意思,出现在宏定义中的#是把跟在后面的参数转换成一个字符串**。
**注意:其只能用于有传入参数的宏定义中,且必须置于宏定义体中的参数名前。**
例如:
```c++
#define exp(s) printf("test s is:%s\n",s)
#define exp1(s) printf("test s is:%s\n",#s)
#define exp2(s) #s
int main() {
exp("hello");
exp1(hello);
string str = exp2( bac );
cout<<str<<" "<<str.size()<<endl;
/**
* 忽略传入参数名前面和后面的空格。
*/
string str1 = exp2( asda bac );
/**
* 当传入参数名间存在空格时,编译器将会自动连接各个子字符串,
* 用每个子字符串之间以一个空格连接,忽略剩余空格。
*/
cout<<str1<<" "<<str1.size()<<endl;
return 0;
}
```
上述代码给出了基本的使用与空格处理规则,空格处理规则如下:
- 忽略传入参数名前面和后面的空格。
```c++
string str = exp2( bac );
cout<<str<<" "<<str.size()<<endl;
```
输出:
```
bac 3
```
- 当传入参数名间存在空格时,编译器将会自动连接各个子字符串,用每个子字符串之间以一个空格连接,忽略剩余空格。
```c++
string str1 = exp2( asda bac );
cout<<str1<<" "<<str1.size()<<endl;
```
输出:
```
asda bac 8
```
### 1.2 符号连接操作符(##
**“##”是一种分隔连接方式,它的作用是先分隔,然后进行强制连接。将宏定义的多个形参转换成一个实际参数名。**
注意事项:
**1当用##连接形参时,##前后的空格可有可无。**
**2连接后的实际参数名必须为实际存在的参数名或是编译器已知的宏定义。**
**3如果##后的参数本身也是一个宏的话,##会阻止这个宏的展开。**
示例:
```c++
#define expA(s) printf("前缀加上后的字符串为:%s\n",gc_##s) //gc_s必须存在
// 注意事项2
#define expB(s) printf("前缀加上后的字符串为:%s\n",gc_ ## s) //gc_s必须存在
// 注意事项1
#define gc_hello1 "I am gc_hello1"
int main() {
// 注意事项1
const char * gc_hello = "I am gc_hello";
expA(hello);
expB(hello1);
}
```
### 1.3 续行操作符(\
**当定义的宏不能用一行表达完整时,可以用”\”表示下一行继续此宏的定义。**
**注意 \ 前留空格。**
```c++
#define MAX(a,b) ((a)>(b) ? (a) \
:(b))
int main() {
int max_val = MAX(3,6);
cout<<max_val<<endl;
}
```
上述代码见:[sig_examp.cpp](sig_examp.cpp)
## 2.do{...}while(0)的使用
### 2.1 避免语义曲解
例如:
```
#define fun() f1();f2();
if(a>0)
fun()
```
这个宏被展开后就是:
```
if(a>0)
f1();
f2();
```
本意是a>0执行f1 f2而实际是f2每次都会执行所以就错误了。
为了解决这种问题,在写代码的时候,通常可以采用`{}`块。
如:
```c++
#define fun() {f1();f2();}
if(a>0)
fun();
// 宏展开
if(a>0)
{
f1();
f2();
};
```
但是会发现上述宏展开后多了一个分号,实际语法不太对。(虽然编译运行没问题,正常没分号)。
### 2.2避免使用goto控制流
在一些函数中我们可能需要在return语句之前做一些清理工作比如释放在函数开始处由malloc申请的内存空间使用goto总是一种简单的方法
```c++
int f() {
int *p = (int *)malloc(sizeof(int));
*p = 10;
cout<<*p<<endl;
#ifndef DEBUG
int error=1;
#endif
if(error)
goto END;
// dosomething
END:
cout<<"free"<<endl;
free(p);
return 0;
}
```
但由于goto不符合软件工程的结构化而且有可能使得代码难懂所以很多人都不倡导使用这个时候我们可以使用do{...}while(0)来做同样的事情:
```c++
int ff() {
int *p = (int *)malloc(sizeof(int));
*p = 10;
cout<<*p<<endl;
do{
#ifndef DEBUG
int error=1;
#endif
if(error)
break;
//dosomething
}while(0);
cout<<"free"<<endl;
free(p);
return 0;
}
```
这里将函数主体部分使用do{...}while(0)包含起来使用break来代替goto后续的清理工作在while之后现在既能达到同样的效果而且代码的可读性、可维护性都要比上面的goto代码好的多了。
### 2.3 避免由宏引起的警告
内核中由于不同架构的限制很多时候会用到空宏。在编译的时候这些空宏会给出warning为了避免这样的warning我们可以使用do{...}while(0)来定义空宏:
```
#define EMPTYMICRO do{}while(0)
```
### 2.4 **定义单一的函数块来完成复杂的操作**
如果你有一个复杂的函数变量很多而且你不想要增加新的函数可以使用do{...}while(0),将你的代码写在里面,里面可以定义变量而不用考虑变量名会同函数之前或者之后的重复。
这种情况应该是指一个变量多处使用但每处的意义还不同我们可以在每个do-while中缩小作用域比如
```c++
int fc()
{
int k1 = 10;
cout<<k1<<endl;
do{
int k1 = 100;
cout<<k1<<endl;
}while(0);
cout<<k1<<endl;
}
```
上述代码见:[do_while.cpp](do_while.cpp)
学习文章:<https://www.cnblogs.com/lizhenghn/p/3674430.html>

BIN
basic_content/macro/do_while Executable file

Binary file not shown.

View File

@@ -0,0 +1,76 @@
#include <iostream>
#include <malloc.h>
using namespace std;
#define f1() cout<<"f1()"<<endl;
#define f2() cout<<"f2()"<<endl;
#define fun() {f1();f2();}
#define fun1() \
do{ \
f1();\
f2();\
}while(0)
int f() {
int *p = (int *)malloc(sizeof(int));
*p = 10;
cout<<*p<<endl;
#ifndef DEBUG
int error=1;
#endif
if(error)
goto END;
// dosomething
END:
cout<<"free"<<endl;
free(p);
return 0;
}
int ff() {
int *p = (int *)malloc(sizeof(int));
*p = 10;
cout<<*p<<endl;
do{
#ifndef DEBUG
int error=1;
#endif
if(error)
break;
//dosomething
}while(0);
cout<<"free"<<endl;
free(p);
return 0;
}
int fc()
{
int k1 = 10;
cout<<k1<<endl;
do{
int k1 = 100;
cout<<k1<<endl;
}while(0);
cout<<k1<<endl;
}
int main() {
if(1>0)
fun();
if(2>0)
fun1();
f();
ff();
fc();
return 0;
}

BIN
basic_content/macro/sig_examp Executable file

Binary file not shown.

View File

@@ -0,0 +1,86 @@
#include <iostream>
#include <string>
#include <cstring>
#include <stdio.h>
using namespace std;
///===========================================
/**
* (#)字符串操作符
*/
///===========================================
#define exp(s) printf("test s is:%s\n",s)
#define exp1(s) printf("test s is:%s\n",#s)
#define exp2(s) #s
///===========================================
/**
*##)符号连接操作符
*/
///===========================================
#define expA(s) printf("前缀加上后的字符串为:%s\n",gc_##s) //gc_s必须存在
#define expB(s) printf("前缀加上后的字符串为:%s\n",gc_ ## s) //gc_s必须存在
#define gc_hello1 "I am gc_hello1"
///===========================================
/**
* (\)续行操作符
*/
///===========================================
#define MAX(a,b) ((a)>(b) ? (a) \
:(b))
int main() {
///===========================================
/**
* (#)字符串操作符
*/
///===========================================
exp("hello");
exp1(hello);
string str = exp2( bac );
cout<<str<<" "<<str.size()<<endl;
/**
* 忽略传入参数名前面和后面的空格。
*/
string str1 = exp2( asda bac );
/**
* 当传入参数名间存在空格时,编译器将会自动连接各个子字符串,
* 用每个子字符串之间以一个空格连接,忽略剩余空格。
*/
cout<<str1<<" "<<str1.size()<<endl;
///===========================================
/**
* (#)字符串操作符
*/
///===========================================
const char * gc_hello = "I am gc_hello";
expA(hello);
expB(hello1);
char var1_p[20];
char var2_p[20];
// 连接后的实际参数名赋值
strcpy(var1_p, "aaaa");
strcpy(var2_p, "bbbb");
///===========================================
/**
* (\)续行操作符
*/
///===========================================
int max_val = MAX(3,6);
cout<<max_val<<endl;
return 0;
}

View File

@@ -0,0 +1,174 @@
# 引用与指针那些事
## 关于作者:
个人公众号:
![](../img/wechat.jpg)
## 1.引用与指针
总论:
| 引用 | 指针 |
| ------------ | ------------ |
| 必须初始化 | 可以不初始化 |
| 不能为空 | 可以为空 |
| 不能更换目标 | 可以更换目标 |
> 引用必须初始化,而指针可以不初始化。
我们在定义一个引用的时候必须为其指定一个初始值,但是指针却不需要。
```c++
int &r; //不合法,没有初始化引用
int *p; //合法但p为野指针使用需要小心
```
> 引用不能为空,而指针可以为空。
由于引用不能为空,所以我们在使用引用的时候不需要测试其合法性,而在使用指针的时候需要首先判断指针是否为空指针,否则可能会引起程序崩溃。
```c++
void test_p(int* p)
{
if(p != null_ptr) //对p所指对象赋值时需先判断p是否为空指针
*p = 3;
return;
}
void test_r(int& r)
{
r = 3; //由于引用不能为空所以此处无需判断r的有效性就可以对r直接赋值
return;
}
```
> 引用不能更换目标
指针可以随时改变指向,但是引用只能指向初始化时指向的对象,无法改变。
```
int a = 1;
int b = 2;
int &r = a; //初始化引用r指向变量a
int *p = &a; //初始化指针p指向变量a
p = &b; //指针p指向了变量b
r = b; //引用r依然指向a但a的值变成了b
```
## 2.引用
#### 左值引用
常规引用,一般表示对象的身份。
#### 右值引用
右值引用就是必须绑定到右值(一个临时对象、将要销毁的对象)的引用,一般表示对象的值。
右值引用可实现转移语义Move Sementics和精确传递Perfect Forwarding它的主要目的有两个方面
- 消除两个对象交互时不必要的对象拷贝,节省运算存储资源,提高效率。
- 能够更简洁明确地定义泛型函数。
#### 引用折叠
- `X& &`、`X& &&`、`X&& &` 可折叠成 `X&`
- `X&& &&` 可折叠成 `X&&`
C++的引用**在减少了程序员自由度的同时提升了内存操作的安全性和语义的优美性**。比如引用强制要求必须初始化,可以让我们在使用引用的时候不用再去判断引用是否为空,让代码更加简洁优美,避免了指针满天飞的情形。除了这种场景之外引用还用于如下两个场景:
> 引用型参数
一般我们使用const reference参数作为只读形参这种情况下既可以避免参数拷贝还可以获得与传值参数一样的调用方式。
```c++
void test(const vector<int> &data)
{
//...
}
int main()
{
vector<int> data{1,2,3,4,5,6,7,8};
test(data);
}
```
> 引用型返回值
C++提供了重载运算符的功能我们在重载某些操作符的时候使用引用型返回值可以获得跟该操作符原来语法相同的调用方式保持了操作符语义的一致性。一个例子就是operator []操作符,这个操作符一般需要返回一个引用对象,才能正确的被修改。
```c++
vector<int> v(10);
v[5] = 10; //[]操作符返回引用然后vector对应元素才能被修改
//如果[]操作符不返回引用而是指针的话,赋值语句则需要这样写
*v[5] = 10; //这种书写方式,完全不符合我们对[]调用的认知,容易产生误解
```
## 3.指针与引用的性能差距
指针与引用之间有没有性能差距呢这种问题就需要进入汇编层面去看一下。我们先写一个test1函数参数传递使用指针
```c++
void test1(int* p)
{
*p = 3; //此处应该首先判断p是否为空为了测试的需要此处我们没加。
return;
}
```
该代码段对应的汇编代码如下:
```c++
(gdb) disassemble
Dump of assembler code for function test1(int*):
0x0000000000400886 <+0>: push %rbp
0x0000000000400887 <+1>: mov %rsp,%rbp
0x000000000040088a <+4>: mov %rdi,-0x8(%rbp)
=> 0x000000000040088e <+8>: mov -0x8(%rbp),%rax
0x0000000000400892 <+12>: movl $0x3,(%rax)
0x0000000000400898 <+18>: nop
0x0000000000400899 <+19>: pop %rbp
0x000000000040089a <+20>: retq
End of assembler dump.
```
上述代码1、2行是参数调用保存现场操作第3行是参数传递函数调用第一个参数一般放在rdi寄存器此行代码把rdi寄存器值指针p的值写入栈中第4行是把栈中p的值写入rax寄存器第5行是把立即数3写入到**rax寄存器值所指向的内存**中,此处要注意(%rax)两边的括号,这个括号并并不是可有可无的,(%rax)和%rax完全是两种意义(%rax)代表rax寄存器中值所代表地址部分的内存即相当于C++代码中的*p而%rax代表rax寄存器相当于C++代码中的p值所以汇编这里使用了(%rax)而不是%rax。
我们再写出参数传递使用引用的C++代码段test2
```c++
void test2(int& r)
{
r = 3; //赋值前无需判断reference是否为空
return;
}
```
这段代码对应的汇编代码如下:
```c++
(gdb) disassemble
Dump of assembler code for function test2(int&):
0x000000000040089b <+0>: push %rbp
0x000000000040089c <+1>: mov %rsp,%rbp
0x000000000040089f <+4>: mov %rdi,-0x8(%rbp)
=> 0x00000000004008a3 <+8>: mov -0x8(%rbp),%rax
0x00000000004008a7 <+12>: movl $0x3,(%rax)
0x00000000004008ad <+18>: nop
0x00000000004008ae <+19>: pop %rbp
0x00000000004008af <+20>: retq
End of assembler dump.
```
我们发现test2对应的汇编代码和test1对应的汇编代码完全相同这说明C++编译器在编译程序的时候将指针和引用编译成了完全一样的机器码。所以C++中的引用只是C++对指针操作的一个“语法糖”在底层实现时C++编译器实现这两种操作的方法完全相同。
## 3.总结
C++中引入了引用操作,在对引用的使用加了更多限制条件的情况下,保证了引用使用的安全性和便捷性,还可以保持代码的优雅性。在适合的情况使用适合的操作,引用的使用可以一定程度避免“指针满天飞”的情况,对于提升程序鲁棒性也有一定的积极意义。最后,指针与引用底层实现都是一样的,不用担心两者的性能差距。
上述部分参考自:<http://irootlee.com/juicer_pointer_reference/#>

Binary file not shown.

View File

@@ -0,0 +1,35 @@
/**
* @file copy_construct.cpp
* @brief g++ -o copy_construct copy_construct.cpp -fno-elide-constructors
* -fno-elide-constructors选项(关闭返回值优化)
* @author 光城
* @version v1
* @date 2019-08-09
*/
#include <iostream>
using namespace std;
class Copyable {
public:
Copyable(){}
Copyable(const Copyable &o) {
cout << "Copied" << endl;
}
};
Copyable ReturnRvalue() {
return Copyable(); //返回一个临时对象
}
void AcceptVal(Copyable a) {
}
void AcceptRef(const Copyable& a) {
}
int main() {
cout << "pass by value: " << endl;
AcceptVal(ReturnRvalue()); // 应该调用两次拷贝构造函数
cout << "pass by reference: " << endl;
AcceptRef(ReturnRvalue()); //应该只调用一次拷贝构造函数
}

BIN
basic_content/pointer_refer/effec Executable file

Binary file not shown.

View File

@@ -0,0 +1,22 @@
#include<iostream>
using namespace std;
void test1(int* p)
{
*p = 3; //此处应该首先判断p是否为空为了测试的需要此处我们没加。
return;
}
void test2(int& p)
{
p = 3; //此处应该首先判断p是否为空为了测试的需要此处我们没加。
return;
}
int main() {
int a=10;
int *p=&a;
test1(p);
test2(a);
cout<<a<<endl;
return 0;
}

View File

@@ -0,0 +1,191 @@
# 类大小计算
首先来个总结,然后下面给出实际例子,实战!
- 空类的大小为1字节
- 一个类中,虚函数本身、成员函数(包括静态与非静态)和静态数据成员都是不占用类对象的存储空间。
- 对于包含虚函数的类,不管有多少个虚函数,只有一个虚指针,vptr的大小。
- 普通继承,派生类继承了所有基类的函数与成员,要按照字节对齐来计算大小
- 虚函数继承不管是单继承还是多继承都是继承了基类的vptr。(32位操作系统4字节64位操作系统 8字节)
- 虚继承,继承基类的vptr。
## 1.原则1
```c++
/**
* @file blackclass.cpp
* @brief 空类的大小为1字节
* @author 光城
* @version v1
* @date 2019-07-21
*/
#include<iostream>
using namespace std;
class A{};
int main()
{
cout<<sizeof(A)<<endl;
return 0;
}
```
## 2.原则2
```c++
/**
* @file static.cpp
* @brief 静态数据成员
* 静态数据成员被编译器放在程序的一个global data members中它是类的一个数据成员但不影响类的大小。不管这个类产生了多少个实例还是派生了多少新的类静态数据成员只有一个实例。静态数据成员一旦被声明就已经存在。
* @author 光城
* @version v1
* @date 2019-07-21
*/
#include<iostream>
using namespace std;
class A
{
public:
char b;
virtual void fun() {};
static int c;
static int d;
static int f;
};
int main()
{
/**
* @brief 16 字节对齐、静态变量不影响类的大小、vptr指针=8
*/
cout<<sizeof(A)<<endl;
return 0;
}
```
## 3.原则3
```c++
/**
* @file morevir.cpp
* @brief 对于包含虚函数的类,不管有多少个虚函数,只有一个虚指针,vptr的大小。
* @author 光城
* @version v1
* @date 2019-07-21
*/
#include<iostream>
using namespace std;
class A{
virtual void fun();
virtual void fun1();
virtual void fun2();
virtual void fun3();
};
int main()
{
cout<<sizeof(A)<<endl; // 8
return 0;
}
```
## 4.原则4与5
```c++
/**
* @file geninhe.cpp
* @brief 1.普通单继承,继承就是基类+派生类自身的大小(注意字节对齐)
* 注意:类的数据成员按其声明顺序加入内存,无访问权限无关,只看声明顺序。
* 2.虚单继承派生类继承基类vptr
* @author 光城
* @version v1
* @date 2019-07-21
*/
#include<iostream>
using namespace std;
class A
{
public:
char a;
int b;
};
/**
* @brief 此时B按照顺序
* char a
* int b
* short a
* long b
* 根据字节对齐4+4=8+8+8=24
*/
class B:A
{
public:
short a;
long b;
};
class C
{
A a;
char c;
};
class A1
{
virtual void fun(){}
};
class C1:public A
{
};
int main()
{
cout<<sizeof(A)<<endl; // 8
cout<<sizeof(B)<<endl; // 24
cout<<sizeof(C)<<endl; // 12
/**
* @brief 对于虚单函数继承派生类也继承了基类的vptr所以是8字节
*/
cout<<sizeof(C1)<<endl; // 8
return 0;
}
```
## 5.原则6
```c++
/**
* @file virnhe.cpp
* @brief 虚继承
* @author 光城
* @version v1
* @date 2019-07-21
*/
#include<iostream>
using namespace std;
class A
{
virtual void fun() {}
};
class B
{
virtual void fun2() {}
};
class C : virtual public A, virtual public B
{
public:
virtual void fun3() {}
};
int main()
{
/**
* @brief 8 8 16 派生类虚继承多个虚函数会继承所有虚函数的vptr
*/
cout<<sizeof(A)<<" "<<sizeof(B)<<" "<<sizeof(C);
return 0;
}
```

View File

@@ -0,0 +1,18 @@
/**
* @file blackclass.cpp
* @brief 空类的大小为1字节
* @author 光城
* @version v1
* @date 2019-07-21
*/
#include<iostream>
using namespace std;
class A{};
int main()
{
cout<<sizeof(A)<<endl;
return 0;
}

Some files were not shown because too many files have changed in this diff Show More