第二章 变量和基本类型

2.1 基本内置类型

1
2
sizeof(int) = 4; sizeof(long int) = 8; sizeof(long long int) = 8;
sizeof(float) = 4; sizeof(double) = 8; sizeof(long double) = 16;

字面值常量(literal)

分多行书写字符串:C++ 允许在一条语句中自动连接多个双引号字符串(连接处无空格)

1
2
std:cout<<"wow, a really, really long string"
       "literal that spans two lines" <<std::endl;

2.2 变量

声明和定义

  • 初始化(initialize):初始化不是赋值
    • 初始化 = 创建变量 + 赋予初始值
    • 赋值 = 擦除对象的当前值 + 用新值代替
  • 变量的声明(declaration)和定义(define)
    • 声明使得名字为程序所知;定义负责创建与名字关联的实体(分配内存)。
    • extern:表示符号的定义在模块外部,但如果包含了初始值,就变成了定义
    • C和C++关于声明和定义、强符号和弱符号、变量在ELF节中的位置规定似乎不同
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      
      // a.c(或a.cpp)
      #include <stdio.h>
      int x;
      int main(){
          printf("%d\n", x);
          return 0;
      }
      
      // b.c(或b.cpp)
      int x = 3;
      
      gcc a.c b.c -o test1:编译链接并运行,正常 gcc a.cpp b.cpp -o test2:链接出错:x多重符号定义

作用域

  • 同时存在同名的全局和局部变量时,在内层作用域中可以使用::reused显式访问全局变量reused。

2.3 复合类型

复合类型的含义:比如int型是基本类型,但是引用和指针(即&、*称为类型修饰符)可以再作为声明符的一部分(类型修饰符+变量标识符),构成复合类型

  • 类型修饰符是声明符的一部分,因此int *a, b, &c;中只有a是指针,c是引用
  • 但是理解上类型修饰符可以看作是类型的一部分,比如int *&pa=a;中pa是对int*类型(指向int的指针)的引用
  • 判断类型是从右向左读,最靠近变量名的类型修饰符说明这是一个什么复合类型,其余符号和基本类型共同说明指向/引用何种(复合)类型

(左值)引用

  • 引用必须初始化。
  • 引用和它的初始值是绑定在一起的,而不是拷贝。一旦定义就不能更改绑定为其他的对象
  • 引用只能绑定到对象上,不能与字面值或某个表达式的计算结果绑定
  • 引用不是对象

指针

  • void*指针可以存放任意对象的地址。因无类型,仅操作内存空间,对所存对象无法访问(不能直接操作void*指向的对象)。
  • 指针的类型要与所指向的对象严格匹配,两个例外:
    • 可以使用指向常量的指针指向非常量
    • 可以使用基类的指针指向派生类
  • 注意不能直接给指针赋值一串地址,给指针赋值应该是另一个指针或是变量取地址
    1
    2
    3
    4
    5
    6
    7
    8
    
    int a = 1;
    int *p = &a;    // a是int型, p是int型的指针(int *)
    int *q = p;     // q也是int型的指针, 将p赋值给q
    int* *pp = &p;  // pp是指向int*类型变量的指针
    // 不能是 int *pp = &p, 即犯了直接把内存地址赋值给指针的错误
    int** *ppp = &pp; // ppp是指向int**类型的指针
    int*** &r_ppp = ppp;    // r_ppp是指向int***类型的引用
    cout<<a<<" "<<*p<<" "<<**pp<<" "<<***ppp<<" "<<***r_ppp;
    
    # 各类指针的详细介绍

