开始


IO操作时,输入和输出数据会保存在缓冲区中,可以显示刷新缓冲区,读取cin会刷新cout,程序异常终止也会刷新coutendl可以结束当前行,并将缓冲区内容刷新到设备中。

while(std::cin >> value)

循环执行过程中,不断检测流的状态,遇到错误或者文件结束符才会结束,Linux中的文件结束符是Ctrl+D。

变量和基本类型


类型 最小宽度
bool 未定义
char 8位
wchar_t 16位
char16_t 16位
char32_t 32位
short 16位
int 16位
long 32位
long long 64位
float 6位有效数字
double 10位有效数字
long double 10位有效数字

char16_tchar32_t用于Unicode字符集,wchar_t可以存放最大的扩展字符集。

类型的选择规则:

  • 知道非负,使用无符号类型
  • 整数运算用intshort太短,long一般和int一样
  • 算数表达式不要用boolchar
  • 浮点运算用duoble,比float精度高速度快
unsigned char c = -1;

c的范围是0~255,给他赋了这个范围外的值时,实际值为对256取mod,即-1 % 256=255。

无符号数与有符号数相加时,有符号数会转换成无符号数,严禁混用。

八进制数以0开头。

数字-42的字面值是不包括-的,也就是说十进制数字面值不会为负。字面值的类型是可以指定的:

L'a'    // wchar_t类型
u8"hi!" // utf-8字符串
42ULL   // unsigned lon long类型
1E-3F   // float类型
3.14159L    // long double类型

字面值在选择所占空间时,尽量找最小的,存不下再找大的,如后缀有U,会在unsigned intunsigned longunsigned long long中选择最小的。

字符串太长可以这样分开书写:

std::cout << "a"
             "b"
             "c";

nullptr是指针字面值。

初始化几种形式:

  • int a = 0;
  • int a(0);
  • int a = {0};
  • int a{0};

使用大括号的初始化方式由C++11支持,用他初始化内置变量时,如果存在丢失信息风险,就会报错,如用double初始化int

默认初始化的值通常由变量类型决定。定义在函数体内部的内置变量不会被初始化,值未定义。尽量给每一个内置类型初始化。

声明与定义是有区别的,声明只是让程序知道存在这个名字,定义会将名字与值关联。仅仅需要声明使用extern

extern int i;   // 仅声明
int j;  // 声明加定义

当需要在多个文件使用同一个变量,只能在一处定义,可以在多处声明。

当局部变量覆盖全局变量时,仍要访问全局变量可以用::变量名访问。不过尽量不要与全局变量重名。

int global = 1;
int main(void) {
    int global = 2;
    std::cout << ::global << global;    // 12
    return 0;
}

引用类型定义时必须用对象初始化,而且引用的类型要一致,引用会与对象一直绑定在一起。引用只是一个别名,不是对象,所以不能定义引用的引用, 不过可以用引用初始化引用,即都绑定到同一个对象。

int a = 1;
int &b = a;
int &c = b;

bc都与a绑定,他们的值会同时改变。

引用的定义:

int a = 1;
int &b = a, c = a, &d = a;

bd是引用,cint引用不能用字面值和表达式初始化,而且引用要与被引用类型完全匹配

指针与引用不同,他是对象,可以进行赋值和拷贝。指针与指向的变量类型也应该完全匹配。

int i;
int *p = &i;

指针的值有4种:

  1. 指向一个对象
  2. 指向对象所占空间的下一个位置
  3. 空指针
  4. 无效指针

定义空指针的方法:

int *p1 = nullptr;
int *p2 = 0;
int *p3 = NULL;

这三种作用相同,尽量使用第一种。

void*这种指针可以指向任意对象的地址,但是不能用*解引用,只能进行比较,函数输入输出或者赋值给另一个void*

int i = 4;
int *p;
int *&r = p;    // 从右往左读

r = &i;
*r = 0;

r是一个引用,引用的是一个指针,指针是int指针。

const对象必须初始化,可以通过函数的返回值初始化。

// const.cc
extern const int buf = fcn();
// const .h
extern const int buf;

