03
-
每个名字都需要独立的using声明
-
头文件不应该包含using声明 因为头文件的内容会拷贝到其他文件中去 可能污染名字空间产生冲突
-
使用
=
一般是拷贝初始化 其他方式一般是直接初始化 -
通过 cin 输入 string 对象时 会自动u忽略开头的空白(空格换行制表符等等)直到遇见下一处空白
比如输入" Hello World! “时结果是"Hello” -
getline(cin, string)
可以读取一整行 直到遇到换行符结束 读取到换行时停止读取 但是换行并咩有被写入string中 -
cctype中有很多大小写判断相关接口哦
-
建议使用c++版本的标准库头文件
c++兼容c时 c语言的头文件为name.h c++将这些文件命名为cname 在name前添加了c前缀 去掉了.h后缀
cname与name.h的内容是一样的 但是cname的名字空间都在std中 .h的则需要记住名字空间在哪里 -
c++标准库string与c的string头文件include
#include <string> #include <cstring>
-
vector不能用下标增加元素只能用来访问已经存在的元素
-
在for循环中尽量使用
!=
而不是小于<
因为标准库容器的迭代器都定义了==
和!=
但是他们中的大多数都没定义<
运算符 -
const_iterator的cit只能读vector<string>中的元素 但是it既可以读也可以写
cbegin和cend可以返回const_iterator的迭代器vector<string> v(10, "av"); vector<string>::const_iterator cit; vector<string>::iterator it; it = v.begin(); cit = v.begin(); (*it) = "A"; for (auto s : v) { cout << s << " " << flush; }
string初始化的几种方式
string s1;
string s2(s1);
string s3 = s2;
string s4("value");
string s5 = "value";
int n = 10;
string s6(n, 'c');
for的简单形式 使用&取地址符表示引用可以修改
string s = "jsjhsafhjsajk";
for (auto c : s)
{
cout << c << flush;
}
cout << endl;
for (auto &c : s)
{
c = toupper(c);
}
cout << s << endl;
vector初始化的几种方式
int n = 10;
int val = 1;
vector<int> v1;
vector<int> v2(v1);
vector<int> v3 = v2;
vector<int> v4(n, val);
vector<int> v5(n);
vector<int> v6{1, 2, 3};
vector<int> v7 = {4, 5, 6};
值初始化
通常情况 只提供vector对象容纳的元素数量而省略初始值 此时库会创建一个值初始化
的元素初值覆给容器中的所有元素
如果vector的元素类型是内置类型 比如int则元素初始值自动设为0
花括号的列表初始化的值可能不符合初始化条件
花括号列表的值不能初始化时会尝试构造函数进行构造
vector<string> v1{10};
vector<string> v2{10, "av"};
// 这里的10不能初始化string 所以会尝试构造函数
for (auto s : v2)
{
cout << s << " " << flush;
}
输出结果
av av av av av av av av av av
数组
-
数组的元素个数必须编译时已知 必须是
常量表达式
-
默认初始化的规则 比如在函数内部定义了某种内置类型的数组 那么值都是为定义的 gg
-
数组的元素为对象 所以不存在引用的数组
-
数组的类型不允许使用auto推导
-
如果提供的初始值比维度小 那么其他的值默认
值初始化
// 这三个是一样的效果 string s1[3] = {"he", "wor"}; string s2[3] = {"he", "wor", ""}; string s3[3] = {"he", "wor", ""};
-
不允许拷贝和赋值
不允许用一个数组初始化另一个数组
不允许把一个数组直接赋值给另一个数组 -
复杂的声明 从右到左 解释
int *(&array)[10] = ptrs; 1. 是一个引用 因为有括号 2. int *[10] 3. 是一个数组的引用 是一个有10个元素的数组引用 4. 数组的元素指针 数组的元素是int的指针 5. 最后 array 是一个有10个int*元素的数组的引用
-
使用数组下标时 通常定义为size_t类型 在
cstddef
中有size_t的定义 -
用到数组名字的地方会 编译器会自动将其替换为一个指向数组首元素的指针
p和p2和nums都相等
string nums[] = {"one", "two", "three"}; string *p = &nums[0]; string *p2 = nums; cout << (p == p2) << " " << (p == nums) << endl;
-
auto对数组的推断是指针非数组 decltype对数组的推断就是数组不会发生上述的转换
-
获取数组的尾指针
int arr[] = {1, 2, 3, 4, 5, 6, 7, 8, 9};
不能对尾指针解引用等等等等等
直接获取 int *e = &arr[10]
c++11引入了两个名为begin
和end
的函数可以数组进行使用begin(arr) end(arr)
这样子 -
数组的下标可以为
负数
多维数组 其实就是数组的数组
-
数组的阅读
有内而外
分析 -
初始化以及如何使用for进行循环 提供的初始化数据不如维度的个数时 所以其他的部分执行
值初始化
(前面说道)int arr1[3][4] = {0}; int arr2[3][4] = { {1, 2}, {4, 5}, }; for (const auto &row : arr1) { for (auto v : row) { cout << v << " " << flush; } cout << endl; } for (auto &row : arr2) { for (const auto v : row) { cout << v << " " << flush; } cout << endl; }
-
上述的for如果在row的auto处不加上
&
符号 然后auto会自动将row转换为首地址的指针(在auto一节有说)
如下代码将会无法编译通过使用for循环多维数组的时候除了最内层的循环 其他所有循环都需要是引用类型
for (auto row : arr2) { for (const auto v : row) { cout << v << " " << flush; } cout << endl; }
-
多维数组指针
别名简化
p是一个行数组的指针 所以*p是行数组首地址 *p+4是行数组尾指针// 这俩声明等价等价等价 using int_array1 = int[4]; typedef int int_array2[4]; using int_array_4 = int[4]; int ia[3][4] = {1, 2, 3, 4, 7, 8, 9, 0, 4, 5, 6, 7}; for (int_array_4 *p = ia; p != ia + 3; ++p) { for (int *q = *p; q != *p + 4; ++q) { cout << *q << " " << flush; } cout << endl; }
C风格字符串
-
#include <cstring>
-
实际是 const char * 类型
-
永远记住最后的
\0
结尾 -
string对象不能用来初始化char或者const char对象 但是string.c_str()返回一个const char指针可以用来初始化const char对象
但是c_str()的返回值是无法一直保持有效的 如果s发生了变化 那么之前返回的c_str可能会失效
如果程序想要一直可以使用c_str最好是将该数组拷贝一份 -
可以使用数组初始化vector
04
-
算数运算符左结合率
-
多数情况下 没有明确求值的顺序 int i = f1() * f2() 并不会对f1和f2的求值顺序进行要求
-
短路求值 &&与运算 ||或运算 ?:三目运算 ,逗号运算 规定了求值顺序
-
例子 f() + g() * h() + j()
优先级 先算乘法
结合率 从左到右加
但是 函数的调用顺序 其实没有明确规定 -
但是这个顺序 编译器很有可能优化了
-
关于 / 除法 和 % 求余
定义满足 (m/n)*n+m%n=m
如果 m%n!=0 那结果一定与m的符号相同 即如下
m/-n -m/n 的商都是 -(m/n)
m%-n 符号与m相同 结果= m%n
-m%n 符号与-m相同 结果= -(m%n) -
前置
递增递减返回左值
后置
递增递减返回右值
-
后置递增运算符的优先级高于解引用运算符即
*it++
相当于*(it++)
-
sizeof运算符 返回字节数 sizeof不需要实际计算其运算对象的值 所以 sizeof *p 即使p已经不合法了也可以求sizeof
sizeof对数组执行可以求得数组所占空间大小 不会把数组转换为指针处理 所以得到了一种计算数组大小的方法
sizeof(数组)/sizeof(数组类型) -
逗号运算符的结果是右侧表达式的值
-
P147
运算符优先级和结合率表
类型转换
-
主要是整型提升 int unsigned int long unsigned long long long unsigned long long
-
如果一个是无符号一个是带符号的分两种情况
-
无符号类型不小于带符号类型 则转换为无符号的 和提升一样
-
如果带符号类型大于无符号类型 则依赖于机器 哪个大转换为哪个
比如一个long和一个unsigned int
如果long和unsigned int的大小一样 则long转换为unsigned int
如果long比unsigned int的大 则unsigned int转换为long
-
-
0或nullptr可以转换为任意指针类型
-
static_cast<T> 不包含底层const才可以使用这个
-
const_cast<T> 只能改变底层const 不能改变类型 但是去掉const属性很有可能
导致未定义结果
-
reinterpret_cast 高危操作
-
dynamic_cast 后面有介绍
-
旧的强制转换 type(expr) 函数式的强制转换 (type)expr C风格的强制类型转换
const char *cp; // 正确 去掉const属性 未改变类型 但是修改*p1可能导致未定义结果 毕竟之前是const char *p1 = const_cast<char *>(cp); // 错误 改变了类型const_cast只能改变const属性 string p2 = const_cast<string>(cp); //错误 static_cast不能改变const属性 char *p3 = static_cast<char *>(cp); // 正确 string p4 = static_cast<string>(cp);
05
-
语句case
最重要的 一旦匹配了case 那么将从该标签开始执行后续所有的case分支 除非显示的中断 这也就是为什么尽量给每一个case都尽量break
case的值一定是整型常量表达式
所以 switch 的值必须可以转换为整型
default
标签int v = 3; switch (v) { case 0: cout << 0 << endl; break; case 1: cout << 1 << endl; break; case 2: cout << 2 << endl; break; case 3: cout << 3 << endl; break; case 4: cout << 4 << endl; break; default: cout << "default" << endl; break; } cout << "-----------------" << endl; switch (v) { case 0: cout << 0 << endl; case 1: cout << 1 << endl; case 2: cout << 2 << endl; case 3: cout << 3 << endl; case 4: cout << 4 << endl; default: cout << "default" << endl; } cout << "-----------------" << endl; // 根据没有break会继续往下执行 可以对多种情况进行一样的处理 // 如下 这样 0 1 2 就会进行相同的处理啦 3 4 5也是同样啦 // 就算没有换行也是一样的 switch (v) { case 0: case 1: case 2: break; case 3: case 4: case 5: break; case 6: break; default: break; }
结果如下 3 ----------------- 3 4 default -----------------
-
while 和 for
定义在while(或者for)内(条件内或者循环体内)的变量每次迭代都会经历创建到销毁的过程
-
break只能 for while do while switch
-
continue 只能 for while do while switch嵌套在循环内部也可以在switch中使用continue
-
goto 需要lable标签 goto语句和转向的标签语句必须在同一个函数内
标签需要用冒号:表示 示例如下int test_goto() { int f = 1; flag1: cout << "goto" << endl; if (f) { f = 0; goto flag1; } else { cout << "done" << endl; } return 0; }