C++Primer | Part 1: C++基础

  • 编程语言的基本语法特征:内置类型、变量、表达式、语句、控制结构、函数
  • 编程语言对其基本语法特征的补充:允许自定义数据类型、提供库函数
  • C++是一种静态数据类型语言:编译时进行类型检查

第2章 变量和基本类型

(1) 类型转换:给unsigned或signed类型赋一个超出其表示范围的值
  • 赋给无符号类型一个超出它表示范围的值,结果是初始值对无符号类型表示数值总数取模后的余数。
  • 赋给带符号类型一个超出它表示范围的值,结果是未定义的(undefined)。
// 假设char占8比特,则表示范围是0~255,可以表示的数值总数为256
unsigned char c1 = -1; // c1值为-1%256 = 255
signed char c2 = 256; // c2值为未定义的
(2) 类型转换:含有unsigned类型的表达式
  • 一个算术表达式中同时含有int类型和无符号类型,int类型将转换为无符号类型:
    (负数转换为无符号类型:该负数加上无符号类型的模)
unsigned u = 10;
int i  = -42; // 假设int占32位
cout << u + i; //结果为 10 + 2^32 + (-42) = 10 + 4294967296 - 42 = 4294967264
// 32位int含有1个符号位,表示的最大数为2^31-1,而32位unsigned int表示的最大数为2^32-1
  • 从无符号数中减去一个数,结果总为非负值:
unsigned u1 = 42, u2 = 10;
cout << u1 - u2 << endl; // 32
cout << u2 - u1 << endl; // (10 - 42) % 2^32 = 4294967264
  • 无符号数作为循环变量递减,可能导致死循环:
for(unsigned u = 10; u >= 0; --u) // u不会小于0,循环条件一直成立
  cout << u;
(3) 字面值
  • 编译器在每个字符串的结尾添加一个空字符'\0',字符串的实际长度比它的内容多1。
  • 如果2个字符串字面值位置紧邻,仅由空格、缩进、换行符分隔,则它们实际上是一个整体:
cout<<"Primer "
      "C++ "
      "5th edition"<
  • 通过给字面值添加前缀、后缀,可以指定其类型,如42ULL类型为unsigned long long
(4) 变量的初始化 ≠ 赋值
  • 初始化:创建变量时,赋予其一个初始值。
  • 赋值:把对象的当前值擦除,以一个新值来替代。
(5) 列表初始化
  • 用花括号初始化变量
  • 4种初始化变量的形式:
int a = 0;
int a = {0};
int a{0};
int a(0);
(6) 默认初始化
  • 定义变量时没有指定初值
  • 内置类型的变量未被显式初始化:
    • 定义于任何函数之外的变量被初始化为0;
    • 定义于函数体内部的变量将不被初始化,其值为未定义的(undefined),以拷贝等方式访问它将产生错误。
  • 每个类各自决定其初始化对象的方式。
  • 定义于函数体内的内置类型变量没有显式初始化,其值未定义;类的对象没有显式初始化,其值由类决定。
(7) 变量的声明 ≠ 定义
  • C++支持分离式编译:将程序分割成若干个文件,每个文件可被独立编译。
  • 一条声明语句:一个基本数据类型 + 一个声明符列表。
  • 声明(declaration):使得名字为程序所知。一个文件若想使用其它文件定义的名字,则必须包含该名字的声明。
  • 定义(definition):创建与名字关联的实体。
  • 声明和定义都包含变量类型和名称;定义还申请存储空间,可能为变量赋一个初始值。
  • 声明而非定义:添加关键字extern,并且不能显式初始化变量。
  • 若既有关键字extern,又显式初始化变量,则还是定义,不是声明。
extern int i; //声明而非定义
int j; //声明并定义
extern int k = 1; //定义
  • 变量只能被定义一次,但可以多次声明
  • 在多个文件中使用同一个变量:只能在某一个文件中定义一次(不可重复定义),在其它文件中声明。
(8) 名字的作用域
  • 名字的有效区域始于名字的声明语句,以声明语句所在的作用域末端为结束。
  • 全局作用域 / 块作用域
  • 嵌套的作用域:允许在内层作用域中重新定义外层作用域已有的名字,在内层作用域中默认使用内层作用域的变量。
  • 可以用作用域操作符::显式访问全局变量:::num,此时作用域操作符左侧为空,向全局作用域发出请求获取名字为num的变量。
(9) 引用(reference)
  • 引用为对象起了另外一个名字。
  • 定义引用类型:将声明符写成&d的形式,d是声明的变量名。
int i = 1;
int &ref1 = i; // ref1与i绑定
  • 定义引用时,程序把引用和它的初始值绑定,而不是将初始值拷贝给引用。
  • 无法重新令引用绑定到另外一个对象,引用必须初始化
  • 引用并非对象,而是为对象起的别名
  • 对引用进行的所有操作,实际上是在与之绑定的对象上进行的。
  • 以引用作为初始值,实际上是以与引用绑定的对象作为初始值。