指针与引用

  • 不同
    • 指针存储一个变量地址,是一个对象;而引用只是变量的一个别名
    • 引用在定义时必须要进行初始化,而指针不用
    • 指针可以在任意时候改变指向,引用在初始化时绑定对象后就无法改变绑定的对象
    • sizeof(引用)=引用类型的大小,sizeof(指针)=4或8(地址空间所占字节数)
    • 自加效果不同
    • 有多级指针,但是没有多级引用
    • 存在指向指针的指针,无引用的引用,不能定义指向引用的指针,可以定义指向指针的引用。
  • 编译器实际上是将对引用的操作翻译成对指针的操作
    • [# 简谈 C++ 中指针与引用的底层实现](# 简谈 C++ 中指针与引用的底层实现)

2.4 const限定符

初始化和const

  • const表示编译器限制该变量只能是只读的,修改变量时编译器会报错,因此const对象必须在一开始声明时进行赋值
    • 在C中,可以使用指针指向const对象,进行修改(不要这样写)
    • 在CPP中,使用非const指针指向const对象会报错(但是可以将const指针强转为普通指针进而修改,不要这样写)
  • const变量默认不能被其他文件访问(作用域在本模块中)
    • 如果非要共享const对象,不管是声明还是定义,都要加上extern关键字(因此C++中const变量从内部变量转变为extern const的外部变量,如果再初始化就变成强符号)
-constextern const
Cglobalglobalglobal
C++globallocalglobal

两个例子

引用和const

  • reference to const(对常量的引用,简称为常量引用):指向const对象的引用
    1
    2
    3
    
    const int ival = 1;    // const
    const int &r = ival; // 常量引用,指向常量的引用
    int &r2 = ival; // 错误:非常量引用绑定一个常量对象
    
  • 引用的类型必须与所引用对象的类型一致
    • 例外:在初始化常量引用时允许用任意表达式作为初始值
    • 因此,对const的引用(常量引用)可能引用一个非const的对象
      1
      2
      3
      
      int a = 1;
      const int &r = a;
      a = 2; // 可以修改a的值,但是不能通过引用r来修改值
      
    • 原理:如果不一致,则中间使用临时量进行类型转换,引用指向这个临时量
       1
       2
       3
       4
       5
       6
       7
       8
       9
      10
      11
      12
      13
      
      double dval = 3.14;
      const int &r1 = dval; // 常量引用r1的类型int与所引用对象dval的类型double不同
      /* 相当于:
      const int tmp = dval; // 临时未命名对象(临时量)
      const int &r1 = tmp; // 因此修改dval的值,不会影响r1
      */
      
      // 除了初始化常量引用时,普通引用的类型必须与所引用对象的类型一致
      int &r2 = dval; // 错误
      /* 相当于:
      int tmp = dval;
      int &r2 = tmp; // 引用绑定的是一个临时量,C++规定非法
      */
      
int变量const int常量double变量const double变量
int&普通引用×××
const int&常量引用
(可以修改int的值,但是不能通过引用来修改)
warning
(见dval

指针和const

  • pointer to const(指向常量的指针):const int i = 0; const int *p = &i;
    • 变量p的类型是指针*,指向的类型是const int
    • 指向常量的指针可以指向一个非常量对象,但是不允许通过指针修改变量的值
    • 底层 const属性
      • 底层const属性对元素赋值有影响:等号两边需要有相同的底层const资格,或者非常量转换为常量
  • const pointer(常量指针):int i = 0; int *const ptr = &i;
    • 顶层const属性:这个类型的变量是只读的
      • 对于常量指针而言,指针本身是常量(即指针固定指向某个地址)
      • 顶层const属性对指向元素的拷贝无影响
    • 变量ptr的类型是常量指针*const,指向的类型是int

constexpr和常量表达式

  • 常量表达式:值不会改变,且在编译过程中就能得到计算结果的表达式。
  • constexpr
    • 背景:有时很难判断一个初始值是否为常量表达式(因为有的const对象的值直到运行时才能知道)
    • 使用:C++11规定,将变量声明为constexpr类型以便由编译器来验证变量的值是否是一个常量的表达式,constexpr对象的值必须在编译期间确定。
      • 定义在函数体内部的变量,地址在编译器无法确定,无法用来初始化constexpr指针
      • 定义在函数体外部的变量,地址固定,可以用来初始化constexpr指针
    • constexpr指针相当于顶层const
  • constexpr表示真正的常量,const表示只读

2.5 处理类型

类型别名

1
2
3
4
5
typedef int* ptr;
int a = 1;
const ptr p_a = &a; // p_a是const ptr类型的对象,即指向int的常量指针(顶层const)
const int* p = &a; // p是指向const int的指针(底层const)
const ptr* pp_a = &p_a; // pp_a是指向const ptr的指针,即指向int的常量指针的指针(底层const)

auto类型说明符

  • 一条声明语句中只能有一个基本数据类型,但可以有复合类型(比如指针和引用)
  • 复合类型自动推导
    • auto会忽略引用类型:
      • int i = 0, &r = i; auto a = r; 推断a的类型是int而非int&
      • 用 auto 定义引用时,必须用&指明要定义的是引用
    • auto会忽略顶层const,但保留底层const:
      1
      2
      3
      4
      5
      6
      
      const int i = 1;
      const int* pi = &i; // 底层const
      auto a = *pi; // *pi类型是const int,推断a的类型是int,auto忽略顶层const
      const auto b = *pi; // 如果希望是顶层const需要自己加const
      auto p = pi; // 推断p的类型是const int*,auto保留底层const
      // *p = 2; // 报错
      

decltype类型指示符

  • 背景:希望获得表达式的类型但是不需要值(编译器分析类型但是不求值)
  • 使用
    • 不会忽略顶层const和引用
    • 当获得的类型是引用时,必须进行初始化
    • 如果表达式不是一个变量,返回表达式结果对应的类型,比如b
    • 一些表达式比如【指针解引用、变量加括号、赋值操作】的结果均为引用类型
    1
    2
    3
    4
    5
    6
    
    int a = 1, &r = a, *p = &a, b = 2;
    decltype(r) r_a = a;  // r_a是int&
    decltype(r+0) b = a;  // b是int型
    decltype(*r) r_a2 = a; // r_a2是int&
    decltype((a)) r_a3 = a; // r_a3是int&
    decltype(a = b) r_a4 = a; //r_a4是int&
    

2.6 自定义数据结构

struct

1
2
3
4
5
6
7
// 复杂写法
struct student {};
struct student xiaoli;

// 简单写法:使用typedef取别名,不用写struct student,而是直接写stu
typedef struct student {} stu;
stu xiaozhang;

编写自己的[[ch01-开始#头文件|头文件]]

其他

各种“符”

  • 声明符(declarator):类型修饰符(可有可无)+变量名(也称标识符)
  • 类型修饰符:*,&
  • 标识符(identifier):变量名
  • 类型说明符(type specifier):int,char,void等
  • 数据类型修饰符:unsigned等
  • 类型限定符(qualifier):const,static等
  • 访问限定符:public, private, protected

const和指针、引用的组合

1
2
int a = 0;
const int ca = 1;
  • 常量指针(const pointer):int *const p = &a,是顶层const
  • 指向常量的指针(pointer to const):const int *p = &a;*,是底层const
  • 对常量的引用(reference to const):const int &r = a;
  • 常量引用(const reference):不存在