1. 数据类型
1.1 基本内置类型
我们通过下列代码可知电脑上的基本数据类型的大小:
1 #include<iostream> 2 #include<string> 3 #include <limits> 4 using namespace std; 5 6 int main() 7 { 8 9 cout << "char: " << "所占字节数:" << sizeof(char); 10 cout << " 最大值:" << (numeric_limits<char>::max)(); 11 cout << " 最小值:" << (numeric_limits<char>::min)() << endl; 12 13 cout << "float: " << "所占字节数:" << sizeof(float); 14 cout << " 最大值:" << (numeric_limits<float>::max)(); 15 cout << " 最小值:" << (numeric_limits<float>::min)() << endl; 16 17 cout << "int: " << "所占字节数:" << sizeof(int); 18 cout << " 最大值:" << (numeric_limits<int>::max)(); 19 cout << " 最小值:" << (numeric_limits<int>::min)() << endl; 20 21 cout << "unsigned: " << "所占字节数:" << sizeof(unsigned); 22 cout << " 最大值:" << (numeric_limits<unsigned>::max)(); 23 cout << " 最小值:" << (numeric_limits<unsigned>::min)() << endl; 24 25 cout << "long: " << "所占字节数:" << sizeof(long); 26 cout << " 最大值:" << (numeric_limits<long>::max)(); 27 cout << " 最小值:" << (numeric_limits<long>::min)() << endl; 28 29 30 cout << "double: " << "所占字节数:" << sizeof(double); 31 cout << " 最大值:" << (numeric_limits<double>::max)(); 32 cout << " 最小值:" << (numeric_limits<double>::min)() << endl; 33 return 0; 34 }
1.2 const 和 auto
1. const 基本介绍
const对象一旦创建后其值就不能再改变,所以const对象必须初始化。编译器在编译过程会把用到该变量的地方替换成相应的值,为了执行上述的替换,编译器必须知道变量的初始值。
1 const int i = get_size(); // 运行时初始化 2 const int j = 3; // 编译时初始化 3 const int k;// 错误
默认情况下,const对象仅在文件中有效。当多个文件中出现了同名的const变量时,其实等同于在不同文件中分别定义了独立的变量。如果想在在文件中得到共享,即在一个文件中定义const,而在其他多个文件中声明并使用它。可以使用 extern 关键词,对于const变量不管是声明还是定义都添加 extern 关键词,这样只需定义一次就可以了。
但是需要注意下面这个例子:
1 struct A { 2 int* ptr; 3 }; 4 5 int main() 6 { 7 int k = 5, r = 6; 8 const A a = { &k };// const声明的变量必须初始化 9 a.ptr = &r; // !error 10 *a.ptr = 7; // no error 11 }
可以看到,const 修饰 a 之后其实要求的是 ptr (存储一个地址)不能变,但其实可以改变 *ptr—— c++ 保持物理常量行不变,逻辑常量性不保证。
2. 类中的const变量
1 struct A { 2 const int i; // 构造类是初始化。非静态 3 }; 4 5 struct B { 6 static const int i;// 静态 7 }; 8 9 const int B::i = 3; // 常在类中中直接声明 10 11 int main() 12 { 13 A bf = { 1 }; 14 15 }
3. 顶/底层 const 和 auto
下面是顶层 const 和 底层 const 的实例:
1 /* 2 ① 3 顶层const:指针本身是个常量 4 底层const:指针所指的对象是一个常量 5 6 ② 7 auto一般会忽略顶层const,会保留底层const 8 */ 9 10 #include"iostream" 11 using namespace std; 12 int main() 13 { 14 const int i = 19; //i值不能改变,顶层const 15 const int *p = &i;//p值可以改变,底层const(*P值不能改变->指针所指的对象是个常量) 16 17 int z = 18; 18 int *const o = &z;//o值不能改变,顶层const 19 int t = 13; 20 //o = &t; //错误,因为o是顶层,o值不能改变 21 22 //打印类型 23 cout << typeid(i).name() << endl;//int 24 cout << typeid(p).name() << endl;//int const * 25 cout << typeid(*p).name() << endl;//int 26 cout << typeid(o).name() << endl << endl;// int * 27 28 auto a = o; 29 int y = 12; 30 a = &y; // a值可改变,说明忽略了o的顶层const。对比第20行的o 31 cout << "*a=" << *a << " a类型" << typeid(a).name()<< endl; 32 33 auto b = p; 34 b = &y; //p是底层const,b也是底层const,b值可改变,但是*b不能改变 35 //*b = 9; //报错,b是底层const,b指针所指的对象不能改变。---》说明auto保留了底层const 36 cout << "*b=" << *b << " b类型" << typeid(b).name() << endl; 37 38 39 return 0; 40 }
4. const 修饰函数
const 可以修饰类返回值(一般用于类返回值是指针的形式。修饰返回值的函数没有意义)。也可以写在函数后(如第7行),标是允许修改类的成员变量(mutable关键字修饰的成员变量除外).
1 struct A { 2 int i; 3 mutable int j; 4 5 void f() const{ 6 //this->i++;// error, const 修饰的函数不能改变类成员变量。 7 this->j++;// 除非是使用mutable关键字 8 } 9 10 const int* g() { 11 int k = 2; 12 return &k; 13 } 14 15 int *const h() { 16 int k = 2; 17 return &k; 18 } 19 20 }; 21 22 int main() 23 { 24 A a = { 1 , 2}; 25 a.f(); 26 27 /* 28 a.g() 返回的是 const int *k 类型。是底层const, *k 不能改变 29 */ 30 const int* k = a.g(); 31 //*k = 3; // 非法 32 k = new int();// 合法 33 34 35 /* 36 a.h() 返回的是 int *const b 类型。是顶层const, b 不能改变 37 */ 38 int* const b = a.h(); 39 *b = 3; // 合法 40 //b = new int();// 非法 41 }
2. vector 类型
2.1 vector 基本操作
- v.empty()
- v.size() 返回 vector 对象中元素个数,返回值类型由 vector 定义的 size_type 决定 (vector<int>::size_type)
- v[n] 返回 v 中第 n 个位置上元素的引用
- v1 = v2 用 v2 中元素的拷贝替换 v1 中的元素
- v1 == v2 当且仅当元素数量 and 元素值相同
2.2 访问 vector
- 可以通过下标形式访问 vector,但是不能通过下标形式添加元素而是,push_back()
- 使用 range-base-for 或者 iterator 遍历
1 #include<iostream> 2 #include<vector> 3 #include<typeinfo> 4 5 int main() { 6 std::vector<std::string> v1{ "Tom","Jack","Michael" }; 7 std::vector<int> v2(5, 7); 8 9 for (auto& i : v1) { 10 std::cout << i << std::endl; 11 } 12 13 std::cout << "====================" << std::endl; 14 15 for (auto k = v2.begin(); k != v2.end(); ++k) { 16 std::cout << *k << std::endl; 17 } 18 19 std::cout << "====================" << std::endl; 20 21 v1.push_back("good dog"); 22 std::vector<std::string>::iterator iter = v1.begin(); 23 for (; iter != v1.end(); ++iter) { 24 std::cout << *iter << std::endl; 25 } 26 27 std::cout << "====================" << std::endl; 28 29 v2[0] = 9; 30 for (decltype(v2.size()) i = 0; i < v2.size(); ++i) { // v2.size() 返回的是无符号数(>=0),可以用 int,但需要保证不能为负数,否则无符号数会自动合法话。可运行36~38行 31 std::cout << v2[i] << std::endl; 32 } 33 34 std::cout << "====================" << std::endl; 35 // 会输出 “大于” 36 /*decltype(v2.size()) i = -1; 37 if( i > 10) 38 std::cout << "大于" << std::endl;*/ 39 40 41 return 0; 42 }
3. 迭代器
- 我们知道已经可以使用下标运算符来访问 string 对象或 vector 对象的元素(因为只有一些标准库类型有下标运算符,并非全都如此),但是还有一种更通用的机制可以实现——使用迭代器。
- 如果容器为空,可通过 iter.end() != iter.begin() 来判断,例如:有 string str 对象,则 if (str.end() != str.begin()) {.....} 来保证 str 不为空
- 凡使用了迭代器的循环体,都不要向迭代器所属容器中添加元素
3.1 迭代器和指针
我们可能有疑问,如果是 vector<int> v1 = {1,2,3},那么通过迭代器迭代时,每次的迭代器是 1,2,3 三个数字的地址吗(因为我们可以通过 *iter 来取得数值 )?答案是不是的。
1 #include<iostream> 2 #include<vector> 3 #include<typeinfo> 4 5 int main() { 6 std::vector<int> v = { 1 ,2,4 }; 7 for (auto& i : v) 8 std::cout << typeid(i).name() << &i <<std::endl; 9 for (auto k = v.cbegin(); k != v.cend(); ++k) // cbegin()得到const_iterator。begin()得到iterator。如果只进行读操作可用前者 10 std::cout << typeid(k).name() <<std::endl; 11 return 0; 12 }
我们可以比较 range-based-for 和 iterator-for 的输出类型,其中第八行 &i 输出了 v 中的元素的地址。
那迭代器到底是啥?此处有解释。
range based for loop vs regular iterator for loop