1. 基本内置类型

1.1 算术类型

算术类型分为两类: 整型 (包括字符和布尔类型在内)和 浮点型

第2章  变量和基本类型 - 图1

1.2 类型转换

当在程序中使用一种类型而其实对象应该取另一种类型时,程序会自动进行类型转换。

  1. bool b = 42; // b 为真
  2. int i = b; // i 的值为3
  3. i = 3.14; // i 的值为3
  4. double pi = i; // pi 的值为 3.0
  5. unsigned char c = -1; // 假设 char 占8bit, c 的值为255
  6. signed char c2 = 256; // 假设 char 占8bit, c2 的值是为定义的

含有无符号类型的表达式

无符号和有符号数混用带来的问题

示例1:

  1. unsigned u = 10;
  2. int i = -42;
  3. std::cout << i + i << std::endl; // 输出 -84
  4. std::cout << u + i << std::endl; // 如果 int 占 32bit,输出 4294967264

当一个算术表达式中既有无符号数又有 int 值时,那个 int 值就会转换成无符号数

  1. unsigned u1 = 42, u2 = 10;
  2. cout << u1 - u2 << endl;
  3. cout << u2 - u1 << endl;

示例2:

  1. for (int i = 10; i >= 0; --i)
  2. std::cout << i << std::endl;
  3. // 错误: 变量u永远不会小于0,循环条件一直成立
  4. for (unsigned i = 10; i >= 0; --i)
  5. std::cout << i << std::endl;

1.3 字面值常量

一个形如 42 的值被称作字面值常量 , 每个字面值常量都对应一种数据类型,字面值常量的形式和值决定了它的数据类型。

整型和浮点型字面值

整型

  1. 20 // 十进制
  2. 024 // 八进制
  3. 0x14 // 十六进制

浮点型

  1. 3.14159 3.14159E0 0. 0e0 .001

字符和字符串字面值

  1. 'a' // 字符字面值
  2. "Hello World!" // 字符串字面值

字符字面值的类型是 char ,字符串字面值是字符数组类型 const char[]

指定字面值的类型

我们可以通过下表的符号来指定字面值的类型

第2章  变量和基本类型 - 图2

示例:

  1. L'a' // 宽字符型字面值,类型是 wchar_t
  2. u8"hi!" // utf-8 字符串字面值
  3. 42ULL // 无符号整型字面值,类型是 unsigned long long
  4. 1E-3F // 单精度浮点型字面值,类型是 float
  5. 3.14159L // 扩展精度浮点类型字面值,类型是 long double

布尔字面值和指针字面值

truefalse 是布尔类型的字面值:

  1. bool test = false;

nullptr 是指针字面值。

2. 变量

变量提供一个具名的、可供程序操作的存储空间。

 2.1 变量定义

C++变量的定义要指定变量的类型。

  1. //变量定义并初始化
  2. int a = 2;
  3. //变量定义
  4. int b;
  5. //变量赋值
  6. b = 1;

在C++中变量的初始化和赋值是有区别的, int a = 2;= 运算符表示的是初始化,

而在 b=1; 中的 = 是赋值。

C++11 列表初始化

  1. int val = 1;
  2. int val = {0};
  3. int val{0};
  4. int val(0);

使用 {} 来初始化变量,称为列表初始化,这种方式初始化变量,编译器化检查初始化的变量是否符号定义的变量的类型

  1. long double pi = 3.14;
  2. int a{pi}; //错误,编译器会检查类型,无法通过编译

总结:C++变量初始化的语法形式有三种:= , () , {}

默认初始化 ,定义变量时没有初始化变量的值,则变量会被默认初始化。默认初始化的值取决于变量定义的类型。定义在函数体内的局部变量和类中的成员属性是不会被初始化的 (不同编译器的实现可能会不同), 所以不用试图使用任何方式去访问这些变量。

  1. #include <iostream>
  2. using namespace std;
  3. int init;
  4. struct A {
  5. int m;
  6. void print()
  7. {
  8. cout << m << endl;
  9. }
  10. };
  11. void test()
  12. {
  13. int un_init;
  14. cout << un_init << endl;
  15. }
  16. void test01()
  17. {
  18. A a;
  19. a.print();
  20. }
  21. int main()
  22. {
  23. test01();
  24. cout << init << endl;
  25. return 0;
  26. }

2.2 声明和定义

声明,使程序知道变量(对象)的存在

定义,负责创建于名字关联的实体

  1. extern int i; //声明i
  2. extern int i = 1; //错误,给i赋值extern失效
  3. int j; //声明并定义j

变量能且只能被定义一次,但是可以被多次声明

2.3标识符

标识符

变量命名按照规范,不要使用保留关键字。

