Sec1 Begin
Sec2 变量和基本类型
2.1 基本内置类型
- 符号数:
- 无符号数和负数相加。会先将负数转化为无符号数再进行相加
- 无符号数相减,无论如何都不可能为负数,若结果为负数,则会直接按照比特位来读取无符号数
- 混用无符号和有符号型,比如相乘,得看机器上int所占位数而定
- 转义序列(escape sequence)
- \ 后面跟3个字符。都是转义的内容
- 例子:\1234,表示转义字符\123和字符4
- \ 后面跟3个字符。都是转义的内容
- 0开头的数为8进制数。以0X开头的为16进制数
- 表示浮点数:
- 反例:
1024f
。正确应该为1024.f
- 反例:
2.2 变量
-
初始化
-
列表初始化
long double ld = 3.14159 int a{ld}, b = {ld}; // 报错,存在丢失信息风险 int a(ld), b = ld; // 正确,但丢失部分值
-
默认初始化
没有指定初值,默认初始化- 定义于任何函数之外的变量被初始化为0
- 定义在函数体内部的内置类型变量将不被初始化 uninitialized
- 每个类各自决定初始化对象的方式
是否不经过初始化就定义对象也由类自己决定 - 建议初始化每一个内置类型的变量
-
声明和定义:
变量声明规定了变量的类型和名字,定义也一样,不同点在于,定义还申请存储空间,也可能会给变量赋一个初始值
任何包含显示初始化的声明,即成为定义extern int i; // 声明i,并非定义i int j; // 声明并定义j extern double pi = 3.14 // 定义
函数内部,如果试图初始化一个由extern关键字标记的变量会引发错误
变量能且只能被定义一次,但可以被多次声明 -
作用域
scope
2.3 复合类型 (Compound Type)
-
引用:reference
-
lvalue reference
一般指:左值引用 &一定要初始化!定义引用时,程序把引用与他的初始值bind绑定在一起,而不是将初始值拷贝给引用。
一旦初始化完成,引用将和它的初始值对象一直绑定在一起。因为无法令引用重新绑定另外一个对象,因此引用必须初始化。
(引用只是为一个已经存在的变量起一个别名)
-
-
指针 pointer
&:取地址符
*:解引用符-
空指针:
nullptr可以转化为任意其他的指针类型int *p1 = nullptr; // 建议 int *p2 = 0; int *p3 = NULL; // (预处理变量(preprocessor variable))
-
void* 指针
不能直接操作,因为我们不知道是什么类型 -
指向指针的引用:(不能定义指向引用的指针,因为引用不是对象)
int i = 42; int *p; int *&r = p; // r是一个对指针p的引用,从右往左看 r = &i; // r引用了一个指针,因此给r赋值给&i, 就是令p指向i
-
2.4 const限定符
-
-
const对象必须初始化
-
多文件使用同一个const?
每个文件都用extern const int bufSize
// 例子,都加extern -
const的引用
(reference to const)-
const int ci = 1024; const int &r1 = ci; // 引用及其对应的对象都是常量 r1 = 42; // 错误 int &r2 = ci; // 错误。让一个非常量引用指向一个常量对象
-
int i = 42; const int &r2 = 42; // 正确,常量引用(甚至可以绑定任意表达式作为初始值,或者字面值) const int &r1 = i; // 正确,常量引用 const int &r3 = r1*2; // 正确,常量引用 int &r4 = r1 * 2; // 错误,r4是一个普通的非常量引用
为什么r4是错误引用?
例子:double dval = 3.14; const int &ri = dval; --> 编译器具体操作,会产生一个temp变量 const int temp = dval; const int &ri = temp; 所以ri不是引用dval,而是引用了一个隐藏的temp,这显然不好
-
对const的引用可能引用一个并非const的对象
int i = 42; const int &ri = i; // 这表明不能通过ri,来修改i的值。
-
常量引用!
const int &r = 0; // 合法。因为是常量引用,所以可以绑定在常量上。
-
非法的情形:不能让引用恒定不变
例子:const int &const r2
:
非法,引用不是对象,所以不能让引用恒定不变! -
指针和const
const double pi = 3.14; // 定义一个const常量 double *ptr = π // 错误,因为ptr是普通指针,不能指向常量 const double *cptr = π // 正确,cptr可以指向一个常量 *cptr = 42; // 错误,不能给常量赋值
-
指针类型与所指对象不一致的例外:
-
允许一个指向常量的指针,指向一个非常量对象
double dval = 3.14; cptr = &dval; // 正确,但是不能通过cptr改变dval的值
(指向常量的指针,仅仅要求不能通过该指针改变对象的值!)
(指向常量的引用也是一样!) -
常量指针 const pointer
必须初始化
表示不变的是指针的值,而不是指向的那个值(指向不变!)int errNumb = 0; int *const curErr = &errNumb; // *号放在const之前,说明指针是一个常量 const double pi = 3.14 const double *const pip = π // pip是指向常量对象的常量指针
-
如何辨别?
从右往左阅读!
例子:const int *const curErr = &errNumb; 离curErr最近的是const,所以curErr是一个常量对象! 然后对象是类型,由声明符的其余部分决定 声明符下一个是*,意思是curErr是一个常量指针, 最后const int表示常量指针指向的是一个int常量对象。
指针是常量,并不意味着不能通过指针修改指向的值,而是要看指向的对象的类型!
-
非法例子:
int *p1; const int *const p3; p1 = p3; // 非法,p1可以修改其指向的值,但是p3不能被修改!语法上错误!
-
-
-
-
-
顶层(top-level) const / 底层(low-level) const
-
顶层const:表示指针本身是常量 (不允许改变对象的值)
-
底层const:表示指针所指对象是const (允许改变对象的值)
用于声明引用的const都是底层const -
拷贝上的区别:
-
拷贝操作对顶层const无影响
-
拷贝操作对底层const来说,左右两边必须具有相同的底层const资格
-
还有例如:
int i = 0; const int ci = 42; int &r = ci; // 错误,普通int不能绑定在一个const int上 const int &r2 = i; // 正确,const int可以绑定在普通int上
-
-
顶:自己就是常量
底:指向的东西是常量
-
-
constexpr和常量表达式
- 常量表达式:
值不会改变且编译过程就能得到计算结果的表达式 - constexpr变量
- 作用于指针,只表示常量指针(顶层const)
- 需要字面值类型 literal type
- constexpr指针初始化必须为0或者nullptr
- 常量表达式:
2.5 处理类型
-
类型别名 (type alias)
typedef double wages; typedef wages base, *p;
- 别名声明 (alias declaration)
using SI = Sales_item;
- 注意的点:
typedef char *pstring; // pstring是指向char的指针 const pstring cstr = 0; // cstr 是指向char的常量指针 const pstring *ps; // ps是指针,指向指向char的指针
-
auto类型说明符
目的:常常会将表达式的值赋给变量,但需要知道表达式的类型,所以引入auto,变量就可以自动转换类型了。auto
可以在一条语句重声明多个变量。因为一条声明语句只能有一个基本数据类型,所以该语句中所有变量的初始基本数据类型都必须一致auto i = 0, *p = &i; // 正确,类型一致 auto sz = 0, pi = 3.14 // 错误
-
auto
一般会忽略顶层const,保留底层constconst int ci = i, &cr = ci; auto b = ci; // b 是一个整数,ci的顶层const被忽略 auto c = cr; // c 是一个整数, cr是ci的别名,ci是一个顶层const,所以也被忽略 auto d = &i; // d是一个整型指针,整数的地址就是指向整数的指针 auto e = &ci; // e是一个指向整数常量的指针 (对常量对象取地址是一种底层const)
如果希望推断出的auto是一个顶层const,需要明确指出
const auto f = ci;
还可以将引用的类型设置为auto
auto &g = ci; // g是一个整型常量引用 auto &h = 42; // 错误,不能将非常量引用绑定字面值 const auto &j = 42; // 正确,可以将常量引用绑定字面值
-
-
decltype类型指示符
作用是选择并返回操作数的数据类型 (得到类型但不实际计算表达式的值)
decltype(f()) sum = x; // sum的类型就是函数f的返回类型
若decltype使用的表达式是一个变量,则decltype返回该变量的类型,包括顶层const和引用在内
如果表达式是一个解引用,则会得到引用类型int i = 42, *p = &i, &r = i; decltype(r+0) b; // 正确,结果是int类型 decltype(*p) c; // 错误,c是int&类型,所以必须初始化
// 注意,如果里面是加了括号的变量,结果将是引用 decltype((i)) d; // 错误,d为int&,必须初始化 decltype(i) e; // 正确
Sec3 字符串、向量和数组
3.1 using
using namespace::name
3.2 string:
-
初始化:
string s1; string s2(s1); string s2 = s1; string s3("value"); string s3 = "value"; // 拷贝初始化 (用了等号 = ) string s4(n,"c");
-
操作
os<<s is>>s getline(is, s) s.empty() s.size() s[n] s1+s2 s1=s2 s1==s2 s1!=s2 <,<=,>=,>
-
cctype头文件
isalnum(c) isalpha(c) iscntrl(c) isgraph(c) ispunct(c) islower(c) tolower(c) isupper(c) toupper(c) isxdigit(c)
-
操作每一个字符
for(declaration : expression){ statement; }
// 例子: string str("swafasgdsvsaefwqa"); for(auto c : str) cout << c << endl;
3.3 Vector
vector<int> ivec;|
vector是模板,并非类型!
-
初始化:
- 列表初始化
只能用花括号 - 拷贝初始化
- 创建指定数量的元素
- 值初始化
仅指定元素数量
但接下来只能用直接初始化,而且有的类一定要初始化指定值!
// 区别 vector<int> v1(10); vector<int> v2{10}; vector<int> v3(10,1); vector<int> v4{10,1}; // 注意要是不同类型 vector<string> v5{"hi"}; // 列表初始化 vector<string> v6("hi"); // 错误,不能用字符串字面值构建vector对象 vector<string> v7{10}; vector<string> v8{10, "hi"};
- 列表初始化
-
基本操作
v.empty() v.size() v.push_back(t) v[n]
-
vector<int>::size_type // 正确 vector::size_type; // 错误
-
不能用下标形式添加元素
3.4 迭代器
-
v.begin() v.end() // 尾后迭代器,off the end. 指向容器的一个本不存在的尾后 若容器为空,则v.begin() == v.end()
-
迭代运算符
*iter iter->mem ++iter --iter iter1 == iter2 iter1 != iter2
-
使用:
string s("awefdwafefga"); if(s.begin() != s.end()) // 若容器非空 { auto iter = s.begin(); // 定义迭代器变量 *iter = touppper(*iter);// 解引用来直接访问 }
// 将第一个单词转换为大写 for(auto iter = s.begin(); iter != s.end() && !isspace(*iter); ++iter){ *iter = toupper(*iter); }
-
迭代器类型
const_iterator: 只读不写
iterator
如果vector对象或者string对象是一个常量,则只能使用const_iteratorvector<int> v; const vector<int> cv; auto it1 = v.begin() // it1的类型是vector<int>::iterator auto it2 = cv.begin() // it2的类型是vector<int>::const_iterator
- begin和end 返回的具体类型由对象是否为常量决定:
const_iterator
iterator
- 如果需要是对象只需要读操作而无需写操作。最好使用常量类型。
cbegin()
cend()
可以返回常量类型迭代器(不管对象是不是常量
- begin和end 返回的具体类型由对象是否为常量决定:
-
迭代器的解引用
// 注意: (*iter).mem 和 *iter.mem结果不同。点运算符的优先级是比解引用运算符更高的 (*it).empty(); // 解引用,访问empty成员 *it.empty(); // 错误,it是个迭代器,没有empty成员 简化: (*it).mem 等价于 it->mem
-
某些对vector对象的操作会使迭代器失效
- 不能在范围for循环中向vector对象添加元素
- 任何一种可能改变vector对象容量的操作,比如push_back,都会使该vector对象的迭代器失效。
(凡是使用了迭代器的循环体,都不要往迭代器所属的容器添加元素)
-
迭代器运算
iter+n iter-n iter1 += n iter1 -= n iter1 - iter2 // 迭代器相互之间的距离
-
例子,得到迭代器中间的元素
auto mid = vi.begin() + vi.size() / 2
距离的类型:
difference_type
的带符号型整数 -
例子2:二分搜索:
// Binary search // text 必须是有序的 // beg和end表示我们搜索的范围 auto beg = text.begin(), end = text.end(); auto mid = beg + (beg-end)/2; while(mid!=end && *mid!=sought){ if(sought < *mid) end = mid else beg = mid + 1 mid = beg + (end-beg)/2 }
-
3.5 数组
-
复杂数组的声明
阅读方法,从内到外int *ptrs[10]; int &refs[10]; // 错误 int (*Parray)[10] = &arr; int (&arrRef)[10] = arr; // arrRef引用一个含有10个整数的数组 int *(&arry)[10] = ptr; // array是数组的引用,该数组有10个指针
-
访问数组
数组下标的类型:size_t
而且数组下标如果不是纯数字,就必须是constexper类型!!!!!
unsigned cnt = 42; constexpr unsigned sz = 42; string bad[cnt]; // 错误,cnt不是常量表达式 int *parr[sz]; // 正确
vector允许拷贝,数组不允许拷贝
-
迭代器:
可以把指针看成是数组的迭代器
同样 数组也有begin和endc++11 std int ia[] = {1,2,3,4,5,65}; int *beg = begin(ia); int *last = end(ia);
-
C风格字符串
// function strlen(p) strcmp(p1,p2) // 返回p1-p2的长度的正负 strcat(p1,p2) // 将p2附加到p1后,返回p1 strcpy(p1,p2) // 将p2拷贝给p1,返回p1
-
string和字符串数组相互转换
提供了c_str()成员函数string s("dawdadaw"); char *str = s; // 错误 const char *str = s.c_str(); // 正确
-
-
多维数组的下标引用
int (&row)[4] = ia[1]; // 把row绑定到ia的第二个4元素数组上
-
多维数组的for语句处理
- 用范围for语句
size_t cnt = 0; for(auto &row : ia) // 因为要改变变量,所以要把控制变量row和col设置为引用 for(auto &col : row) { col = cnt; ++cnt; } 先访问ia的所有元素-->即大小为4的数组,再访问大小为4的数组
为什么要用引用??(避免数组被自动转成指针)
声明为引用的另一个好处,如果是string的话,可能会非常大,声明为引用可以避免对对象的拷贝操作!!!!
for(auto row : ia) for(auto col : row) ... 会报错!原因是第一个for循环的auto将row转化为指向整数的指针,而不是指向数组的指针!所以第二个for循环错误!
要使用范围for语句处理多维数组,除别最内层的循环外,其他所有循环的控制变量都应该是引用类型
- 常规for语句
for(auto p = begin(ia);p!=end(ia); ++p){ for(auto q = begin(*p); q!=end(*p); ++q) cout << *q << ' ' cout << endl; }