update
This commit is contained in:
BIN
basic_content/::/::
Executable file
BIN
basic_content/::/::
Executable file
Binary file not shown.
20
basic_content/::/::.cpp
Normal file
20
basic_content/::/::.cpp
Normal 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;
|
||||
}
|
||||
14
basic_content/::/README.md
Normal file
14
basic_content/::/README.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# :: 范围解析运算符那些事
|
||||
|
||||
## 关于作者:
|
||||
|
||||
个人公众号:
|
||||
|
||||

|
||||
|
||||
- 全局作用域符(::name):用于类型名称(类、类成员、成员函数、变量等)前,表示作用域为全局命名空间
|
||||
- 类作用域符(class::name):用于表示指定类型的作用域范围是具体某个类的
|
||||
- 命名空间作用域符(namespace::name):用于表示指定类型的作用域范围是具体某个命名空间的
|
||||
|
||||
具体代码见:[::.cpp](::.cpp)
|
||||
|
||||
58
basic_content/abstract/README.md
Normal file
58
basic_content/abstract/README.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# 纯虚函数和抽象类
|
||||
|
||||
## 关于作者:
|
||||
|
||||
个人公众号:
|
||||
|
||||

|
||||
|
||||
## 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)
|
||||
31
basic_content/abstract/abstract.cpp
Normal file
31
basic_content/abstract/abstract.cpp
Normal 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;
|
||||
}
|
||||
32
basic_content/abstract/derived_full.cpp
Normal file
32
basic_content/abstract/derived_full.cpp
Normal 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;
|
||||
}
|
||||
29
basic_content/abstract/interesting_facts1.cpp
Normal file
29
basic_content/abstract/interesting_facts1.cpp
Normal 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;
|
||||
}
|
||||
|
||||
38
basic_content/abstract/interesting_facts2.cpp
Normal file
38
basic_content/abstract/interesting_facts2.cpp
Normal 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;
|
||||
}
|
||||
|
||||
30
basic_content/abstract/interesting_facts3.cpp
Normal file
30
basic_content/abstract/interesting_facts3.cpp
Normal 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;
|
||||
}
|
||||
|
||||
35
basic_content/abstract/interesting_facts4.cpp
Normal file
35
basic_content/abstract/interesting_facts4.cpp
Normal 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;
|
||||
}
|
||||
30
basic_content/abstract/interesting_facts5.cpp
Normal file
30
basic_content/abstract/interesting_facts5.cpp
Normal 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;
|
||||
}
|
||||
|
||||
29
basic_content/abstract/pure_virtual.cpp
Normal file
29
basic_content/abstract/pure_virtual.cpp
Normal 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是抽象类,不能创建对象
|
||||
}
|
||||
23
basic_content/abstract/test.cpp
Normal file
23
basic_content/abstract/test.cpp
Normal 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 */
|
||||
};
|
||||
56
basic_content/assert/README.md
Normal file
56
basic_content/assert/README.md
Normal file
@@ -0,0 +1,56 @@
|
||||
# assert那些事
|
||||
|
||||
## 关于作者:
|
||||
|
||||
个人公众号:
|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
## 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
BIN
basic_content/assert/assert
Executable file
Binary file not shown.
18
basic_content/assert/assert.c
Normal file
18
basic_content/assert/assert.c
Normal 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
BIN
basic_content/assert/iga
Executable file
Binary file not shown.
19
basic_content/assert/ignore_assert.c
Normal file
19
basic_content/assert/ignore_assert.c
Normal 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
179
basic_content/bit/README.md
Normal file
@@ -0,0 +1,179 @@
|
||||
## 关于作者:
|
||||
|
||||
个人公众号:
|
||||
|
||||

|
||||
|
||||
## 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/>
|
||||
19
basic_content/bit/learn.cpp
Normal file
19
basic_content/bit/learn.cpp
Normal 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;
|
||||
}
|
||||
41
basic_content/c_poly/README.md
Normal file
41
basic_content/c_poly/README.md
Normal file
@@ -0,0 +1,41 @@
|
||||
# C实现C++的面向对象特性
|
||||
|
||||
## 关于作者:
|
||||
|
||||
个人公众号:
|
||||
|
||||

