From ce125cfa5fef691131483a6a85de46d32243ff9b Mon Sep 17 00:00:00 2001 From: Aurelius84 Date: Fri, 12 Jun 2020 21:03:43 +0800 Subject: [PATCH] refine content of assert and const --- basic_content/assert/README.md | 21 ++-- basic_content/assert/ignore_assert.c | 4 +- basic_content/const/README.md | 139 ++++++++++++--------------- 3 files changed, 78 insertions(+), 86 deletions(-) diff --git a/basic_content/assert/README.md b/basic_content/assert/README.md index 534fc49..8699eff 100644 --- a/basic_content/assert/README.md +++ b/basic_content/assert/README.md @@ -10,12 +10,17 @@ ## 1.第一个断言案例 -断言,**是宏,而非函数**。assert 宏的原型定义在 (C)、(C++)中,其作用是如果它的条件返回错误,则终止程序执行。可以通过定义 NDEBUG 来关闭 assert,但是需要在源代码的开头,include 之前。 +断言,**是宏,而非函数**。 + +assert 宏的原型定义在 (C)、(C++)中。其作用是如果它的条件返回错误,则终止程序执行。 + +可以通过定义 `NDEBUG` 来关闭 assert,**但是需要在源代码的开头,include 之前。** ```c void assert(int expression); ``` -对应代码:[assert.c](./assert.c) + +> 代码样例:[assert.c](./assert.c) ```c #include #include @@ -35,22 +40,22 @@ int main() return 0; } - ``` 输出: ```c -assert: assert.c:13: main: Assertion `x==7' failed. +assert: assert.c:13: main: Assertion 'x==7' failed. ``` 可以看到输出会把源码文件,行号错误位置,提示出来! ## 2.断言与正常错误处理 -断言主要用于检查逻辑上不可能的情况。例如,它们可用于检查代码在开始运行之前所期望的状态,或者在运行完成后检查状态。与正常的错误处理不同,断言通常在运行时被禁用。 ++ 断言主要用于检查逻辑上不可能的情况。 -忽略断言: +>例如,它们可用于检查代码在开始运行之前所期望的状态,或者在运行完成后检查状态。与正常的错误处理不同,断言通常在运行时被禁用。 -在代码开头加上: ++ 忽略断言,在代码开头加上: ```c++ #define NDEBUG // 加上这行,则 assert 不可用 ``` -对应学习的代码:[ignore_assert.c](./ignore_assert.c) + +> 样例代码:[ignore_assert.c](./ignore_assert.c) diff --git a/basic_content/assert/ignore_assert.c b/basic_content/assert/ignore_assert.c index d3069e5..0a5a15e 100644 --- a/basic_content/assert/ignore_assert.c +++ b/basic_content/assert/ignore_assert.c @@ -6,12 +6,10 @@ * @date 2019-07-25 */ -# define NDEBUG +# define NDEBUG // 忽略断言 #include - - int main(){ int x=7; assert(x==5); diff --git a/basic_content/const/README.md b/basic_content/const/README.md index 69af42c..c23e458 100644 --- a/basic_content/const/README.md +++ b/basic_content/const/README.md @@ -10,47 +10,45 @@ ## 2.const作用 -(1)可以定义常量 ++ 可以定义常量 -```c++ +```cpp const int a=100; ``` -(2)类型检查 ++ 类型检查 -const常量与#define宏定义常量的区别:~~**const常量具有类型,编译器可以进行安全检查;#define宏定义没有数据类型,只是简单的字符串替换,不能进行安全检查。**~~感谢两位大佬指出这里问题,见: + + const常量与`#define`宏定义常量的区别: + > ~~**const常量具有类型,编译器可以进行安全检查;#define宏定义没有数据类型,只是简单的字符串替换,不能进行安全检查。**~~感谢两位大佬指出这里问题,见:[issue](https://github.com/Light-City/CPlusPlusThings/issues/5) -> https://github.com/Light-City/CPlusPlusThings/issues/5 + + const定义的变量只有类型为整数或枚举,且以常量表达式初始化时才能作为常量表达式。 + + 其他情况下它只是一个 `const` 限定的变量,不要将与常量混淆。 -`const` 定义的变量只有类型为整数或枚举,且以常量表达式初始化时才能作为常量表达式。其他情况下它只是一个 `const` 限定的变量,不要将与常量混淆。 ++ 防止修改,起保护作用,增加程序健壮性 -(3)防止修改,起保护作用,增加程序健壮性 - -```c++ +```cpp void f(const int i){ i++; //error! } ``` -(4)可以节省空间,避免不必要的内存分配 ++ 可以节省空间,避免不必要的内存分配 -const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是像#define一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而#define定义的常量在内存中有若干个拷贝。 + + const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是像`#define`一样给出的是立即数。 + + const定义的常量在程序运行过程中只有一份拷贝,而`#define`定义的常量在内存中有若干个拷贝。 ## 3.const对象默认为文件局部变量

注意:非const变量默认为extern。要使const变量能够在其他文件中访问,必须在文件中显式地指定它为extern。

