本 Wiki 开启了 HTTPS。但由于同 IP 的 Blog 也开启了 HTTPS,因此本站必须要支持 SNI 的浏览器才能浏览。为了兼容一部分浏览器,本站保留了 HTTP 作为兼容。如果您的浏览器支持 SNI,请尽量通过 HTTPS 访问本站,谢谢!
这里会显示出您选择的修订版和当前版本之间的差别。
两侧同时换到之前的修订记录前一修订版后一修订版 | 前一修订版 | ||
cs:programming:cpp:cpp_primer:2_var_n_types [2024/01/14 13:46] – 移除 - 外部编辑 (未知日期) 127.0.0.1 | cs:programming:cpp:cpp_primer:2_var_n_types [2024/04/16 11:38] (当前版本) – [指针与 constexpr] codinghare | ||
---|---|---|---|
行 1: | 行 1: | ||
+ | ======变量和基本类型====== | ||
+ | C++ Primer 笔记 第二章\\ | ||
+ | ---- | ||
+ | 在 C++ 中,基础类型(// | ||
+ | ====算术类型==== | ||
+ | |||
+ | 在C++中, 整型(// | ||
+ | - //bool//, 存放 '' | ||
+ | - //char//, 存放字符,C++ 中有比标准 '' | ||
+ | - //int//, 存放整数,有 '' | ||
+ | <WRAP center round tip 100%> | ||
+ | 计算机使用二进制序列(比如01010101)来处理数据。数列中的每一位称为 **bit**。大部分情况下,计算机会将二进制序列按块划分,块的大小往往是 2 的 n 次方。这些块中,最小的被称为 **Byte**(Byte 需要足够大来装载计算机中的基础字符集)。Byte 拥有自身唯一的地址。 | ||
+ | \\ 计算机一次运算能够处理的 Byte 的数量,称为 **Word**,这也往往是存储器的单位大小。现代计算机的 Byte 大小一般为 8 bits, word 大小为 32 或 64 bits. | ||
+ | </ | ||
+ | ===unsigned 类型=== | ||
+ | |||
+ | 对于所有 // | ||
+ | \\ \\ | ||
+ | unsigned 类型的所有位数都用于存储值,因此一般来说范围是从 0 开始。signed 类型由于需要多出一位记录正负,因此能表达的数值范围会以 0 为中心点。 | ||
+ | |||
+ | ===关于类型使用的注意事项=== | ||
+ | |||
+ | - '' | ||
+ | - 对整型操作中,'' | ||
+ | - 不要将 '' | ||
+ | - 浮点数运算请使用 '' | ||
+ | |||
+ | ===类型转换=== | ||
+ | ==Bool 与其他类型的转换== | ||
+ | * nobool -> bool : '' | ||
+ | * bool -> nobool(算数类型) : '' | ||
+ | ==Float point 与 整型的转换== | ||
+ | * '' | ||
+ | * '' | ||
+ | ==out of range value== | ||
+ | * 超过范围的值 -> '' | ||
+ | * 超过范围的值 -> '' | ||
+ | <WRAP center round important 100%> | ||
+ | 在很多环境中,编译器不会(也不能)检查**未定义行为**(// | ||
+ | </ | ||
+ | ==unsigned+singed 运算导致的类型转换== | ||
+ | |||
+ | 如果进行 unsigned+singed 运算,**结果将会被转换为 unsigned 类型**。也就是说,如果结果为负数,会进行取模运算。 | ||
+ | |||
+ | <WRAP center round important 100%> | ||
+ | 如无必要,请不要把 **unsigned** 和 **signed** 类型混淆运算。 | ||
+ | </ | ||
+ | ===Literals=== | ||
+ | **字面值常量**(// | ||
+ | ==整型 / 浮点型的字面值== | ||
+ | 整型和浮点型的字面值可以写成十进制(decimal)、八进制(octal)或者十六进制(hexdecimal)的形式,比如: | ||
+ | <code cpp> | ||
+ | 20 decimal //signed by default | ||
+ | 024 octal //start with 0 | ||
+ | 0x14 hexadeicmal //start with 0x | ||
+ | </ | ||
+ | 当本类型常量为八进制或者十进制的时候,其数据类型为最小的可以容纳下该数据的类型(比如 int 能容纳下,就是 int,否则就尝试 long,以此类推)。\\ \\ | ||
+ | 技术上来说,十进制下的常量不会为负值。也就是说,符号不包含在字面值常量中,而是一种对字面值常量取负的运算。\\ \\ | ||
+ | 浮点型的字面值为**小数**,可以由科学计数法的小数表示。 | ||
+ | ==字符 / 字符串字面值== | ||
+ | * 字符 / 字符串的字面值由单引号 / 双引号之间的内容表示。 | ||
+ | * 字符串比看上去的字面值长度要大 1,因为编译器会在字符串末尾加 '' | ||
+ | * 如果字符串之间仅由空格、缩进、换行符分割,那么实际上他们是一个整体。 | ||
+ | ==转义序列== | ||
+ | <WRAP center round important 100%> | ||
+ | Escape Sequence 是 char / string type literal,因此输出的时候需要加上 quote. | ||
+ | </ | ||
+ | |||
+ | Ref: | ||
+ | **转义序列**(// | ||
+ | {{ cs: | ||
+ | ==Numeric escape sequences== | ||
+ | 也被称为 generalized escape sequence。该序列通过两种组合方式来表现不同的不可打印字符(总之就是用数字来表示对应的字符): | ||
+ | * 以 '' | ||
+ | * 以 '' | ||
+ | 上面的数对应了字符的字符值([[https:// | ||
+ | ====变量==== | ||
+ | |||
+ | 变量被存储于计算机里,可以被计算机做修改操作。C++ 中变量的一般包括变量( // | ||
+ | |||
+ | ===变量的定义与声明=== | ||
+ | |||
+ | 在C++中,变量的**定义**( // | ||
+ | \\ | ||
+ | 因此,我们通过在一个文件中定义一个对象(变量),然后在其他文件中需要使用的时候声明便可以使用了。正常的使用流程是,首先做出定义(存到指定的 header),然后在到需要使用的地方声明一遍即可。 | ||
+ | \\ | ||
+ | ==keyword extern== | ||
+ | 如果希望只声明而不定义变量,可以使用 '' | ||
+ | * 如果在含有 '' | ||
+ | * '' | ||
+ | <code cpp> | ||
+ | extern int i; // | ||
+ | extern int i = 10; // | ||
+ | </ | ||
+ | <WRAP center round info 100%> | ||
+ | C++ 是静态语言,因此在编译的时候编译器必须知道变量的类型。编译过程中编译器会检查程序中的运算是否支持当前变量,如果不支持则会报错。 | ||
+ | </ | ||
+ | |||
+ | ===整型变量的定义和初始化=== | ||
+ | |||
+ | 在C++中,初始化和赋值是两个完全不同的概念。**初始化发生在对象创建的时候,而赋值是替换已存在对象的值。** | ||
+ | |||
+ | ==初始化的形式== | ||
+ | |||
+ | C++提供了4种形式的整形变量初始化: | ||
+ | <code cpp linenums: | ||
+ | int a = 0; // expression | ||
+ | char c = {' | ||
+ | int b{0}; // { initializer-list } | ||
+ | char d(' | ||
+ | </ | ||
+ | |||
+ | ==List 初始化== | ||
+ | |||
+ | 其中带 //curly braces// 的初始化形式是新标准, | ||
+ | <code cpp> | ||
+ | double x = 3.1415926; | ||
+ | int a{x}; // error: narrowing conversion | ||
+ | int b(x), c = x; //ok: b and c are truncated | ||
+ | </ | ||
+ | |||
+ | ==默认初始化== | ||
+ | |||
+ | |||
+ | 如果在初始化的时候没有制定初始化的值,那么编译器就会对变量做**默认初始化**( //Default Initialization// | ||
+ | - 对象初始化的位置:算术类型定义在函数**外部**的,默认值为 '' | ||
+ | - 对象的类型: | ||
+ | ==Identifiers / 如何命名== | ||
+ | 总的来说,Identifiers 指广义范围内变量 / 对象的名字。Identifiers 有几个特性: | ||
+ | * 必须以**字母**(// | ||
+ | * 区分大小写。 | ||
+ | * 不能与系统保留的关键字重名。 | ||
+ | 一些约定俗成的规矩: | ||
+ | * Identifiers 需要表意 | ||
+ | * variable Identifiers 一般情况下小写 | ||
+ | * class Identifiers 一般开头大写 | ||
+ | * 较长的 Identifiers 使用下划线或者骆驼写法区分,比如 '' | ||
+ | === Scope of a name=== | ||
+ | 如果希望同一个 name 对应不同的实体,可以使用 //Scope// 实现。通过在指定的 scope 内声明变量,就可以在指定的作用域内绑定name 与实体。作用域为的 scope 的范围,其通过 //curly brace// | ||
+ | * '' | ||
+ | * 在 scope 中定义的变量,其作用域范围从其被看见(声明)的位置开始直到该 scope 结束。这种变量具有 **block scope**。 | ||
+ | <WRAP center round info 100%> | ||
+ | 变量 / 对象的定义最好是放置在其对应的 scope 附近。这样可以提高程序的可读性,也更容易给与该变量/ | ||
+ | </ | ||
+ | ==Nested Scope== | ||
+ | scope 是可以嵌套的。外部的被称为 outter scope,内部的则被称为 inner scope。通常情况下: | ||
+ | * outter scope 定义的变量可以被 Inner scope 使用 | ||
+ | * inner scope 可以定义与 outter scope 名字相同的变量,并**重新定义**,也就是**就近使用** | ||
+ | 如果在 inner scope 已经做了与外部变量重名的定义,但又要使用外部重名的变量,可以使用 //Scope operator// ''::'' | ||
+ | <code cpp> | ||
+ | #include < | ||
+ | int reused = 42; | ||
+ | int main(int argc, char const *argv[]) | ||
+ | { | ||
+ | std::cout << reused << std::endl; //print the outter reused, the 42 | ||
+ | int reused = 0; | ||
+ | std::cout << reused << std::endl; //print the inner reused, the 0, outter definition overwrited | ||
+ | std::cout << ::reused << std::endl; //print the outter reused, the 42, explictly accesss the outter variable | ||
+ | |||
+ | return 0; | ||
+ | } | ||
+ | </ | ||
+ | ====复合类型==== | ||
+ | |||
+ | **复合类型**( //Compound Type// )是指基于某种指定的类型而定义下来的类型。C++ 中 比较重要的复合类型有:**指针**(// | ||
+ | |||
+ | ===引用=== | ||
+ | |||
+ | 引用定义了一个**对象的别名**(// | ||
+ | <code cpp linenums: | ||
+ | int ival = 1024; //object will be bind. | ||
+ | int &refVal =ival; //a reference will bind to ival | ||
+ | int & | ||
+ | </ | ||
+ | |||
+ | ==引用的定义== | ||
+ | |||
+ | 我们用符号 ''&'' | ||
+ | <code cpp linenums: | ||
+ | int i = 1024, i2 = 2048; //two int objects that will be used for bind | ||
+ | int &r = i; &r2 = i2; // r是i的引用,r2是i2的引用。 | ||
+ | </ | ||
+ | |||
+ | ==引用的几个注意点== | ||
+ | |||
+ | - 引用**必须指向一个对象**(必须用对象来初始化 / 必须绑定对象),**literal 不能直接引用。** | ||
+ | - 引用自身不是对象,是别名(所以不存在引用的引用)。 | ||
+ | - 引用的类型必须和被引用的对象类型一致。 | ||
+ | <WRAP center round important 100%> | ||
+ | Reference to const 是可以直接引用 literal 的。 [[https:// | ||
+ | </ | ||
+ | |||
+ | ===指针=== | ||
+ | |||
+ | 指针(// | ||
+ | 相较于引用,指针拥有以下特性: | ||
+ | * 可以被赋值和复制 | ||
+ | * 可以改变指向的内存位置 | ||
+ | * 定义的时候不需要被初始化(不用绑定已存在对象) | ||
+ | <WRAP center round important 100%> | ||
+ | 类似 build-in type,函数内的**指针默认初始化**也会导致** // | ||
+ | </ | ||
+ | Ref: | ||
+ | ==指针的定义== | ||
+ | 指针变量的定义使用 '' | ||
+ | <code cpp linenums: | ||
+ | int i = 1; | ||
+ | int *p = &i; //store the address of i in pointer p | ||
+ | </ | ||
+ | 同时定义几个指针的时候,每个指针前面必须加上 '' | ||
+ | <code cpp linenums: | ||
+ | int *p1, *p2; | ||
+ | </ | ||
+ | 需要注意的是: | ||
+ | * 引用自身并没有地址,因此不能创建一个指向引用的指针. | ||
+ | * 指针在 initialization / assignment 的时候,**类型必须与所处指向地址的数据类型相同**(指向不同的类型,或者指向指向该类型的指针,都是不行的。) | ||
+ | <code cpp> | ||
+ | double dval; | ||
+ | double *pd = &dval; // ok: initializer is the address of a double, address to address | ||
+ | double *pd2 = pd; // ok: initializer is a pointer to double, address to address | ||
+ | int *pi = pd; // error: types of pi and pd differ | ||
+ | pi = &dval; // error: a pointer pointed to double can't assign to an int pointer | ||
+ | </ | ||
+ | ==指针的形式== | ||
+ | * 第一类,指向某个对象 | ||
+ | * 第二类,指向某个位置,该位置正好在某个对象占用空间的下个地址格 | ||
+ | * 第三类,不指向任何对象,也就是空指针 | ||
+ | * 除以上三种,都是无效指针(对此类型指针操作都将导致 undefined 行为) | ||
+ | 需要注意的是,**访问**所有没有**指向明确对象的指针**都属于 undefined 行为。 | ||
+ | ==使用指针访问== | ||
+ | '' | ||
+ | <code cpp> | ||
+ | int ival = 42; | ||
+ | int *p = &ival; // 这里的 * 是定义,是定义指针的标识,不是解引用。这里的 & 是运算,是取对象地址的运算,不是定义引用的标识。 | ||
+ | cout << *p; // 这里的 * 是运算,是解引用,是将指针指向的对象中的内容取出。 | ||
+ | </ | ||
+ | <WRAP center round important 100%> | ||
+ | 再次强调,安全访问指针指向的内容的前提是**该指针必须指向一个存在的对象**(第一类指针)。换句话说,尽量使用初始化后的指针。访问指向未初始化内容的指针会带来一系列的后果,包含但不限于: | ||
+ | * run-time crash,而且非常难以 debug | ||
+ | * 指针可能指向正在被程序其他部分访问(使用)的内存,导致非法的内存空间申请带来的冲突。 | ||
+ | 如果没有可以指向的对象,尽量将指针初始化为空指针。 | ||
+ | </ | ||
+ | |||
+ | ==关于 ' | ||
+ | * 定义下的用法(// | ||
+ | * '' | ||
+ | * ''&'' | ||
+ | * 运算下的用法 | ||
+ | * 访问指针/ | ||
+ | * 取地址的时候:'' | ||
+ | 总的来说,就是在声明的时候,''&'' | ||
+ | |||
+ | ==空指针== | ||
+ | |||
+ | 如果定义指针的时候不明确指向对象,我们可以用到 '' | ||
+ | \\ | ||
+ | 为什么在C++中使用 '' | ||
+ | [[http:// | ||
+ | <WRAP center round important 100%> | ||
+ | 某些情况下给指针赋值 0 也等同于创建空指针的效果。但需要注意的是,这里的 0 只能是 literal,将值为 0 的 int 类型变量赋值给指针是非法的。 | ||
+ | </ | ||
+ | |||
+ | |||
+ | ==指针的赋值== | ||
+ | 对指针的赋值和对引用的赋值是不同的。引用一旦绑定,就不可能再进行引用对象的更改,任何对引用的操作都是对其初始对象的操作。指针则不同,指针是可以通过赋值来改变指向位置的: | ||
+ | <code cpp> | ||
+ | int i =42; | ||
+ | int *pi = 0; // | ||
+ | int *pi2 = &i; // | ||
+ | int *pi3; //bad pratice, pi3 would be uninitialized if it is not a global pointer | ||
+ | pi3 = pi2; // pi3 and pi2 point to the same address | ||
+ | pi2 = 0; // pi2 points is null pointer now. | ||
+ | </ | ||
+ | 一个通用的规则是,**赋值改变的是**赋值运算符左边算子**的内容**。这个算子是指的一个**整体**。比如: | ||
+ | <code cpp> | ||
+ | pi = &ival; // | ||
+ | </ | ||
+ | 那么如果是如下形式: | ||
+ | <code cpp> | ||
+ | *pi = 0; // | ||
+ | </ | ||
+ | 由于这里是赋值运算,那么左边的 '' | ||
+ | ==指针的其他运算== | ||
+ | * 指针在某些情况下可以用做条件判断。当指针为**空指针**的时候,条件为假,反之为真。 | ||
+ | * 指针之间可以进行比较(通过比较运算符 '' | ||
+ | 还有一类特殊类型的指针 '' | ||
+ | * 与其他指针比较 | ||
+ | * 传递给函数或者作为函数的返回值 | ||
+ | * 赋值给另外一个 void 类型的指针 | ||
+ | 这种指针的局限性在于,**不能通过 void 指针来对其指向的内容进行操作**,因为对指向对象操作的前提是必须知晓该对象的类型。 | ||
+ | ===复合类型的声明=== | ||
+ | ==定义多个变量的情况== | ||
+ | 在定义('' | ||
+ | <code cpp> | ||
+ | int* p; //bad practice, misleading | ||
+ | int *p0; //good practice to define a pointer with int type | ||
+ | int* p1, p2; //p1 is a pointer , p2 is an int variable | ||
+ | int *p3, *p4; //good way to define multiple pointers in 1 statement. | ||
+ | </ | ||
+ | <WRAP center round info 100%> | ||
+ | 在定义复合类型的时候,Type Modifier 是每个变量不可缺少的一部分。本书推荐使用 type modifier 靠近 变量名的写法。 | ||
+ | </ | ||
+ | |||
+ | ==指向指针的指针== | ||
+ | 指针本身是对象,也会拥有自身的地址。因此定义指向指针的指针也是可以的: | ||
+ | <code cpp> | ||
+ | int i = 1024; | ||
+ | int *pi = &i; | ||
+ | int **ppi = π //&pi is the address of the pointer, not the address which pointer points | ||
+ | </ | ||
+ | \\ \\ | ||
+ | {{ : | ||
+ | \\ \\ | ||
+ | |||
+ | 当然,如果需要通过 '' | ||
+ | <code cpp> | ||
+ | std::cout << **ppi << std:: | ||
+ | </ | ||
+ | |||
+ | |||
+ | ==指针的引用== | ||
+ | 引用不是对象,因此不存在指向引用的指针。但反过来则可以: | ||
+ | <code cpp> | ||
+ | int i = 0; | ||
+ | int *p; | ||
+ | int *&r = p; //reference to pointer | ||
+ | r = &i; //r is an altertive name to p, so assign the address of i to r makes p points to i. | ||
+ | *r = 0; // dereference r means dereference p, which yields i. then changes i to 0 | ||
+ | </ | ||
+ | 复合类型的混用向来比较让人困惑。书上讲了一个阅读的规则,那就是< | ||
+ | \\ | ||
+ | 比如这个例子: | ||
+ | <code cpp linenums: | ||
+ | int *&r = i; | ||
+ | </ | ||
+ | 来分析一下这个步骤: | ||
+ | - 首先找到 '' | ||
+ | - 往右看看到 ''&'' | ||
+ | - 继续往左看则是声明的引用 refer 的指针类型:'' | ||
+ | 因此这就是一个与整型指针绑定的引用的声明过程。 | ||
+ | |||
+ | |||
+ | ====Const 修饰符==== | ||
+ | |||
+ | |||
+ | '' | ||
+ | <code cpp linenum: | ||
+ | const int i = 10; //ok | ||
+ | const int j; //error, const variable must be initialized. | ||
+ | i = 20; //error, attempt to write to const object | ||
+ | </ | ||
+ | 我们也可以用表达式来作为 '' | ||
+ | <code cpp linenum: | ||
+ | const int k = getsize();// | ||
+ | </ | ||
+ | <WRAP center round tip 100%> | ||
+ | const 只对当前初始化的变量起作用,旨在限制对当前变量的修改操作。只要不涉及到更改变量的操作,const 对象与普通对象的操作是一致的。 | ||
+ | </ | ||
+ | |||
+ | ===Const 作用范围是文件以内=== | ||
+ | 由于编译的时候,'' | ||
+ | - 针对每个文件单独创建一个 const 变量。 | ||
+ | - 通过 '' | ||
+ | 第二种情况下,为了使用外部的 const 变量,我们需要定义以及声明两个步骤: | ||
+ | * 在指定的源文件(.cc / .cpp)中定义 const 变量。这里的 const 变量可以接受对象,甚至函数: | ||
+ | <code cpp > | ||
+ | /*file.cc, defines and initializes a const, that is accessible to other files*/ | ||
+ | extern const int bufSize = fcn(); // | ||
+ | </ | ||
+ | * 之后通过头文件对该变量进行声明,来达到共享的目的: | ||
+ | <code cpp> | ||
+ | /*file.h, declar the bufSize. Include the header to share variable bufSize*/ | ||
+ | extern const int bufSize; | ||
+ | </ | ||
+ | 一个简单的多文件共享全局变量的例子如下: | ||
+ | * '' | ||
+ | * '' | ||
+ | * '' | ||
+ | <code cpp> | ||
+ | / | ||
+ | extern const int bufSize = 512; | ||
+ | |||
+ | / | ||
+ | extern const int bufSize; | ||
+ | |||
+ | / | ||
+ | #include < | ||
+ | #include " | ||
+ | int main(int argc, char const *argv[]) | ||
+ | { | ||
+ | std::cout << "The size of global buffer is: "<< | ||
+ | return 0; | ||
+ | } | ||
+ | </ | ||
+ | 编译以及链接的过程: | ||
+ | <code bash> | ||
+ | #分别对 cpp 文件进行编译 | ||
+ | # | ||
+ | g++ -c extern_const_test.cpp | ||
+ | g++ -c extern_const_test_def.cpp | ||
+ | # | ||
+ | g++ -o a.out extern_const_test.o extern_const_test_def.o | ||
+ | #访问 | ||
+ | ./a.out | ||
+ | </ | ||
+ | ===Const、指针和引用=== | ||
+ | |||
+ | ==Reference to const== | ||
+ | 对 const 的引用指“reference that refers to a const type”,基于const 修饰类型的引用。这类引用确保了**不能通过该引用**修改绑定对象的内容。 | ||
+ | <code cpp> | ||
+ | const int ci = 1024; | ||
+ | const int &r1 = ci; // defines a refernece refers to a const variable | ||
+ | int &r2 = ci; // error. a plain reference can't refers to a const object | ||
+ | </ | ||
+ | 绝大多数情况下,引用的类型必须与被引用的对象一致;但有一种例外:如果被绑定的对象的类型,能够通过类型转换变得与引用的类型一致,那么这样的对象也是可以用于引用的初始化的: | ||
+ | <code cpp> | ||
+ | int i = 42; //variable i | ||
+ | const int &r1 = i; // r1 is the reference refers to a const varible, initialized by a non-const int object | ||
+ | const int &r2 = 42; // ok. initializor can be convert to const int | ||
+ | const int &r3 = r1*2; //ok. initializor can be convert to const int | ||
+ | int &r4 = r1 * 2; //error, const int initializor can't be convert to non-const int. | ||
+ | </ | ||
+ | |||
+ | 这种情况下,引用的绑定实际上分为两步: | ||
+ | - 创建一个临时的 const 对象,使用目标对象对其进行初始化; | ||
+ | - 将引用与该临时的对象绑定 | ||
+ | 类型转换将发生在第一步,也就是说临时对象的类型会与引用一致。那么实际上,我们定义的引用绑定的是一个不可更改的,经过类型转换的临时对象。该对象显然不能被 refer to non-const 的引用绑定。 | ||
+ | \\ \\ | ||
+ | 只要被绑定的对象可以转化为 const 类型,那么无论被绑定的对象是不是 const, 都不会影响 reference to const 的初始化(注:literal 也可以通过上述步骤转化为临时对象!)。下面是一个实例: | ||
+ | <code cpp> | ||
+ | int i = 42; | ||
+ | int &r1 = i; //ok, non-const bind to non-const | ||
+ | const int &r2 = i; // ok const bind to non-const | ||
+ | //*now, r1 and r2 refer to the same int variable i*// | ||
+ | r1 = 0; //ok, we can modify i through non-const reference r1 | ||
+ | r2 = 0; //error, we can't modify i through const-const r2 | ||
+ | /*and finally i is changed to 0*/ | ||
+ | </ | ||
+ | <WRAP center round tip 100%> | ||
+ | 可以将 reference 理解为门,reference to const 是一扇关闭的门,告诉你不能通过这扇门去改变屋里的环境。至于有没有其他门,这扇门保证不了。 | ||
+ | </ | ||
+ | |||
+ | Ref: | ||
+ | |||
+ | ==Const 和指针== | ||
+ | |||
+ | 指针是对象。所以 '' | ||
+ | * pointer to const:与 reference to const 类似,指不能通过该指针去修改被指向的内容(指针绑定的内容) | ||
+ | * const pointer:指该指针指向的地址无法被改变(指针本身) | ||
+ | |||
+ | ==两种情况的初始化== | ||
+ | |||
+ | 这两种组合的初始化也比较容易混淆: | ||
+ | <code cpp linenums: | ||
+ | int i = 10; | ||
+ | const int *p = &i; //define a pointer to const | ||
+ | int *const q = &i; //define a const pointer that point to i | ||
+ | </ | ||
+ | 这里我们的从右向左看的方法依然适用(C语言中还有 '' | ||
+ | \\ | ||
+ | 还有一个用法: | ||
+ | <code cpp linenums: | ||
+ | const int *const ptr = &i; // ptr is a const pointer,its value can't be changed, and the value pointed by it cannot be changed through itsself(the pointer). | ||
+ | </ | ||
+ | 同样先前说到的初始化的时候类型要匹配规则同样适用于这里。比如: | ||
+ | <code cpp linenums: | ||
+ | const double x = 3.1415; | ||
+ | double *xptr = &x;// error, type not match. | ||
+ | </ | ||
+ | |||
+ | ==Top_level 和 Low_level== | ||
+ | 根据 pointer 与 const 的两种组合方式,我们可以定义两种等级: | ||
+ | * 自身为 const 的等级为 top level,比如 const pointer | ||
+ | * 无法通过自身修改对应内容的等级为 low level, 比如 pointer to const | ||
+ | |||
+ | 示例代码如下: | ||
+ | <code cpp linenums: | ||
+ | int i = 10; | ||
+ | // const pointer, its-self cannot be changed -> this const produces a forbidden operation of manipulating the object -> top_level | ||
+ | int *const p = & | ||
+ | // pointer to const, the pointer its-self can be changed, but the value of i can't be changed by using the pointer(expression)-> | ||
+ | const int *q = & | ||
+ | </ | ||
+ | 还有一个例子: | ||
+ | <code cpp linenums: | ||
+ | const int *const ptr = &i; // | ||
+ | </ | ||
+ | 右边的 const 定义了 指针,因此是 top level, 左边的 const 定义了该指针指向的是一个不能改变的 int 值,因此属于 Low level。\\ \\ | ||
+ | 之所以提出这两个 level, 是因为绝大多数的,带 const 初始化/ | ||
+ | \\ \\ | ||
+ | 首先,拷贝 const 对象的时候,top-level 可以无视,因为拷贝并不影响 const 对象的内容: | ||
+ | <code cpp> | ||
+ | int i = 0; | ||
+ | const int ci = 42; | ||
+ | i = ci; //top-level ci is ignored. | ||
+ | </ | ||
+ | 其次,low-level 的 const 在任何时候都不能忽略。如果想要成功拷贝,那么等号两边都必须是 Low-level 的 const, | ||
+ | <code cpp> | ||
+ | / | ||
+ | int &r = ci; //error, ci is on low-level, r must at the same level. | ||
+ | </ | ||
+ | 下面是特殊情况,指向 const 且自身是 const pointer: | ||
+ | <code cpp> | ||
+ | / | ||
+ | const int *p2 = & | ||
+ | const int *const p3 = p2; | ||
+ | </ | ||
+ | 该指针左边的 const 为 low-level, 右边的 const 为 top-level。因此: | ||
+ | * p3 可以初始化、赋值 p2, 拷贝的时候 top-level 自动忽略了。 | ||
+ | * p2 可以初始化 p3。两边的 low-level 匹配。 | ||
+ | <WRAP center round help 100%> | ||
+ | 个人感觉拷贝的过程中,只要是 top-level 的 const 就可以不用管。因为 top-level 的 const 只强调对象本身的不可更改性;也就是拷贝过程中只需要考虑两边 low-level 的匹配就行。通俗点来说,如果 low-level const 是不能打开,但可以装到任意的墙上的门。那么 top-level const 就是让门自身无法被移动和摧毁的力场。这样一来,我们就有两个任务: | ||
+ | * low-level const 对象初始化 top | low-level 混搭的对象:这种情况就像是工头指定了位置让你装门,门外已经有力场保证了你的门无法被移动和摧毁。你只要保证你的门打不开就对了。 | ||
+ | * top | low-level 混搭的对象初始化 / 赋值 low-level const 对象:工头让你找一扇不能开的门随便找个地方装了。同样你只需要保证你的门打不开,至于那个门之前在不在力场里工头根本不 care。 | ||
+ | </ | ||
+ | <WRAP center round tip 100%> | ||
+ | const 语句的合法性,归根结底诠释了一个权限的概念。如果对象的赋值算是一个链条,那么 low-level const 的对象会对其下游(左边)对象有同样的限制。只要下游存在修改 low-level const 对象的风险,那么编译器就会报错。 | ||
+ | </ | ||
+ | |||
+ | |||
+ | ===Constexpr=== | ||
+ | |||
+ | 这一节有一个概念,**常量表达式**(// | ||
+ | 因此,所有非常量的表达式,或者需要在 //Run Time// 下才能确定初始值的表达式(比如函数),都不是常量表达式。比如: | ||
+ | <code cpp linenums: | ||
+ | int a = 10; // a is not const object | ||
+ | const int b = getsize(); // getsize() fincution is an uncertain initializor during the compling time. | ||
+ | const int c = 100; // const expression | ||
+ | </ | ||
+ | ==Constexpr Variables== | ||
+ | 因为在大型系统中,判断一个表达式是不是 '' | ||
+ | * 声明的变量必须是明确的 '' | ||
+ | * 该变量必须被一个常量表达式初始化。 | ||
+ | <WRAP center round info 100%> | ||
+ | //A constexpr specifier used in an **object** declaration or non-static member function (until C++14) **implies const**. A constexpr specifier used in a **function** or static data member (since C++17) declaration **implies inline**.// | ||
+ | </ | ||
+ | <WRAP center round help 100%> | ||
+ | constexpr 的优势主要在于能将初始化的过程放到编译期,而不是在 run-time 期让程序自己计算,这样可以提高客户端程序的效率。但这样带来的弊端就是定义下来的量是绝对无法更改的(除非重新编译)。因此,constexpr 才对定义的过程有要求:只有一个不可更改的,并在编译期就可以计算出结果的变量,才称之为 constexpr 变量。 | ||
+ | </ | ||
+ | |||
+ | |||
+ | ==Limitation of constexpr== | ||
+ | 因为常量表达式的初始值需要在编译期确定,因此 constexpr 的对象的初始化会有一些局限性: | ||
+ | - '' | ||
+ | - '' | ||
+ | - '' | ||
+ | ==指针与 constexpr== | ||
+ | 在定义 constexpr 指针的时候,constexpr 特指< | ||
+ | <code cpp> | ||
+ | const int *p = nullptr; // p is a pointer to const int | ||
+ | constexpr int *q = nullptr; // q is a const pointer to int | ||
+ | </ | ||
+ | <WRAP center round tip 100%> | ||
+ | 愚见:从结果上来讲,constexpr pointer 与 const pointer 对指针自身的限制都是一样的:都定义了一个无法更改指向地址的指针。但 const pointer 指向的地址中的内容是可以在 run-time 时期改变的;constexpr 只是确保初始化在编译期内进行。如果能确定指针指向的初始值全程不变,比如 const int *const p 这样的形式,完全可以用 constexpr 替代。\\ \\ | ||
+ | 从这个角度来看,实际上绝大部分情况下,如果只是对指针本身的不可更改性有要求,那么 constexpr 是可以取代 const的;如果仅仅是要求无法通过指针来修改某对象,那么就使用 const。也就是: | ||
+ | * top + low level 使用 constexpr | ||
+ | * low-level 使用 const | ||
+ | * top-level 酌情使用 | ||
+ | </ | ||
+ | '' | ||
+ | <code cpp> | ||
+ | constexpr const int *p = &i; | ||
+ | </ | ||
+ | Ref: | ||
+ | |||
+ | ====类型处理==== | ||
+ | 类型随着程序的复杂性变大会出现两个比较显著的问题: | ||
+ | * 类型名会变得越来越长,变得难懂并且容易输错 | ||
+ | * 类型的形式太复杂导致需要看完类型的内容才能明白 | ||
+ | |||
+ | ===Type Alias=== | ||
+ | |||
+ | 解决第一类问题可以用到 //Type Alias//。 总的来说,// | ||
+ | 有两种方法可以定义 //Type Alias//: | ||
+ | - '' | ||
+ | <code cpp> | ||
+ | typedef double wages; // wages is a synonym for double'' | ||
+ | typedef wages base; //base is synonym for double | ||
+ | typedef wages *p; // p is synonym for double* | ||
+ | </ | ||
+ | - C++11的新标准:'' | ||
+ | SI item; // same sa Sales_item type</ | ||
+ | ==Pointer, const and Type Aliases== | ||
+ | //Type Alias// 也可以与复合类型,'' | ||
+ | <code cpp> | ||
+ | typedef char *pstring; | ||
+ | const pstring cstr = 0; | ||
+ | // const 修饰的是经过type alias化的指针 | ||
+ | // const pstring -> const char *,cstr 是指向 char 的常量指针 | ||
+ | // 而不是指向常量 char 的指针(char const*) | ||
+ | </ | ||
+ | |||
+ | ===Auto Type Specifier=== | ||
+ | |||
+ | 在很多时候,我们想自己决定表达式的类型是非常困难的。C++11引入了一种新的类型说明符 '' | ||
+ | 和别的说明符一样,'' | ||
+ | |||
+ | ==Auto、复合类型、Const== | ||
+ | '' | ||
+ | 首先,使用引用对某类型进行初始化的时候,'' | ||
+ | <code cpp> | ||
+ | int = 0, &r = i; | ||
+ | auto a = r; // a is an int | ||
+ | </ | ||
+ | 其次,在有 const 参与的类型初始化中,'' | ||
+ | <code cpp> | ||
+ | const int ci = i, &cr = ci; | ||
+ | auto *p = &ci; // p points to a const variable, low-level | ||
+ | auto b = i;// b is an int | ||
+ | auto c = cr; // c is an int | ||
+ | auto d = &i; // d is an int* | ||
+ | </ | ||
+ | 如果需要保留 top-level const, 需要**显式的添加上** const: | ||
+ | <code cpp> | ||
+ | const int i = 0; | ||
+ | const auto a = i; | ||
+ | </ | ||
+ | 除此之外,low-level const 不会被 '' | ||
+ | <code cpp> | ||
+ | const int ci = i; | ||
+ | auto &g = ci; // g is an reference refer to const int ci | ||
+ | auto &h = 42; // error, plain reference can be bound to a literal | ||
+ | const auto &j = 42;// reference to const can be bound to literal | ||
+ | </ | ||
+ | ===Decltype Type Specifier=== | ||
+ | '' | ||
+ | decltype(f()) sum = x; //sun has whatever x returns | ||
+ | </ | ||
+ | 跟 '' | ||
+ | <code cpp> | ||
+ | const int ci = 0; &cj = ci; | ||
+ | decltype(ci) | ||
+ | decltype(cj) y = x //y is reference to const int x | ||
+ | decltype(cj) z; //error, exception; if z requires initialize, then the initialization is a must. | ||
+ | </ | ||
+ | 注意 decltype 与引用连用的时候需要初始化(因为引用需要)。与 auto 不同的是,decltype 会返回其表达式的类型,因此这里返回的是引用,所以必须初始化。 | ||
+ | ==Decltype 和 表达式== | ||
+ | decltype 的返回类型是根据表达式的表现形式来判断的。我们设定 '' | ||
+ | - 如果表达式是函数,那么返回的就是函数返回值的类型 | ||
+ | - 如果 '' | ||
+ | - 如果 '' | ||
+ | | ||
+ | | ||
+ | Refs: | ||
+ | * [[http:// | ||
+ | * [[http:// | ||
+ | * [[http:// | ||
+ | 一些例子: | ||
+ | <code cpp> | ||
+ | int i = 10; | ||
+ | int &r = i; | ||
+ | int *p = &i; | ||
+ | decltype(i) x1 = 0; //decltype ( entity )形式,返回 i 的类型int | ||
+ | decltype(r) x2 = x1;// | ||
+ | decltype(r+0) x3;// | ||
+ | decltype(*p) x4 = i;// | ||
+ | decltype((i)) x5 = i;// if the name of an object is parenthesized, | ||
+ | decltype(' | ||
+ | </ | ||
+ | <WRAP center round important 100%> | ||
+ | 赋值表达式也是一种左值,因此会返回引用。引用的类型由赋值操作符左边的变量决定。比如 decltype(i=0) 会返回int& | ||
+ | </ | ||
+ | ====Defining Our Own Data Structures==== | ||
+ | 一些注意事项: | ||
+ | * class 定义的末尾要加一个 semicolon | ||
+ | * object 的定义最好与 class 的定义分开 | ||
+ | ===Header=== | ||
+ | * 因为 class 的复用性,因此 class 的定义通常应该放到 header 中。 | ||
+ | ==使用Preprocessor解决多重包含== | ||
+ | Preprocessor 继承至 C,指一系列在编译器改变源代码之前运行的程序,比如 ''# | ||
+ | 在我们的 C++ 程序中,资源往往来自不同的头文件,某一些头文件之间可能会有互相的依赖。比如我们自定义的头文件 '' | ||
+ | \\ \\ | ||
+ | **// | ||
+ | C++ 中,某对象的声明可以有很多次,定义只能有一次。多重包含往往会导致某个对象的重复定义,这是不被允许的。 | ||
+ | \\ \\ | ||
+ | 为了解决这个问题,C++ 提供了 //Header guards// | ||
+ | <code cpp> | ||
+ | #ifndef SALES_DATA_H // | ||
+ | #define SALES_DATA_H //如果 header 是被第一次包含,那么证明该 header 未被定义过,执行下列语句 | ||
+ | #include < | ||
+ | struct Sales_data { | ||
+ | std::string bookNo; | ||
+ | unsigned units_sold = 0; | ||
+ | double revenue = 0.0; | ||
+ | }; | ||
+ | #endif | ||
+ | </ | ||
+ | 其中 ''# | ||
+ | * 如果当前程序第一次包含 '' | ||
+ | * 如果已经包含了 '' | ||
+ | 需要注意的是,Preprocessor 中的所有变量,必须是**唯一**的,并且必须**全大写**(避免与其他名字发生冲突)。 | ||
+ | <WRAP center round info 100%> | ||
+ | 所有的 Header 文件都应该使用 header guards。 | ||
+ | </ | ||