要在多个文件间共享变量,需要在定义前加extern

可以为const变量创建引用:

const int c = 707;
const int &rc = c;

可以用const和非const变量初始化const引用,但不能用非const变量初始化const引用。 在这一点上,指针与引用类似。

指针是一个变量,所以存在常量指针,即始终指向某个地址:

int i = 707;
int *const a = &i;
const int *const b = &i;

为了区分两个const分别定义,顶层const表示任何对象是常量,底层const表示指针或引用指向的对象是常量。

int i = 0;
int *const p1 = &i; // 顶层
const int ci = 707; // 顶层
const int *p2 = &ci;    // 底层
const int *const p3 = p2;   // 左边是底层,右边是顶层

/**
 * 拷贝时,顶层不受影响,const可以拷贝给非const
 */
i = ci;
p2 = p3;

/**
 * 而底层必须相同才能拷贝,这两种都不能,不管顶层是什么
 */
int *p = p3;
int *const p4 = p3;

常量表达式指值不改变,并且在编译时就能定下来的表达式。比如const int sz = get_size()就不是。 通过定义constexpr变量可以让编译器判断表达式是否是常量。改为constexpr int sz = get_size(), 当get_size是一个constexpr函数就不会报错。constexpr指针必须初始化为nullptr或 指向存储在固定地址中的对象。

特别要注意的是constexpr int *p = nullptr是定义了一个常量指针,对象被定义为顶层constconstexpr const int *p = nullptr表示指向整数常量的常量指针。

类型别名有助于程序理解,有两种定义方式:

  1. typedef double wages;
  2. using SI = SalesItem;

在声明指针类型别名时要注意:

typedef char *pstring;
const pstring cstr = 0;

pstring是指针类型,const表示的是常量指针,而不能这样替换理解const char *cstr = 0

auto一般会忽略顶层const,保留底层const

const int ci = 1, &cr = ci;
auto a = ci;    // 整型
auto b = cr;    // 整型,顶层const,因为cr是ci的别名
auto c = &ci;   // 指向整数常量的指针
const auto d = ci;  // 明确顶层const

// 同时用引用和指针时,类型要相同
auto &m = ci, *p = &ci;

decltype可以用来计算表达式类型

decltype(f()) sum = x;  // sum的类型是函数f的返回值类型

decltype能够得到变量的顶层const和引用

const int ci = 0, &cj = ci;
decltype(cj) y = ci; // y的类型是const int &
decltype(cj) z; // z是引用必须初始化

这里的引用cj就是引用,不是作为别名处理的。

int i = 707, *p = &i, &r = i;
decltype(r + 0) b;  // int
decltype(* p) c;  // int&

r是引用,经过计算后变为int类型。*p是解引用,c就是引用类型。 当变量有双括号时都是引用类型,因为变量是一个左值,加括号代表是一个表达式,可以作为赋值的左值。

decltype((i)) d;    // 引用类型

创建自定义类型时,新标准规定,可以为数据成员提供初始值。

struct S {
    int a = 0;
    double b = 1.0;
};  // 记得结尾加分号

类的定义、const变量、constexpr变量通常放在头文件中,头文件还可包含其他头文件,头文件应该与类的命名相同。 在使用#include时,预处理过程中会用头文件的内容替换#include。 为了避免重复包含,可以用头文件保护符:#defineifdefifndefendif

#ifndef PERSON_H
#define PERSON_H
#include <string>
struct Person {
    std::string name;
    unsigned age = 0;
};
#endif

即使头文件没有被包含,也应该加上保护符,说不定哪天就被使用了。

字符串、向量和数组


作用域操作符告诉编译器,从操作符左侧的作用域寻找右侧的名字,如:std::cin就是从命名空间std中寻找cin

头文件中不应包含using,因为头文件的内容会被引用时拷贝到另一个文件,多个引入可能会导致命名冲突。

string初始化方法:

string s1;
string s2(s1);
string s3("value");  // 直接初始化,直接在创建时设置了值
string s3 = "value"; // 拷贝初始化,使用了=的都是拷贝初始化,将右值对象拷贝到左边
string s4(n, 'c');   // n个字符c