+ > 未被const修饰的变量在不同文件的访问 -```c++ +```cpp // file1.cpp int ext // file2.cpp #include -/** - * by 光城 - * compile: g++ -o file file2.cpp file1.cpp - * execute: ./file - */ + extern int ext; int main(){ std::cout<<(ext+10)< const常量在不同文件的访问 -```c++ +```cpp //extern_file1.cpp extern const int ext=12; //extern_file2.cpp #include -/** - * by 光城 - * compile: g++ -o file const_file2.cpp const_file1.cpp - * execute: ./file - */ extern const int ext; int main(){ std::cout<小结:可以发现未被const修饰的变量不需要extern显式声明!而const常量需要显式声明extern,并且需要做初始化!因为常量在定义后就不能被修改,所以定义时必须初始化。

+> 小结:
可以发现未被const修饰的变量不需要extern显式声明!而const常量需要显式声明extern,并且需要做初始化!因为常量在定义后就不能被修改,所以定义时必须初始化。

## 4.定义常量 @@ -86,7 +79,9 @@ const string s = "helloworld"; const int i,j=0 // error: uninitialized const ‘i’ ``` -上述有两个错误,第一:b为常量,不可更改!第二:i为常量,必须进行初始化!(因为常量在定义后就不能被修改,所以定义时必须初始化。) +上述有两个错误: ++ b 为常量,不可更改! ++ i 为常量,必须进行初始化!(因为常量在定义后就不能被修改,所以定义时必须初始化。) ## 5.指针与const @@ -99,13 +94,14 @@ char * const a; //指向类型对象的const指针。或者说常指针、const const char * const a; //指向const对象的const指针。 ``` -小结:如果*const*位于`*`的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量;如果const位于`*`的右侧,*const*就是修饰指针本身,即指针本身是常量。 +> **小结:**
+> 如果*const*位于`*`的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量;
如果const位于`*`的右侧,*const*就是修饰指针本身,即指针本身是常量。 具体使用如下: -(1)指向常量的指针 +(1) **指向常量的指针** -```c++ +```cpp const int *ptr; *ptr = 10; //error ``` @@ -140,12 +136,13 @@ int *ptr1 = &val; cout<<*ptr<小结:对于指向常量的指针,不能通过指针来修改对象的值。
也不能使用void`*`指针保存const对象的地址,必须使用const void`*`类型的指针保存const对象的地址。
允许把非const对象的地址赋值给const对象的指针,如果要修改指针所指向的对象值,必须通过其他方式修改,不能直接通过当前指针直接修改。

-(2)常指针 +> 小结:
1.对于指向常量的指针,不能通过指针来修改对象的值。
2.不能使用void`*`指针保存const对象的地址,必须使用const void`*`类型的指针保存const对象的地址。
3.允许把非const对象的地址赋值给const对象的指针,如果要修改指针所指向的对象值,必须通过其他方式修改,不能直接通过当前指针直接修改。 + +(2) **常指针** const指针必须进行初始化,且const指针的值不能修改。 -```c++ +```cpp #include using namespace std; int main(){ @@ -162,7 +159,7 @@ int main(){ 最后,当把一个const常量的地址赋值给ptr时候,由于ptr指向的是一个变量,而不是const常量,所以会报错,出现:const int`*` -> int `*`错误! -```c++ +```cpp #include using namespace std; int main(){ @@ -174,11 +171,11 @@ int main(){ 上述若改为 const int `*`ptr或者改为const int `*`const ptr,都可以正常! -(3)指向常量的常指针 +(3)**指向常量的常指针** 理解完前两种情况,下面这个情况就比较好理解了: -```c++ +```cpp const int p = 3; const int * const ptr = &p; ``` @@ -191,17 +188,17 @@ ptr是一个const指针,然后指向了一个int 类型的const对象。 这个跟const修饰普通变量以及指针的含义基本相同: -(1)const int +(1)**const int** -```c++ +```cpp const int func1(); ``` 这个本身无意义,因为参数返回本身就是赋值给其他的变量! -(2)const int* +(2)**const int*** -```c++ +```cpp const int* func2(); ``` @@ -209,7 +206,7 @@ const int* func2(); (3)int *const -```c++ +```cpp int *const func2(); ``` @@ -219,7 +216,7 @@ int *const func2(); (1)传递过来的参数及指针本身在函数内不可变,无意义! -```c++ +```cpp void func(const int var); // 传递过来的参数不可变 void func(int *const var); // 指针本身不可变 ``` @@ -228,60 +225,49 @@ void func(int *const var); // 指针本身不可变 输入参数采用“值传递”,由于函数将自动产生临时变量用于复制该参数,该输入参数本来就无需保护,所以不要加const 修饰。 -(2)参数指针所指内容为常量不可变 +(2)**参数指针所指内容为常量不可变** -```c++ +```cpp void StringCopy(char *dst, const char *src); ``` 其中src 是输入参数,dst 是输出参数。给src加上const修饰后,如果函数体内的语句试图改动src的内容,编译器将指出错误。这就是加了const的作用之一。 -(3)参数为引用,为了增加效率同时防止修改。 +(3)**参数为引用,为了增加效率同时防止修改。** -```c++ +```cpp void func(const A &a) ``` -对于非内部数据类型的参数而言,象void func(A a) 这样声明的函数注定效率比较低。因为函数体内将产生A 类型 - -的临时对象用于复制参数a,而临时对象的构造、复制、析构过程都将消耗时间。 +对于非内部数据类型的参数而言,象void func(A a) 这样声明的函数注定效率比较低。因为函数体内将产生A 类型的临时对象用于复制参数a,而临时对象的构造、复制、析构过程都将消耗时间。 为了提高效率,可以将函数声明改为void func(A &a),因为“引用传递”仅借用一下参数的别名而已,不需要产生临 +时对象。 -时对象。但是函数void func(A &a) 存在一个缺点: - -“引用传递”有可能改变参数a,这是我们不期望的。解决这个问题很容易,加const修饰即可,因此函数最终成为 - +> 但是函数void func(A &a) 存在一个缺点:

“引用传递”有可能改变参数a,这是我们不期望的。解决这个问题很容易,加const修饰即可,因此函数最终成为 void func(const A &a)。 以此类推,是否应将void func(int x) 改写为void func(const int &x),以便提高效率?完全没有必要,因为内部数 据类型的参数不存在构造、析构的过程,而复制也非常快,“值传递”和“引用传递”的效率几乎相当。 -

小结:对于非内部数据类型的输入参数,应该将“值传递”的方式改为“const 引用传递”,目的是提高效率。例如将void func(A a) 改为void func(const A &a)。
对于内部数据类型的输入参数,不要将“值传递”的方式改为“const 引用传递”。否则既达不到提高效率的目的,又降低了函数的可理解性。例如void func(int x) 不应该改为void func(const int &x)。

+> 小结:
1.对于非内部数据类型的输入参数,应该将“值传递”的方式改为“const 引用传递”,目的是提高效率。例如将void func(A a) 改为void func(const A &a)。

2.对于内部数据类型的输入参数,不要将“值传递”的方式改为“const 引用传递”。否则既达不到提高效率的目的,又降低了函数的可理解性。例如void func(int x) 不应该改为void func(const int &x)。

+ 以上解决了两个面试问题: -(1)如果函数需要传入一个指针,是否需要为该指针加上const,把const加在指针不同的位置有什么区别; - -(2)如果写的函数需要传入的参数是一个复杂类型的实例,传入值参数或者引用参数有什么区别,什么时候需要为传入的引用参数加上const。 ++ 如果函数需要传入一个指针,是否需要为该指针加上const,把const加在指针不同的位置有什么区别; ++ 如果写的函数需要传入的参数是一个复杂类型的实例,传入值参数或者引用参数有什么区别,什么时候需要为传入的引用参数加上const。 ## 7.类中使用const -在一个类中,任何不会修改数据成员的函数都应该声明为const类型。如果在编写const成员函数时,不慎修改 - -数据成员,或者调用了其它非const成员函数,编译器将指出错误,这无疑会提高程序的健壮性。使用const关 - -字进行说明的成员函数,称为常成员函数。只有常成员函数才有资格操作常量或常对象,没有使用const关键字 - -明的成员函数不能用来操作常对象。 - +在一个类中,任何不会修改数据成员的函数都应该声明为const类型。如果在编写const成员函数时,不慎修改 数据成员,或者调用了其它非const成员函数,编译器将指出错误,这无疑会提高程序的健壮性。 +使用const关键字进行说明的成员函数,称为常成员函数。只有常成员函数才有资格操作常量或常对象,没有使用const关键字进行说明的成员函数不能用来操作常对象。 对于类中的const成员变量必须通过初始化列表进行初始化,如下所示: -```c++ -class Apple -{ +```cpp +class Apple{ private: int people[100]; public: @@ -299,7 +285,7 @@ const对象只能访问const成员函数,而非const对象可以访问任意的 例如: -```c++ +```cpp //apple.cpp class Apple { @@ -347,8 +333,11 @@ int main(){ b.add(100); return 0; } -//编译: g++ -o main main.cpp apple.cpp -//结果 +``` +> 编译: g++ -o main main.cpp apple.cpp
+ +结果: +``` take func 1 2 take func 10 @@ -365,21 +354,21 @@ take func 100 第一:将常量定义与static结合,也就是: -```c++ +```cpp static const int apple_number ``` 第二:在外面初始化: -```c++ +```cpp const int Apple::apple_number=10; ``` 当然,如果你使用c++11进行编译,直接可以在定义出初始化,可以直接写成: -```c++ +```cpp static const int apple_number=10; -或者 +// 或者 const int apple_number=10; ``` @@ -393,13 +382,13 @@ const int apple_number=10; 在类中声明: -```c++ +```cpp static int ap; ``` 在类实现文件中使用: -```c++ +```cpp int Apple::ap=666 ```