|
||||
|
||||
## 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
BIN
basic_content/c_poly/c++_examp
Executable file
Binary file not shown.
42
basic_content/c_poly/c++_examp.cpp
Normal file
42
basic_content/c_poly/c++_examp.cpp
Normal 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
BIN
basic_content/c_poly/c_examp
Executable file
Binary file not shown.
55
basic_content/c_poly/c_examp.c
Normal file
55
basic_content/c_poly/c_examp.c
Normal 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;
|
||||
}
|
||||
405
basic_content/const/README.md
Normal file
405
basic_content/const/README.md
Normal file
@@ -0,0 +1,405 @@
|
||||
## 关于作者
|
||||
|
||||
微信公众号:
|
||||
|
||||

|
||||
|
||||
## 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=# //const指针必须初始化!且const指针的值不能修改
|
||||
int * t = #
|
||||
*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=# //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修饰普通变量以及指针的含义基本相同:
|
||||
|
||||
(1)const int
|
||||
|
||||
```c++
|
||||
const int func1();
|
||||
```
|
||||
|
||||
这个本身无意义,因为参数返回本身就是赋值给其他的变量!
|
||||
|
||||
(2)const int*
|
||||
|
||||
```c++
|
||||
const int* func2();
|
||||
```
|
||||
|
||||
指针指向的内容不变。
|
||||
|
||||
(3)int *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不能进行声明并初始化,也就是上述使用方法。
|
||||
15
basic_content/const/class_const/c++11_example/apple.cpp
Normal file
15
basic_content/const/class_const/c++11_example/apple.cpp
Normal 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;
|
||||
};
|
||||
|
||||
BIN
basic_content/const/class_const/c++11_example/main
Executable file
BIN
basic_content/const/class_const/c++11_example/main
Executable file
Binary file not shown.
31
basic_content/const/class_const/c++11_example/main.cpp
Normal file
31
basic_content/const/class_const/c++11_example/main.cpp
Normal 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;
|
||||
}
|
||||
13
basic_content/const/class_const/first_example/apple.cpp
Normal file
13
basic_content/const/class_const/first_example/apple.cpp
Normal 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;
|
||||
};
|
||||
|
||||
32
basic_content/const/class_const/first_example/main.cpp
Normal file
32
basic_content/const/class_const/first_example/main.cpp
Normal 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;
|
||||
}
|
||||
13
basic_content/const/class_const/overload_example/apple.cpp
Normal file
13
basic_content/const/class_const/overload_example/apple.cpp
Normal 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;
|
||||
};
|
||||
|
||||
BIN
basic_content/const/class_const/overload_example/main
Executable file
BIN
basic_content/const/class_const/overload_example/main
Executable file
Binary file not shown.
32
basic_content/const/class_const/overload_example/main.cpp
Normal file
32
basic_content/const/class_const/overload_example/main.cpp
Normal 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;
|
||||
}
|
||||
14
basic_content/const/class_const/static_example/apple.cpp
Normal file
14
basic_content/const/class_const/static_example/apple.cpp
Normal 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;
|
||||
};
|
||||
|
||||
BIN
basic_content/const/class_const/static_example/main
Executable file
BIN
basic_content/const/class_const/static_example/main
Executable file
Binary file not shown.
34
basic_content/const/class_const/static_example/main.cpp
Normal file
34
basic_content/const/class_const/static_example/main.cpp
Normal 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;
|
||||
}
|
||||
11
basic_content/const/const_function.cpp
Normal file
11
basic_content/const/const_function.cpp
Normal 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);
|
||||
}
|
||||
8
basic_content/const/const_num.cpp
Normal file
8
basic_content/const/const_num.cpp
Normal 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;
|
||||
}
|
||||
1
basic_content/const/extern_const/const_file1.cpp
Normal file
1
basic_content/const/extern_const/const_file1.cpp
Normal file
@@ -0,0 +1 @@
|
||||
extern const int ext=12;
|
||||
11
basic_content/const/extern_const/const_file2.cpp
Normal file
11
basic_content/const/extern_const/const_file2.cpp
Normal 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;
|
||||
}
|
||||
1
basic_content/const/extern_const/file1.cpp
Normal file
1
basic_content/const/extern_const/file1.cpp
Normal file
@@ -0,0 +1 @@
|
||||
int ext;
|
||||
11
basic_content/const/extern_const/file2.cpp
Normal file
11
basic_content/const/extern_const/file2.cpp
Normal 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;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#include<iostream>
|
||||
using namespace std;
|
||||
|
||||
|
||||
int main(){
|
||||
const int *ptr;
|
||||
*ptr=10; //error
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
#include<iostream>
|
||||
using namespace std;
|
||||
|
||||
|
||||
int main(){
|
||||
const int p = 10;
|
||||
const void *vp = &p;
|
||||
void *vp = &p; //error
|
||||
}
|
||||
12
basic_content/const/funciton_const/condition1/condition3.cpp
Normal file
12
basic_content/const/funciton_const/condition1/condition3.cpp
Normal 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;
|
||||
|
||||
}
|
||||
BIN
basic_content/const/funciton_const/condition2/condition1
Executable file
BIN
basic_content/const/funciton_const/condition2/condition1
Executable file
Binary file not shown.
10
basic_content/const/funciton_const/condition2/condition1.cpp
Normal file
10
basic_content/const/funciton_const/condition2/condition1.cpp
Normal file
@@ -0,0 +1,10 @@
|
||||
#include<iostream>
|
||||
using namespace std;
|
||||
int main(){
|
||||
|
||||
int num=0;
|
||||
int * const ptr=# //const指针必须初始化!且const指针的值不能修改
|
||||
int * t = #
|
||||
*t = 1;
|
||||
cout<<*ptr<<endl;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
#include<iostream>
|
||||
using namespace std;
|
||||
int main(){
|
||||
|
||||
const int num=0;
|
||||
int * const ptr=# //error! const int* -> int*
|
||||
cout<<*ptr<<endl;
|
||||
}
|
||||
BIN
basic_content/const/funciton_const/condition2/condition3
Executable file
BIN
basic_content/const/funciton_const/condition2/condition3
Executable file
Binary file not shown.
@@ -0,0 +1,8 @@
|
||||
#include<iostream>
|
||||
using namespace std;
|
||||
int main(){
|
||||
|
||||
const int num=10;
|
||||
const int * const ptr=# //error! const int* -> int*
|
||||
cout<<*ptr<<endl;
|
||||
}
|
||||
BIN
basic_content/const/funciton_const/condition3/condition1
Executable file
BIN
basic_content/const/funciton_const/condition3/condition1
Executable file
Binary file not shown.
10
basic_content/const/funciton_const/condition3/condition1.cpp
Normal file
10
basic_content/const/funciton_const/condition3/condition1.cpp
Normal file
@@ -0,0 +1,10 @@
|
||||
#include<iostream>
|
||||
using namespace std;
|
||||
|
||||
int main(){
|
||||
|
||||
const int p = 3;
|
||||
const int * const ptr = &p;
|
||||
cout<<*ptr<<endl;
|
||||
|
||||
}
|
||||
138
basic_content/decltype/README.md
Normal file
138
basic_content/decltype/README.md
Normal file
@@ -0,0 +1,138 @@
|
||||
# decltype那些事
|
||||
|
||||
## 关于作者:
|
||||
|
||||
个人公众号:
|
||||
|
||||

|
||||
|
||||
## 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是一个没有带括号的标记符表达式或者类成员访问表达式,那么的decltype(e)就是e所命名的实体的类型。此外,如果e是一个被重载的函数,则会导致编译错误。
|
||||
否则 ,假设e的类型是T,如果e是一个将亡值,那么decltype(e)为T&&
|
||||
否则,假设e的类型是T,如果e是一个左值,那么decltype(e)为T&。
|
||||
否则,假设e的类型是T,则decltype(e)为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
BIN
basic_content/decltype/decltype
Executable file
Binary file not shown.
60
basic_content/decltype/decltype.cpp
Normal file
60
basic_content/decltype/decltype.cpp
Normal 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;
|
||||
}
|
||||
105
basic_content/enum/README.md
Normal file
105
basic_content/enum/README.md
Normal file
@@ -0,0 +1,105 @@
|
||||
# 从初级到高级的enum那些事
|
||||
|
||||
## 关于作者:
|
||||
|
||||
个人公众号:
|
||||
|
||||