命名规则,供参考:

  • 普通的局部变量和函数参数名使用小驼峰(第一个单词首字母小写,其他单词首字母大写), 例: userName
  • 全局变量前加 g_, 后面的按小驼峰规则 , g_userName
  • 静态变量前加 s_ , 后面按小驼峰规则, s_userName
  • 类名使用大驼峰,所有单词的首字母大写 , UserManage
  • 类属性(成员变量)前面加 m_ ,后面按小驼峰规则 , m_userName
  • 常量全部使用大写,多个单词用_ 分割, MAX_NUMBER

2.4 名字的作用域

局部变量不能和全局的变量重名,嵌套的块,内部的不要和外部的重名。重名的变量采取就近原则。

3. 复合类型

符合类型的声明语句是由一个 基本数据类型 和紧随其后的一个 声明符 列表组成。

3.1 引用

引用 就是为变量(对象)起一个别名

  1. int val = 1024;
  2. int val1 = 102;
  3. int& refVal = val; //refVal指向val
  4. refVal = val1; //refVal引用并没有改变,只是改变了refVal指向的变量val的值,val = val1
  5. int &refVal2; //错误,引用必须初始化

引用定义

  1. int i = 1024, i2 = 2048; // i和i2都是 int
  2. int &r = i; r2 = i2; // r是一个引用, 与i绑定在一起,r2是int
  3. int i3 = 1024, &ri = i3; // i3 是int,ri是一个引用,与i3绑定在一起
  4. int &r3 = i3, &r4 = i2; // r3和r4都是引用
  5. // 类型必须一致
  6. int &refVal4 = 10; // 错误: 引用类型的初始值必须是一个对象
  7. double dval = 3.14;
  8. int &refVal5 = dval; // 错误:此处引用类型的初始值必须是 int 型对象

注意:

  1. 引用必须初始化,且不能改变
  2. & 符号可以紧靠基本类型(int), 也可以紧靠变量名
  3. 引用绑定的对象类型必须和引用类型是一致的

以上说的引用都是左值引用,C++11还有右值引用

3.2 指针

指针 是 “指向” 另外一种类型的复合类型。指针和引用的不同,指针本身是一个对象,允许对指针赋值和拷贝;指针无须在定义时赋初值。

指针的定义,将声明符写成 *d 的形式,其中 d 是变量名,如果一条语句种定义类几个指针变量,每个变量前面都必须有符号 *

  1. int *p1, *p2; // ip1和ip2都是指向int型对象的指针
  2. double dp, *dp2; //dp2实质性double型对象的指针,dp是double型对象

指针使用建议:

  1. 指针定义是可以不初始化,但建议定义时初始化,如果没有想好指向哪个变量,可以初始化为空指针
  2. 操作指针时,须确定操作的不是空指针和野指针(无效指针)

获取对象的地址

指针存放某个对象的地址,要想获取该地址,需要使用 取地址符 (操作符&):

  1. int ival = 42;
  2. int *p = &ival; // p 存放变量ival的地址,或者说p是指向变量ival的指针

指针类要和所指对象类型匹配

  1. double dval;
  2. double *pd = &dval;
  3. double *pd2 = pd;
  4. int *pi = pd; // 错误
  5. pi = &dval; // 错误

引用不是对象,没有实际地址,所以不能定义指向引用的指针

指针值

指针值(即地址)应属于下列4种状态之一:

  1. 指向一个对象
  2. 指紧邻对象所占空间的下一个位置
  3. 空指针,意味着指针没有指向任何对象
  4. 无效指针,也就是上述情况之外的其他值

利用指针访问对象

如果指针指向一个对象,则允许使用 解引用符 (操作符* )来访问对象

  1. int ival = 42;
  2. int *p = &ival;
  3. cout << *p; // 由符号 * 得到指针p所指向的对象,输出 42
  4. *p = 0; // 由符号 * 得到指针p所指向的对象,即可经由p为变量ival赋值
  5. cout << *p; // 输出0

解引用操作仅使用与那些切实指向了某个对象的有效指针

空指针

空指针不指向任何对象,在操作指针之前必须确定为一个非空指针。下列几个生成空指针的方法

  1. int *p1 = nullptr; // 等价于 int *p1 = 0;
  2. int *p2 = 0; // 直接将 p2初始化为字面常量0
  3. // 需要首先 #include <cstdlib>
  4. int *p3 = NULL; // 等价于 int *p3 = 0;

赋值和指针

指针变量的赋值是指将一个地址值赋值给指针变量,从而指向一个新的对象

  1. int i = 42;
  2. int *pi = 0; // pi被初始化,但没有指向任何对象
  3. int *pi2 = &i; // pi2 被初始化,存有i的地址
  4. int *pi3; // 如果 pi3 定义于块内,则pi3的值是无法确定的
  5. pi3 = pi2; // pi3和pi2指向同一个对象
  6. pi2 = 0; // 现在pi2不执行任何对象