int &ref2 = ref1; //  ref2与i绑定
ref2 = 2; // 改变i的值
int j = ref2; // j被初始化为i的值
  • 因为引用本身不是一个对象,所以不能定义引用的引用。
  • 在一条语句中定义多个引用,每个引用标识符都必须以&开头:
int &a = i, &b = i; // a,b都是引用
int &c = i, d = i; // c是引用,d是int
  • 引用的类型要与绑定的对象的类型匹配(有2种例外情况)。
  • 引用只能绑定在对象上,不能与字面值或某个表达式的计算结果绑定。
int &ref = 10; // 错误,10不是对象
double i = 1;
int &ref = i; // 错误,类型不匹配
(10) 指针(pointer)
  • 指针相较于引用的不同之处:
    • 指针本身就是一个对象,允许对指针赋值和拷贝,在指针的声明周期内可以先后指向几个不同的对象。
    • 指针无须在定义时赋初值。
  • 定义指针:将声明符写成*d的形式,其中d为变量名。指针的类型实际上是它指向的对象的类型。
  • 在一条语句中定义多个指针变量,每个变量前都必须有符号*,如:int *p1, *p2;
  • 指针存放某个变量的地址;可以用取地址符&获取变量的地址:
int val = 42;
int *p = &val; // p存放val的地址,p是指向val的指针
  • 引用不是对象,没有实际的地址,所以不能定义指向引用的指针。
  • 指针的类型应与它指向的对象严格匹配(有两种例外情况)。
  • 指针的值有四种可能的情况:
    1. 指向一个对象
    2. 指向紧邻对象所在空间的下一个位置(访问该指针的对象的后果无法预计)
    3. 空指针(访问该指针的对象的后果无法预计)
    4. 无效指针 (访问无效指针的值将引发错误;编译器不负责检查此类错误)
  • 如果指针p指向一个对象,则可以使用解引用符*来访问该对象:*p
*p = 10;
cout << *p;
  • 解引用符只适用于确实指向某一对象的有效指针。
  • 空指针不指向任何一个对象。试图使用指针前,可以在代码中判断其是否非空。
  • 获得空指针的3种方法:
int *p = nullptr; // 比较推荐的方法
int *p = 0; // 字面值0,不可以是恰好为0的int型变量
int *p = NULL; // NULL值为0
  • 直接把int型变量赋值给指针是错误的,即使int型变量的值恰好为0也不行:
int zero = 0;
p = zero; // 错误,不能将int变量直接赋值给指针p
  • 对于一条赋值语句,它改变的到底是指针的值还是指针所指对象的值
    • p = &val 左侧是指针p,因而改变的是指针本身的值,现在P指向val
    • *p = 0 左侧是指针指向的对象*p,因而改变的是该对象的值。
  • void*指针可以存放任意对象的地址。
    • 不能直接操作void*所指的对象。可以拿它与别的对象比较、作为函数的输入或输出、赋给另外一个void*指针。
(11) 指针和引用的区别
  • 指针是变量,占用内存空间,存储另外一个变量的地址,占4字节或8字节;引用本身不是变量,而是另外一个变量的别名,与该变量绑定在一起,不占用内存空间。
  • 指针可以为空,无须在定义时赋初值,允许对指针赋值和拷贝,指针可以先后指向几个不同的变量;引用必须在定义时初始化,且无法重新绑定到其它变量。
  • 指针可以是多级的;不能定义引用的引用。
(12) 复合类型的声明
  • 在同一条定义语句中,基本数据类型只有一个,声明符的形式可以不同(采用不同的类型修饰符)。因此,一条定义语句可以定义出不同类型的变量:int i = 1024, *p = &i, &r = i;
  • int* p1, p2;中,*仅仅修饰p1,对p2不产生任何影响。
  • 多级指针:**表示指向指针的指针
int i = 1024;
int *pi = &i;
int **ppi = π
cout << *pi; // 1024
cout << *ppi; // 指针ppi存放的内容,即指针pi的地址
cout << **ppi; // 1024
  • 对指针的引用
int i = 42;
int *p;
int *&r = p; // r是对指针p的引用 

r = &i; // 作用等同于p = &i,令指针p指向了i
*r = 0; // 作用等同于*p = 0,令i的值变为0
  • 对于int *&r = p;,对其声明符从右向左分析,离r最近的符号&r的类型有最直接的影响,故r是一个引用。左边的*说明r是一个对指针的引用。而基本数据类型int说明,r引用的是一个int指针。