|
||||
|
||||
|
||||
## 传统行为
|
||||
|
||||
|
||||
枚举有如下问题:
|
||||
|
||||
- 作用域不受限,会容易引起命名冲突。例如下面无法编译通过的:
|
||||
|
||||
```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)
|
||||
BIN
basic_content/enum/classic_practice
Executable file
BIN
basic_content/enum/classic_practice
Executable file
Binary file not shown.
87
basic_content/enum/classic_practice.cpp
Normal file
87
basic_content/enum/classic_practice.cpp
Normal 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;
|
||||
}
|
||||
10
basic_content/enum/tradition_color.cpp
Normal file
10
basic_content/enum/tradition_color.cpp
Normal file
@@ -0,0 +1,10 @@
|
||||
#include <iostream>
|
||||
using namespace std;
|
||||
|
||||
enum Color {RED,BLUE};
|
||||
enum Feeling {EXCITED,BLUE};
|
||||
|
||||
int main()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
16
basic_content/explicit/README.md
Normal file
16
basic_content/explicit/README.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# explicit(显式)关键字那些事
|
||||
|
||||
## 关于作者:
|
||||
|
||||
个人公众号:
|
||||
|
||||

|
||||
|
||||
- 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
BIN
basic_content/explicit/explicit
Executable file
Binary file not shown.
46
basic_content/explicit/explicit.cpp
Normal file
46
basic_content/explicit/explicit.cpp
Normal 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); // OK:static_cast 进行直接初始化
|
||||
|
||||
return 0;
|
||||
}
|
||||
193
basic_content/extern/README.md
vendored
Normal file
193
basic_content/extern/README.md
vendored
Normal file
@@ -0,0 +1,193 @@
|
||||
# extern "C"
|
||||
|
||||
## 关于作者:
|
||||
|
||||
个人公众号:
|
||||
|
||||

|
||||
|
||||
## 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程序相关文件或其头文件中。
|
||||
|
||||
总结出如下形式:
|
||||
|
||||
(1)C++调用C函数:
|
||||
|
||||
```c++
|
||||
//xx.h
|
||||
extern int add(...)
|
||||
|
||||
//xx.c
|
||||
int add(){
|
||||
|
||||
}
|
||||
|
||||
//xx.cpp
|
||||
extern "C" {
|
||||
#include "xx.h"
|
||||
}
|
||||
```
|
||||
|
||||
(2)C调用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
5
basic_content/extern/extern_c++/add.c
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
#include "add.h"
|
||||
|
||||
int add(int x,int y) {
|
||||
return x+y;
|
||||
}
|
||||
9
basic_content/extern/extern_c++/add.cpp
vendored
Normal file
9
basic_content/extern/extern_c++/add.cpp
vendored
Normal 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
4
basic_content/extern/extern_c++/add.h
vendored
Normal 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
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
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
5
basic_content/extern/extern_c/add.c
vendored
Normal 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
5
basic_content/extern/extern_c/add.cpp
vendored
Normal 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
6
basic_content/extern/extern_c/add.h
vendored
Normal 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
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
BIN
basic_content/extern/extern_c/main
vendored
Executable file
Binary file not shown.
115
basic_content/friend/README.md
Normal file
115
basic_content/friend/README.md
Normal file
@@ -0,0 +1,115 @@
|
||||
# 友元函数与友元类
|
||||
|
||||
## 关于作者:
|
||||
|
||||
个人公众号:
|
||||
|
||||

|
||||
|
||||
## 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
BIN
basic_content/friend/friend_class
Executable file
Binary file not shown.
28
basic_content/friend/friend_class.cpp
Normal file
28
basic_content/friend/friend_class.cpp
Normal 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
BIN
basic_content/friend/friend_func
Executable file
Binary file not shown.
33
basic_content/friend/friend_func.cpp
Normal file
33
basic_content/friend/friend_func.cpp
Normal 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
BIN
basic_content/func_pointer/func1
Executable file
Binary file not shown.
33
basic_content/func_pointer/func_pointer.cpp
Normal file
33
basic_content/func_pointer/func_pointer.cpp
Normal 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
BIN
basic_content/img/wechat.jpg
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
137
basic_content/inline/README.md
Normal file
137
basic_content/inline/README.md
Normal file
@@ -0,0 +1,137 @@
|
||||
# inline那些事
|
||||
|
||||
## 关于作者:
|
||||
|
||||
个人公众号:
|
||||
|
||||

|
||||
|
||||
## 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
BIN
basic_content/inline/inline
Executable file
Binary file not shown.
57
basic_content/inline/inline.cpp
Normal file
57
basic_content/inline/inline.cpp
Normal 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)。
|
||||
*/
|
||||
|
||||
|
||||
|
||||
19
basic_content/inline/inline.h
Normal file
19
basic_content/inline/inline.h
Normal 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关键字。
|
||||
|
||||
};
|
||||
35
basic_content/inline/inline_virtual.cpp
Normal file
35
basic_content/inline/inline_virtual.cpp
Normal 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
BIN
basic_content/inline/iv
Executable file
Binary file not shown.
232
basic_content/macro/README.md
Normal file
232
basic_content/macro/README.md
Normal file
@@ -0,0 +1,232 @@
|
||||
# 宏那些事
|
||||
|
||||
## 关于作者:
|
||||
|
||||
个人公众号:
|
||||
|
||||

|
||||
|
||||
## 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
BIN
basic_content/macro/do_while
Executable file
Binary file not shown.
76
basic_content/macro/do_while.cpp
Normal file
76
basic_content/macro/do_while.cpp
Normal 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
BIN
basic_content/macro/sig_examp
Executable file
Binary file not shown.
86
basic_content/macro/sig_examp.cpp
Normal file
86
basic_content/macro/sig_examp.cpp
Normal 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;
|
||||
}
|
||||
174
basic_content/pointer_refer/README.md
Normal file
174
basic_content/pointer_refer/README.md
Normal file
@@ -0,0 +1,174 @@
|
||||
# 引用与指针那些事
|
||||
|
||||
## 关于作者:
|
||||
|
||||
个人公众号:
|
||||
|
||||

|
||||
|
||||
## 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/#>
|
||||
BIN
basic_content/pointer_refer/copy_construct
Executable file
BIN
basic_content/pointer_refer/copy_construct
Executable file
Binary file not shown.
35
basic_content/pointer_refer/copy_construct.cpp
Normal file
35
basic_content/pointer_refer/copy_construct.cpp
Normal 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
BIN
basic_content/pointer_refer/effec
Executable file
Binary file not shown.
22
basic_content/pointer_refer/effec.cpp
Normal file
22
basic_content/pointer_refer/effec.cpp
Normal 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;
|
||||
}
|
||||
191
basic_content/sizeof/README.md
Normal file
191
basic_content/sizeof/README.md
Normal 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;
|
||||
}
|
||||
```
|
||||
|
||||
18
basic_content/sizeof/blackclass.cpp
Normal file
18
basic_content/sizeof/blackclass.cpp
Normal 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
Reference in New Issue
Block a user