区分对指针指向对象的赋值

  1. *pi = 0; // ival 的值被改变,指针pi并没有改变

其他指针操作

只要指针拥有一个合法值,就能将它用在条件表达式中。如果指针的值是 0, 条件取 false,任何非0的指针对应的条件值都是 true

  1. int ival = 1024;
  2. int *pi = 0; // pi 合法,是一个空指针
  3. int *pi2 = &ival; // pi2是一个合法的指针,存放着ival的地址
  4. if (pi) // pi 的值是0,条件的值为false
  5. // ...
  6. if (pi2) // pi2 指向ival,它的值不为0, 条件的值是true
  7. // ...

对于两个类型相同的合法指针,还可以用相等操作符 (==)或不相等操作符 (!=)来比较它们,比较的结果是布尔类型。

void* 指针

指针就是一个整数,没有实际的数值大小,只是一个编号,这个编号指向的是内存中的某个地址。指针无论定义成什么基本类型,其值都是一个固定位数的整数,指针类型数据的大小取决于系统的位数,32bit的系统指针变量大小是4byte = 32 bit, 64 bit系统指针是 8 byte = 64bit。

void* 是一种特殊的指针类型,可用于存放任意对象的地址。它所存放的地址就仅仅是内存空间的一个地址,因为没有指定具体对象的类型,所以无法用 void* 指针访问所指向的地址。

  1. double obj = 3.14, *pd = &obj;
  2. // 正确: void* 能存放任意类型对象的地址
  3. void *pv = &obj; // obj 可以是任意类型的对象
  4. pv = pd; // pv 可以存放任意类型的指针

定义指定类型的指针只是为了提供操作数据时需要操作的字节数。

例如,int 型的指针,在使用指针改变指向的数据时,改变的是以该指针变量为首地址的4个字节内存,

同样对int 型指针的加或减的操作也是以4个字节为基本单位

3.3 理解复合类型的声明

  1. int i = 42;
  2. int *p; //p是int型的指针
  3. int *&r = p; //r是一个对指针p的引用
  4. r = &i; //r是一个指针引用,因此给r赋值&i就是令p指向i
  5. *r = 0; //解引用r,就是解引用指针p,将p指向的变量i的值改为0

Tip: 面对一条比较复杂的指针或引用的声明语句时,从右向左读有助于弄清楚它的真实含义。

4. const 限定符

const 用于定义一个不能改变的变量, 所以定义时就必须初始化

  1. cont int bufSize = 512; //用字面值常量初始化
  2. cont int i = get_size(); //用函数返回值初始化, 运行时初始化
  3. int j = 10;
  4. cont int k = j; //用其他变量初始化

const 定义的变量只对本文件可见,要使其他文件也可见需使用 extern

4.1 const的引用

  1. const int ci = 1024;
  2. const int &r1 = ci; //正确,引用r1和ci都是常量
  3. r1 = 42; //错误, r1 是对常量的引用
  4. int &r2 = ci; //错误,不能让一个常量引用指向一个常量对象
  1. int i = 42;
  2. cont int j = 10;
  3. const int &r1 = i; //正确,允许将 const int&绑定到一个普通int对象
  4. r1 = 10; //错误,不能通过常量引用改变i的值
  5. const int &r2 = 42; //正确
  6. const int &r3 = r1 * 2; //正确
  7. int& r4 = j; //错误
  8. int &r4 = r1 * 2; //错误

组合关系

int i cont int i
int &r
cont int &r

4.2 指针和const

int i cont int i
int *p
cont int *p

const指针

  1. int errNumb = 0;
  2. int *const curErr = &errNumb; //curErr将一直指向errNumb,不可以改变指向
  3. const double pi = 3.14;
  4. cont double *const pip = &pi; //pip是一个指向常量对象的常量指针

从右向左读

C++ Primer 5th :

常量指针: 该变量是一个指针,指针本身是一个常量,即它的指向初始化后不可以改变

另一种说法:

指针常量:该变量是一个指针,指针本身是一个常量,即它的指向初始化后不可以改变

常量指针:该指针变量指向的是常量,指针的指向可以改变,但是不能通过指针解引用改变所指向的变量的值

4. 3 顶层const

顶层const : 表示该变量(对象)本身是常量,不可以改变

底层const: 表示指向的变量(对象)是一个常量

  1. int i = 0;
  2. int *const p1 = &i; //p1是指针,p1的指向不能改变,顶层
  3. const int ci = 42; //ci是普通变量,ci的值不能改变,顶层
  4. const int *p2 = &ci; //p2是一个指针,它必须指向 const int型的数据,但是本身的指向可以改变,底层
  5. const int *const p3 = p2; //第一个底层,第二个顶层
  6. const int &r = ci; //用于声明引用的const都是底层

