C++
开始
IO操作时,输入和输出数据会保存在缓冲区中,可以显示刷新缓冲区,读取cin
会刷新cout
,程序异常终止也会刷新cout
。
endl
可以结束当前行,并将缓冲区内容刷新到设备中。
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_t
和char32_t
用于Unicode字符集,wchar_t
可以存放最大的扩展字符集。
类型的选择规则:
- 知道非负,使用无符号类型
- 整数运算用
int
,short
太短,long
一般和int
一样 - 算数表达式不要用
bool
和char
- 浮点运算用
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 int
、unsigned long
、
unsigned 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;
b
和c
都与a
绑定,他们的值会同时改变。
引用的定义:
int a = 1;
int &b = a, c = a, &d = a;
b
和d
是引用,c
是int
。
引用不能用字面值和表达式初始化,而且引用要与被引用类型完全匹配
指针与引用不同,他是对象,可以进行赋值和拷贝。指针与指向的变量类型也应该完全匹配。
int i;
int *p = &i;
指针的值有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
是定义了一个常量指针,对象被定义为顶层const
。
constexpr const int *p = nullptr
表示指向整数常量的常量指针。
类型别名有助于程序理解,有两种定义方式:
typedef double wages;
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
。
为了避免重复包含,可以用头文件保护符:#define
、ifdef
、ifndef
、endif
。
#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]
指针可以和下标相换操作,是等价的;与string
和vector
不同的是,数组的下标可以是负值。
C风格字符串处理函数位于cstring.h
中:strlen
、strcmp
、strcat
和strcpy
。
为了与旧的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
可以使多维数组指针声明简化,在循环数组时,使用begin
和end
可以更加简洁。
可以用类型别名简化多维数组指针声明using int_arr = int[4]
。
表达式
求值顺序与优先级和结合律无关,如f() + g() * h() - j()
的表达式,函数的调用顺序是不确定的,
如果这几个函数不相关则不会有问题,要是有共享数据可能会出错。
运算符对bool
类型进行操作时,将被提升为int
类型。
bool b = true;
bool b2 = -b; // true