(13) const限定符
  • 在定义对象时,用const加以修饰,使其成为一个常量。此后,任何对它的赋值将会引发错误。const对象一旦创建后其值就不能改变,因此它必须被初始化。const int bufSize = 512;

  • 用一个对象初始化另一个对象,则它们是不是const都无关紧要。

  • 默认情况下,const对象被设定为仅在文件内有效,多个文件中出现同名的const对象,其实等同于在不同文件中分别定义了独立的变量。如果希望只在一个文件中定义const对象、在多个文件中声明,则对于该对象,不管是定义还是声明,都添加关键字extern

    extern const int bufSize = fcn(); // 一个文件中的定义
    extern const int bufSize; // 其它文件中的声明
    
  • 对常量的引用

    • 不能用作修改与其绑定的对象(即该常量)。
    • 引用和其对应的对象都应当是引用。不能让非常量引用指向一个常量对象。
      const int i = 1024;
      const int &r1 = i; // 正确
      int  &r2 = i; // 错误
      
    • (引用类型与其对象类型一致的2种例外之1)初始化常量引用时,允许使用任意表达式作初始值,只要该表达式的结果能转换成引用的类型即可。允许为一个常量引用绑定非常量的对象、字面值、一般表达式。
      int i = 42;
      const int &r1 = i;
      const int &r2 = 42;
      const int &r3 = r1 * 2;
      
    • 当一个常量引用被绑定到另外一种类型的对象上时,编译器实际上让该引用绑定了一个与该引用同类型的临时量。
      // 原本的代码:
      double i = 3.14;
      const int &r = i;
      // 编译器改变后的代码:
      const int temp = i;
      const int &r = temp;
      
    • const的引用可能引用一个非常量的对象。虽然不可以通过该常量引用修改该对象,但是可以通过其它方式修改该对象。
  • 指向常量的指针

    • 不能用于改变其所指对象的值。
    • 指向常量对象的指针,必须是定义时有const修饰的指针(所谓“指向常量的指针”),而非普通指针;所谓“指向常量的指针”,不一定要指向一个常量对象。
    • 指向常量的指针或引用,是其自作多情,它们觉得自己指向了常量,因而自觉地不去改变所指对象的值。
  • const指针

    • 常量指针:把指针定义为常量。
    • 常量指针必须初始化。一旦初始化完成,其值(存放在指针中的那个地址)就不能再改变了。
    • *放在const关键字之前:int *const p = &i;指向int对象的常量指针。这里,可以通过该指针修改i的值。
    • const int *const p = &j;指向int型常量对象的常量指针。
    • 顶层const && 底层const:
      • 顶层const:指针本身是一个常量;或者广义上地,任意类型的一个对象是一个常量。
      • 底层const:指针指向的对象是一个常量。
      • 对于const int *const p = &j;,靠右的是顶层const,靠左的是底层const。
      • 进行对象拷贝时,顶层const没有什么影响;而拷入和拷出的对象应具有相同的底层const资格,或两个对象的数据类型能够相互转换。(非常量可以转换成常量,反之则不行)
  • C++11允许将变量声明为constexpr类型,编译器将验证变量的值是否是一个常量表达式。该变量是一个常量,且由常量表达式初始化。

(14) 类型别名
  • 某种类型的同义词
  • 方法一:typedef
    • typedef 类型原名 类型别名;
    • 可以采用类型修饰符:typedef int *p;pint *的别名。
    • 可以在一个语句中定义多个别名:typedef int a, *p; 其中aint的别名,pint *的别名。
  • 方法二:别名声明(新标准)
    • using SI = Sales_item;SISales_item的别名。
  • 指针、常量和类型别名:
    typedef char *pstring;
    const pstring cstr = 0; // cstr是指向char的常量指针,而非指向常量字符的指针(易混淆)
    const pstring *ps; // ps是二级指针,指向一个指向char的常量指针
    
    
(15) auto类型说明符(C++11)
  • 编译器通过auto类型变量的初始值来推算其类型。(auto类型变量必须有初始值。)
  • auto可以在一条语句中声明多个变量。不过其初始基本数据类型都必须一样。
  • 当引用被用作初始值时,编译器以引用对象的类型作为auto的类型。
  • auto一般会忽略掉顶层const,而底层const一般会保留下来。
  • 如果希望推断出一个顶层const,需要明确指出。
(16) decltype类型指示符(C++11)
  • decltype:选择并返回操作数的数据类型。(注:这个词源自“Declared Type”。)
  • 编译器分析表达式并得到它的类型,不实际计算表达式的值。
  • 例如:decltype(f()) sum = x;
  • 如果decltype使用的表达式是一个变量,则decltype返回该变量的类型(包括顶层const和引用)。
  • 如果decltype使用的是一个不加括号的变量,得到的结果就是该变量的类型;若给变量加上了一层或多层括号,编译器会把它当做一个表达式,所以这样的decltype就会得到引用类型。
  • decltype((var))的类型永远是引用;decltype(var)只有在var本身是引用类型时才是引用类型。
(17) 头文件保护符
  • 预处理变量有两种状态:已定义和未定义。
  • #define指令把一个名字设定为预处理变量。
  • #ifdef当且仅当变量已定义时为真;#ifndef当且仅当变量未定义时为真。
  • 上述检查结果一旦为真,则执行后续操作直到遇到#endif指令为止。

第3章 字符串、向量和数组

第4章 表达式

第5章 语句

第6章 函数

第7章 类

你可能感兴趣的:(C++Primer | Part 1: C++基础)