string.size()返回的类型是string::size_type

string在进行加法操作时,加号两边有一个是string类型即可,另一个可以是字面值。

访问string的所以字符时,使用for(auto i : string),而其他情况使用下标取值。

vector初始化方法:

vector<T> v1;
vector<T> v2(v1);
vector<T> v2 = v1;
vector<T> v3(n, val);   // n个val
vector<T> v4(n);    // n个T类型的初始化对象
vector<T> v5{a, b, c...};
vector<T> v5 = {a, b, c...};

vector<int> vi = 10;    // 错误,必须用直接初始化

// 易混淆的
vector<int> v1(10);     // 10个0
vector<int> v2{10};     // 1个10
vector<int> v3(10, 1);  // 10个1
vector<int> v4{10, 1};  // 1个10,1个1

如果向vector中添加删除元素,则不能使用for(auto i : v)

vector下标不能用来添加元素,必须用push_back

迭代器的类型有:

// 可读写
vector<int>::iterator it;
string::iterator it2;

// 只读
vector<int>::const_iterator it3;
string::const_iterator it4;

箭头运算符a->b相当于(*a).b

凡是使用了迭代器就不要添加删除元素,这会使迭代器失效。

两个迭代器相减得到一个difference_type类型的带符号整数。

声明数组的大小时,变量的类型必须是constexpr

constexpr unsigned sz = 707;
int *parr[sz];  // sz必须是constexpr

字符数组的初始化:

char a1[] = {'a', 'b', 'c'};
char a2[] = "c++";  // 最后会自动存储一个'\0'

复杂的数组声明:

int *ptrs[10];              // 10个整型指针的数组
int &refs[10] = /* ? */     // 错误!不能定义引用数组
int (*Parray)[10] = &arr;   // 指向一个含有10个整数的数组
int (&arrRef)[10] = arr;    // 引用一个含有10个整数的数组
int *(&arry)[10] = ptrs;    // 从内向外阅读,一个引用,引用了一个含有10个整型指针的数组

数组名通常就相当于首元素指针:

string nums[] = {"one", "two", "three"};
string *p = &nums[0];   // 等价于 p = nums
auto a(nums);   // a是string*类型,相当于a(&nums[0])
decltype(nums) b = {"one", "two", "three"}; // 这里的nums不是指针,而是数组,注意decltype

// 指针也是迭代器
string *b = begin(nums);
string *e = end(nums);  // 指向数组下一个位置
auto n = end(nums) - begin(nums);   // 得到ptrdiff_t类型的变量

string last = *(nums + 2);  // 等价于nums[2]

指针可以和下标相换操作,是等价的;与stringvector不同的是,数组的下标可以是负值。

C风格字符串处理函数位于cstring.h中:strlenstrcmpstrcatstrcpy

为了与旧的C风格字符串代码衔接,可以将string对象转换为字符串:

string s("hello");
char *str = s;  // 不能用string初始化char*
const char *str = s.c_str();    // c_str返回一个字符串

可以用数组初始化vector,只需提供首元素和尾后元素的地址:vector<int> v(begin(arr), end(arr))

多维数组初始化为0:int arr[10][20][30] = {0}

多维数组使用范围for时,除了最内层可以不使用引用,其他层都需要,为了防止被转换为指针。

for (auto &row : ia)    // 引用去掉row会被转成指针,内层循环会报错
    for (auto &col : row)
        ;

多维数组名和指针:

int ia[3][4];
int (*p)[4] = ia;   // 指向含有四个整数的数组
p = &ia[2]; // 指向ia第三行

使用auto可以使多维数组指针声明简化,在循环数组时,使用beginend可以更加简洁。 可以用类型别名简化多维数组指针声明using int_arr = int[4]

表达式


求值顺序与优先级和结合律无关,如f() + g() * h() - j()的表达式,函数的调用顺序是不确定的, 如果这几个函数不相关则不会有问题,要是有共享数据可能会出错。

运算符对bool类型进行操作时,将被提升为int类型。

bool b = true;
bool b2 = -b;   // true