引用类型的变量自带顶层const 即引用一旦赋值(指向某个变量)就不可以在变化(指向另一个变量)

4.4 constexpr和常量表达式

C++11 用 constexpr 关键字声明一个变量,编译器会检查该表达式是否是一个常量表达式。

constexpr和指针

  1. const int *p1 = nullptr;
  2. constexpr int *p2 = nullptr;
  3. int *const p3 = nullptr;

p2 和p3是等价的,constexpr修饰指针变量是被定义为顶层const

5. 处理类型

为了复杂程序更加易读易写,通常会给类型取别名,或是利用C++提供的特性自动推导复杂类型。

5.1 类型别名

typedef

传统的方法是使用 typedef 关键字定义类型别名

  1. typedef double wages; //wages表示是double类型
  2. typedef wages base, *p; //base = wages = double, p = double*
  3. //数组的别名
  4. typedef int arrT[10]; //arrT是一个类型别名,他表示的类型是含有10个整数的数组
  5. using arrT = int[10]; //和上面的等价

using

C++11 提供了一种新的方式,使用 using

  1. using SI = Sales_item; //Sales_item是一个类类型, SI表示是该类的别名

这里的 using 要和 using namespace std; 中的 using 区分开。后者是表示引入命名空间,类似于java和python的导包操作

指针、常量和类型别名

  1. typedef char* pstring;
  2. const pstring cstr = 0; //char *const cstr = 0;
  3. const pstring *ps; //char **const ps;

第二行的定义不能理解成 const char *cstr = 0;

const pstringconst 是对 pstring 的修饰,而 pstring 是一个 char* 类型,因此 const petring 是指向 char的 常量指针 ,而并不是指向常量字符的指针

5.2 auto 类型说明符

C++11 auto 类型说明符可以让编译器分析表达式所属的类型。

  1. int val1 = 1, val2 = 3;
  2. auto val = val1 + val2; //编译器可以自动推出val为int类型
  3. auto i = 0, *p = &i; //正确,编译器通过字面值推出i为int,p为int*
  4. auto sz = 0; pi = 3.14; //错误,编译器无法推出类型, sz, pi类型不一致无法统一

复合类型、常量和auto

  • 当使用引用类型推导类型是,auto推导的类型是引用指向变量的实际类型
    1. int i = 0; &r = i;
    2. auto a = r; // r是int型的引用,因此a是int型
  • auto会忽略掉顶层const, 同时底层const则会保留下来
    1. const int ci = i, &cr = ci;
    2. auto b = ci; //int b = ci;
    3. auto c = cr; //int c = cr; cr是ci的别名,ci本身是一个顶层const
    4. auto d = &i; //int *d = &i;
    5. auto e = &ci; //const int *e = &ci; 对常量对象取地址是一种底层const

    如果希望推断出的auto类型是一个顶层const,需要明确指出:
    指定引用类型

auto 使用建议:

使用auto声明变量一定要做到心里有数,你知道编译器会推断出的什么样的类型

通常使用auto是对于一些类型名比较复杂的变量,使用auto写起来更方便

5.3 decltype 类型指示符

C++11 decltype 可以不执行表达式,编译器自动推断出表达式的返回值类型

  1. decltype(f()) sum = x; //sum的类型和f()的返回类型一样

通过 f() 推断出返回类型,但是并不会执行 f()

decltype 和const

decltype处理顶层 consth和引用的方式和auto有点不同,如果 decltype使用的表达式是一个变量,则decltype返回该变量的类型(包括顶层const和引用在内)

  1. const int ci = 0, &cj = ci;
  2. decltype(ci) x = 0; //const int x = 0
  3. decltype(cj) y = 0; //const int &y = 0;
  4. decltype(cj) z; //错误, const int &z; 引用必须初始化

decltype 和引用

  1. int i = 42, *p = &i, &r = i;
  2. decltype(r + 0) b; //int b;
  3. decltype(*p) c; //错误, int &c; 引用需要初始化

r 是引用 decltype(r) 是引用,但是 r + 0 是一个int型数据

解引用指针得到的是指针所指的对象,,因此 decltype(*p)int&

变量加上 () 得到的是引用类型

  1. decltype((i)) d; //错误, int& d; 引用类型需要初始化
  2. decltype(i) e; // int e;

的结果永远是引用decltype((variable))

6. 自定义数据结构

这里的自定义数据结构就是指类类型的数据,在C++中定义类的关键字有classstruct

  1. class ClassName{
  2. //属性
  3. //方法
  4. };
  5. struct ClassName{
  6. //属性
  7. //方法
  8. };

classstruct 在功能上是完全一样的,两者唯一的不同是默认的权限不同

class默认的权限是私有的(private), 而 struct 是公有的(public)

注意:c语言中的结构体是不能有方法(函数)

关于类更具体的介绍在后面的章节~~