本文摘自:Thinking in C++ Vol.1 (添加部分C++ primer内容。待续...)
- 目录:
第零章:help and tips [Index]
安装c/c++的帮助文档:
yum install man-pages libstdc++-docs
- 头文件中不应该使用using指令。
- 当有符号数和无符号数混合进行计算时,编译器会将有符号型看作为无符号型再进行计算。
-
大多数的运算符都没有规定运算对象的求值顺序,一般而言没有问题。但当同一运算符的多个子句中同时改变了同一个变量时,顺序就很重要了:
*beg = toupper(*beg++);//这行语句是错误的,因为我们不知道等号的两侧哪边先进行运算,所以这行语句可以有下面两种不同的执行方式。 *beg = toupper(*beg); //当等号左侧先进行了计算,则上式等价于此式 *(beg+1) = toupper(*beg); //当等号右侧的式子先进行计算,则原式与本式等价。
-
有四种运算符明确规定了运算对象的求值顺序:
&&
||
?:
,
这四种运算符由左到右进行计算。 - 在C++中运算符
<<
没有直接规定求值的顺序,故int i = 0 ; cout << i << ++i<<endl;
的结果是与编译器的实现相关的,其输出可能是 0 1,也可能是1 1 。 cout << *p_int++<<endl;
返回p_int当前指向的内存中的内容,然后p_int 加一,注意p\_int++
返回的是自加之前的值,而自加运算符的优先级比解引用运算符。cout << *it++ <<endl;
等价于cout<< *it<<endl; it++;
- C++新标准中规定除法一律向0取整,即直接去除小数点。
- C++新标准中若m%n 不等于0 , 则结果和m的符号相同。例如:m%(-n) == m%n (-m)%n == -(m%n)。
- 声明使得名字被程序所知,而定义则是创建与名字相关的实体。
extern int
为声明而int j
和extern int k = 0;
为定义。在函数体内试图初始化一个由关键字extern约束的变量是非法的,因为函数内部的局部变量是无法被外部访问的,故不能在函数内部定义全局变量(而初始化一个由extern约束的变量就是在定义全局变量),但可以声明全局变量,告诉编译器这个变量在外部已经定义过了。 - 应当将修饰符 * 和 & 紧靠变量名 ,因为修饰符优先和变量名结合 在
int *x, y;
中x是指针,y是整型变量。 - 静态变量加前缀 s_(表示 static)
- 如果不得已需要全局变量,则使全局变量加前缀 g_(表示 global )。
- 类的数据成员加前缀 m_(表示 member)或者在成员变量后加 _,这样可以避免数据成员与成员函数的参数同名。
- cin cerr clog :cerr 是标准错误,clog是用于输出程序运行时出现的一般信息,如程序的日志文件。cin 从标准输入中获取数据,从左到右返回标准输入中的数据。//设输入是:1 5 1 3 7 1 9
cin>>a>>b>>c>>d>>e>>f>>g;
则a = 1 b = 5 c = 1... - endl 被称为操纵符,写入endl的效果是介绍当前行并且将与设备关联的缓冲区中的内容刷入到设备中。这可以保证所有需要显示的数据已经写入输入流而没有仅仅保存在内存中。
- 整数的后缀:u或U表示无符号型。
- 当我们把一个非布尔类型的数据赋予布尔类型时,只有当原数据是0时布尔类型是false其他情况下布尔类型都是true。反过来就是0或1。
- 当我们把整型赋予浮点时精度可能会有损失。当把浮点赋予整型时,小数部分会被直接截断,只留下整数部分。
- 当我们赋予无符号型数据一个大于其可表示范围的数据时,其中保存的是原数据对这个无符号型可表示的最大值的余数。但赋予一个有符号型数据大于其可表示范围时,结果是未定义的。
- 浮点的后缀L表示long double (C++11特性)
- 初始化和赋值的区别:
- 初始化:创建一个变量时赋予的初始值。
- 赋值 :把变量当前的值擦掉,写入一个新的值。
- 使用花括号对变量进行赋值:(C++11特性)例如:int i{7};特点:使用花括号对变量进行赋值时,当存在丢失数据的风险时,编译器会报错:int i{3.14};是无法通过编译的,因为会丢失数据。
- C++中内建类型变量的默认初始化问题:全局变量会被初始化为0,位于函数内部的变量的值不确定。(对于局部静态变量又会怎样?)
- 常量表达式:在编译期间就可以知道值的表达式,例如:const int a = getchar();可以通过编译但其不是常量表达式。
- constexpr:(C++11特性)使用本关键字约束的表达式必须是常量表达式,constexpr int i = getchar();是无法通过编译的,这弥补了const关键字的一些缺失。然而当constexpr定义了一个指针时,其仅对这个指针有效,而对指针指向的对象无效。
- nullptr:空指针的初始化关键字。这是C++11新加的关键字。NULL是预处理变量。使用nullptr可以使空指针的初始化被编译器控制。不能直接将int型变量赋予指针,哪怕这个变量的值是0,因为C++对类型的约束限制。在C++中一般只有同类型的变量之间才可以赋值,有时是需要类型转换的,一个例外是void指针可以接受其他类型的指针,当然了,void指针也不能进行解引用,因为编译器不知道这个指针指向的对象的类型。
- typedef double wages;//wages就是double 的别名。
- C++11特性:using wages=double ;则wages和double是同样的意思。
using int_array = int[4]; typedef int int_array[4] ;
其中的int_array是相同的意思。 - 如果定义了一个没有任何成员的类,那么这个类的实例化对象在内存中占一个字节,即使类中没有任何的成员。编译器为了区别每一个对象,必须为对象最少分配一个字节的空间用于区别每一个对象。
- 顶层和底层const
- 顶层const:变量本身受const约束。
- 底层const:变量所指向的对象受const约束。
- auto:auto 定义的变量必须有初始值,否则编译器无法进行推导。若auto i = 0;那么编译器会推导i为整型。auto在推导的过程中会忽略对象的顶层const,但会留下底层const特性。
-
decltype:(C++11特性)返回操作数的数据类型。
decltype(f()) sum = x;那么sum的类型就是函数f()返回值的类型,在这段代码中函数f()没有被执行,编译器会推导出函数f()的返回值类型。在其他地方引用都被看作是一个别名,然而在decltype中引用并不仅仅是一个别名,他还代表着“引用”这层含义。
const int ci = 0 , &cj = ci; decltype(ci) x = 0;//ci的类型是const int 故x也是const int型 decltype(cj) y = x;//y 的类型是const int &,而不仅仅是const int 这点需要注意。 decltype(cj) z;//这句是无法通过编译的,因为z为const int &,必须初始化。
当decltype()中的参数是变量时(变量意味着,除了赋值外无法进行任何的操作或计算,例如a 、b...等,而(a)就是一个表达式,因为我们可以对括号进行计算,从而得到a的值)其所定义的变量与其括号中的变量的类型相同。
当decltype()中的参数是表达式时,其所定义的变量的类型就是表达式返回值的类型(主要返回值是临时变量还是存在的变量),此时就要根据返回值的类型是左值还是右值来判断数据的类型,一般而言当返回值是左值时,声明所获得的变量是引用型的,而返回值是右值时就没有其他的约束条件和限制。int i = 42 , *p = &i , &r = r; decltype(r+0) b;//r+0返回的是一个整型,故b为int型 decltype(*p) c;//无法通过编译,因为***解引用运算符返回的是一个对象***,我们可以对这个对象进行赋值,故c是引用型,必须初始化。 decltype((i)) d;//无法通过编译,括号内部是一个表达式,不是一个变量,而且我们可以对其返回值进行赋值,故d也是一个引用型,其必须被初始化。 //当decltype括号中是变量时,其定义的变量就是其括号中变量的类型,然而但我们给这个变量加上括号后,其就不是一个变量而是一个表达式,而这个表达式的返回值我们又可以对其进行赋值,故在decltype中加括号的变量定义出的变量永远有引用属性。 decltype(i) e;//正确,i 是一个变量,则e是int型。
-
在C++11中可以为类中的成员变量赋初始值,即类内的初始值。对类中的成员赋初始值时,可以使用等号,花括号,但不能使用圆括号进行赋值(因为使用圆括号会增加编译器的复杂度,并使得很多问题变得模糊)。
- 拷贝初始化和直接初始化:拷贝初始化类似于等号赋值,会存在一个临时变量。而直接初始化就没有临时变量。可以认为前者使用的是拷贝构造后者使用的是普通的构造函数。直接初始化的效率高于拷贝构造。
string s5 = "Hello"
使用的是拷贝初始化,系统会创建一个临时变量。而string s6("Hello")
就是直接初始化,不用临时量。 -
构造函数和默认构造函数的一些区别
默认构造函数分为两类,一类是系统自动生成的,即此时类中没有定义构造函数。第二类是类的作者写的不带参数的构造函数或者带默认参数的构造函数。调用这类构造函数的标准语法是
ClassName objectname;
(注意这里是不带括号的)。其他的构造函数都是带有非默认参数的,这些构造函数的调用需要加上参数,而这类情况是比较常见的。在定义一个类的时候不能使用这种方式:ClassName object();
即在对象名后加上一个空括号,在任何地方这种语法都会被编译器认为是声明一个没有参数且返回值是ClassName的函数(参考)。MyClass c1;//表示使用不带参数的构造函数,或者有默认参数值的构造函数。 MyClass c2();//不会调用无参构造函数,各种情况下该处是声明一个返回值为MyClass类型的函数而已 MyClass c3(1);//调用参数为int的构造函数 /*---------------对于new关键字加括号和不加括号的区别--- 1.对于自定义类型来说没有区别,都是使用默认构造函数 2.对于内置类型来说加括号会初始化 */
-
返回值优化
在函数内部:
return ClassName(paramaters);
和ClassName tmp(paramaters);
return tem;
有着不同的含义,前者将直接在返回值地址上创建对像,只会调用一个构造函数。而后者会创建一个局部变量然后再返回,此时的开销要比前者大。这就是返回值优化。 -
range for :(C++ 11特性)
for(auto a:str){}//pass-by-value for(auto &a:str){}//pass-by-reference
范围for语句内不应该改变其所遍历的序列的大小,即当循环体内会改变序列的大小时,不要使用range for
-
在range for 中使用多维数组时需要注意引用和pass-by-value 之间的区别:
for(auto &row:ia) for(auto &col:row) {...} for(auto row:ia) for(auto col:row) {...}//这段代码是无法通过编译的,row 只是一个指针,其不是容器,故无法使用range for 在range for 中使用多维数组时,除了最内层的循环外,其他的循环控制变量都应该是引用型的,就像上面的第一段代码。
-
C++中的左值和右值与C中的左值和右值的意义是不同的:
- 左值表示的是对象的内存位置,是对象的内存空间。而右值只是对象的内容,左值是可以被赋值的但右值不行,因为右值没有对应的内存。右值可以被左值所替换,但左值不能被右值所替代。
- 内置的解引用返回的是左值,而取地址返回的是右值。
第一章:对象 [Index]
- 聚合(aggregation)或组合:被认为是“有一个” , 就像“汽车有发动机”。
- inheritance(继承):reusing the interface (接口的重用) 消耗较组合大。
- 当构建一个新类时你应该首先考虑组合(而不是继承),因为组合比继承更简单更灵活。
有两种方法使你继承的类和基类不同:
- 第一个方法很直接:简单的向基类中添加部分新的动作(函数)。
- 第二中方方法更重要:改变基类中某些函数的动作(或功能),这被称为重载基类中的函数。
在继承类中定义一个新的函数(一般与基类中的函数同名),就可以重载基类中所有的同名函数。
使用关键字virtual声明你想让一个函数具有晚绑定的属性。
- 早捆绑:在编译时确定调用的函数(即编译时确定所调用的函数的绝对地址)。
- 晚捆绑:在运行时确定所调用的函数的地址。
我们把处理继承类就像处理其基类的过程称为向上类型转换(upcasting)。
- 我们有两种主要的创建对象的方法:
- 在栈中的变量经常被称为自动变量或局部变量(在运行时分配与销毁)。静态存储区是一块固定的存储区域,在程序运行前就已经分配(与进程的生命周期相同)。
- 第二种方法是在被称作堆(heap)的内存池中创建对象(内存池由系统维护)。如果你需要一个新的对象,可以使用关键字new在堆中创建它。当你不再使用这个对象,必须使用delete销毁这块内存。
#include <iostream.h>
means#include <iostream>
using namespace std;
八进制和十六进制:
cout << "in octal: " << oct << 15 << endl;
cout << "in hex: " << hex << 15 << endl;
容器 vector:
- 容器是模板,意味着它可以高效的应用于不同的类型。
- 不存在引用vector ,但可以存在指针型的vector。引用原本就是原数据的别名,编译器是知道原数据的精确地址的,然而vector中是要保存其自己的数据的,这就产生了矛盾:vector中应该保存什么?保存指针的话违背了引用的含义。
- 在早期的编译器中如果vector中的对象也是一个vector,那么在声明新vector时要使用这样的语法:
vector<vector<type>>
,在新的编译器中不用这样。 - vector的初始化;
- 因为vector相当于一个对象的数组,故在初始化时我们可以预先设定对象的个数,并对这些对象赋相同的初始值。
vector<T> v(n , val)
v中有n个T类型的且初始值为val的对象。vector <string> v2("a" , "a")
是错误的,括号相当于调用构造函数,而这条语句没有太大的意义。vector<string> v3(10)
v3中有10个string对象,而且其初始化值为0。对于没有默认初始化值的类型,使用类似vector<T> val(n)
的语法是错误的,系统不知道对n个T类型的对象赋什么样的初始值。当然了,内建类型都有默认的初始值。
- 因为vector相当于一个对象的数组,故在初始化时我们可以预先设定对象的个数,并对这些对象赋相同的初始值。
- vector就是一个容器(其结构类似于数组,左边是起始,右边是尾部)故使用push_back()添加新的数据。
- vector中的元素以字典顺序比较大小。
- vector和string对象的下标运算符可以用于访问已存在的数据,不可以用于添加数据。
- 可以使用数组来初始化vector ,
vector<int> ivec(begin(arr) , end(arr))
其中arr是一个int类型的数组。注意首迭代器和尾后迭代器的区别,以及对容器的影响(尾后迭代器指向的不是有效的成员)。#include<iostream> #include<fstream> #include<vector> #include<string> using namespace std; int main() { ifstream in("test.txt"); string str; vector< string > strvec; //while(getline(in, str)) strvec.push_back(strvec); // 每次读一行,下面这行代码,每次读两个空格之间的字符串, while( in >> str )strvec.push_back(str); //以空格作为间隔读取数据,可能同时输出单词和符号,例如 .hello, for(int i = 0 ; i <strvec.size();i++)cout<<i<<':'<<strvec[i]<<endl; return 0; }
如果定义了一个数组而没有初始化,则编译器不会初始化数组。但若给出少于数组元素个数的初始化数据,则余下的数组元素会被初始化为0。
故int b[n] = {0} ; 是将所有数组元素初始化为0 的简洁方法。
第二章:c in c++ [Index]
- pass-by-value:按值传递,c中只有这一种传递方式,分为数据传递与指针传递(注意指针也是按值传递)。
- pass-by-reference:按引用传递,c++独有,可在函数中直接使用内存中存在的变量。
-
C 原来的库在C++中有与其对应的库,但库名进行了更改:stdio.h 更名为 cstdio ,其他的库类似。
-
引用的特点:
- 引用被创建的同时必须被初始化(指针则可以在任何时候被初始化)。
- 不能有 null 引用,引用必须与合法的存储单元关联(指针则可以是 null)。
- 一旦引用被初始化,就不能改变引用的关系(指针则可以随时改变所指的对象)。这中操作是没有意义的,故非法。
-
强制转换:c++比c多了一种转换的语法int(data),在c中只能写成(int)data。
-
强制类型转换:
- c可用: (int) i;
- c++可用:(int) i; int(i);
-
c++的类型检查比c严格:
int i = 10; void *vp = &i;//c与c++均可 int *ip = vp;//c中可用,但在c++中不允许,在c++中不允许将void指针直接赋予其他类型的指针。
-
c++中不允许调用未事先声明的函数,但c可以。 c++中有显式运算符,例如:
&&
可以表达为 and -
使用函数指针调用函数的正规语法:(*funp)(data),但大部分编译器允许程序员写作funp(data) ,但这只是一种简写的方式。
-
下面的#和##只能用于宏定义中:
#将变量或表达式转化为字符串。当你在宏变量前加上 # ,预处理器会把宏参数转换为字符串。
标志粘贴:##将两个标识符连接从而形成一个新的标识符。 -
在标准头文件中你可以发现assert(),这是一个方便的debug宏。当你使用assert()时,你提供一个参数,也就是一个你认为为真的等式(断言)。预处理器会生成一些代码测试这个等式,如果断言是假,程序会停止运行并给出断言所处的位置。 完成测试之后在代码的开始加上 #define ndebug 则可使assert()失效,从而提高效率。
-
函数的地址可以使用其函数名表示。 当然也可以使用更明确的方法,使用取址符号, &fun().
int main() { int (*fp)(void); //定义函数指针 pr(); fp = pr; fp(); //这两个使用方法均是正确的,但后者更规范,fp() 这种定义是编译器提供的。 (*fp)(); }
第三章:深入理解字符串 [Index] 10 12月 2015
C++标准规定字符串内存的分配允许但不要求使用引用计数。但无论是否使用引用计数,类的实现对用户而言必须是透明的。
- 不存在引用的数组,就像不存在引用的vector一样。
int &refs[10];
是错误的语句,因为不存在引用的数组。int (&arrref)[10];
是正确的,arrref是数组的别名。
- 数组的维度在编译的过程中应该是已知的,即数组的维度是编译时常量。虽然说在gcc编译器中数组的维度在编译期间可以是未知的,但要与标准相同,在编译时确定数组的维度。
- 系统定义的数组下标是size_t类型的,这是一个和机器相关的无符号整型。
- 字符串数组有一种额外的初始化的形式:
char str[] = "hello"
, 但要注意的是sizeof(str)的值是6,而不是5,char a[5] = "hello"
是错误的语句,因为系统需要最少6个字节来保存hello字符串,因为hello后有一个 0 用于结束字符串hello,故编译器将提示错误。同理,char a[] = {'2' , 'B'}
和char a[] = "2B"
所得的结果不同的,前者使用了两个字节,而后者使用了三个字节。 - C++11中新添加了两个获得数字的首元素指针和尾后指针的全局函数begin() 和end() 。
- 使用数组作为一个auto变量的初始值时,所得到的数据的类型依旧是一个指针。
- decltype(ia) ia1;其中ia是一个数组的名字,那么ia1依旧是一个数组,故ia1也是一个常量型的指针,其数组的维度和ia是相同的。
-
引用计数:
在引用计数中,每一个对象负责维护对象所有引用的计数值。当一个新的引用指向对象时,引用计数器就递增,当去掉一个引用时,引用计数就递减。当引用计数到零时,该对象就将释放占有的资源。使用引用计数的一个优点是可以实现写时复制,这样可以节省时间和内存。但在多线程编程中几乎无法使用引用计数技术。
-
字符串"\1234"所表示的是两个字符:\123为123所对应的字符unicode值,而4又是另一个字符的Unicode值(或者ASCII)。
- string 类中有个成员函数c_str()返回字符串c类型的数组,但这个指针指向的数组的内容可能被外部的代码所改变(即string对象被其他代码改变,则指针指向的数据也可能被改变),故当长时间使用c_str()函数返回的数组时,要重新拷贝一份数据。
- 只初始化多维数组中子数组的第一个元素的方式类似于:
int a[3][4] = {{1} , {1} , {1}};
-
字符和字符串字面值:(具体解释见笔记:编程杂记)
+--------+-----------+----------+ | 前缀 | 含义 | 类型 | +--------+-----------+----------+ | u | Unicode16 | char16_t | +--------+-----------+----------+ | U | Unicode32 | char32_t | +--------+-----------+----------+ | L | 宽字符 | wchar_t | +--------+-----------+----------+ | u8 | UTF-8 | char | +--------+-----------+----------+
-
getline(src , str_buf)
每次从src中获得行字符串,并放在str_buff中。 string::size_type str_buf.size()
返回的是字符串中字符的个数而不是字节数。而且其返回值的类型并不一定是int型,这个类型与编译器的实现有关,故一般写成auto len = str.size()
让系统自动推演返回值的类型。- 字符串的比较,系统会逐个字符对比两个字符串的内容,比较的结果是两个相同位置但内容不同的字符的比较结果,对于内容相同但长度不同的字符串,短的小于长的。
- 当将字符串和string对象混合在一起使用 + 号时,必须保证加号至少一侧是string对象。这样才能使用重载的+号。
string & wstring
- sring常用的初始化方法:s4(6 , 'c');没有string s4(6 , "c")
- 在C++中string定义的类中存储的字符是char型,而wstring中的字符是wchar_t型的。
string类和wstring类都是由模板basic_string生成的:
typedef basic_string<char > sting ;
typedef basic_string<wchar_t> wstring;
注意:string不是关键字,在使用string时需要包头文件string:#include <string>
。
第四、五章:实现的隐藏 [index]
第四章的部分内容: ::(作用域解析运算符):
指定所定义的函数所属的范围。stash::link::add() 说明定义的add()函数属于stash中的link结构。此操作符只用于类或结构,不能用于他们的实例,因为这是没有意义的,只用改变类或结构才会改变他们的实例(类和结构像模板,而实例相当于他们的拷贝,只有模板变了,拷贝才会变)。如果在变量或函数之前使用 ::说明函数或变量时是全局变量和函数,不是本类或结构体中的与全局变量同名的函数和变量。
c++访问控制:
- class中默认的成员为private而struct中的成员默认是public。
- public访问说明符意味着在其下的声明对所有人都是可用的。
- private关键字从另一个方面来说,意味着除了类的创建者,其他人都不能访问私有的(private)内容。
- protected的行为和private很像,但两者有一点不同:在继承中,私有的内容不能被访问,但protected内容可以被访问。
构造函数与析构函数均为public成员,因为析构函数与构造函数均由系统自动调用,而调用的方式是在对象定义处与包含对象的右括号处由编译器自动插入调用构造与析构函数的语句。此时这两个函数相当于在对象的外部执行,其必须为public。
可以通过在结构中声明友元实现让一个非成员函数更改某个结构中的成员。在所有关系中一个非常重要的规则是“谁能访问我的私有成员?
第六、七章:初始化和清除 [Index]
构造函数:
与类的名字相同,但没有返回值 。在创建对象时,由编译器自动的调用。编译器会在对象的定义处插入构造函数的调用行,编译器会隐含的向构造函数传递一个参数,即本对象的内存地址。 构造函数没有返回值是因为构造函数是由系统自动调用的,如果为其添加返回值,系统必须自动处理返回值,而这样做没有意义。
默认构造函数:
可以不显式的提供参数的构造函数,例如类中定义的无参构造函数和参数都有默认参数的构造函数,当然了还有系统自动生成的。当类中没有显式的定义构造函数,系统会生成一个默认的构造函数,但这个默认构造函数不会对对象中的数据做明确的初始化。
在析构函数内部使用exit函数是非常危险的,因为程序从main中退出时有时也是调用exit函数,如果在析构函数中使用exit函数将会造成死循环:例如程序从main中退出,系统会调用exit函数,这时系统会调用对象的析构,如果析构函数中存在exit函数那么系统会去终止main函数,而main中打exit触发后会再次去执行对象的析构函数,这样就形成了死循环。
析构函数:
析构函数与类的名字相同但在前面添加 ~ ,析构函数没有参数表(有参数表也没意义,因为析构函数由系统自动调用,有参数表可以完成的动作,没有参数表一样可以完成,有还是没有参数对析构函数而言是一样的),且析构函数由编译器自动调用。析构函数自动调用的唯一根据是包含这个对象的右括号。
因为析构函数与构造函数均由系统自动调用,而调用的方式是在对象定义处与包含对象的右括号处由编译器自动插入调用析构与构造函数的调用语句。此时这两个函数相当于在对象的外部执行,其必须为public。
析构函数的调用顺序和构造函数的调用顺序相反。
局部跳转(goto)会可能触发析构函数(只要goto跳出了对象的作用域)。非局部跳转不会触发析构,例如:setjmp()和longjmp。构造函数与析构函数均为public成员。
c++中对象的定义和初始化是集为一体的:对象的创建和初始化必须同时完成,否则无法通过编译。一般编译器会检查是否在条件语句中定义了对象。 因为在条件语句中的对象可能没有被初始化:例如:if(*)static x x; if语句很可能因为假而被跳过,故对象x只是被分配了内存,但没有被初始化,此系统会报错或警告。
重载:
相同的名字但不同的参数表。不能使用返回值实现重载,因为很多时候我们使用了一个函数但忽略它的返回值。一般而言编译器内部实现重载的方法是使用参数列表来标识每一个函数,例如一个函数int print(float flt)在编译器内部可能表示为_print_float()。
union:
同一片内存空间,当选择联合中不同的成员时,会对这片内存做不同的解释。
匿名联合:
没有名字的联合,例如:
union{
float f;
double d;
int i;
};
在使用这种联合时,不需要联合的名字就可以使用其中的变量,例如直接对i赋值:i=9; 可以用作浮点数的相等比较
占位符参数:
声明:void f(int x , int = 0 , float flt = 1);//默认参数只能存在于函数声明中。
定义:void f(int x , int , float flt ){}
调用:调用含有占位符参数的函数,必须为占位符参数提供一个参数,而且这个参数要与占位符类型一致。
函数的声明中 int fun(); 在c++与c中的含义是不同的,c中意味着函数的参数是任意的,类型任意,参数也任意。但在c++中表示没有参数。
static在不同范围的两种意思(这些规则对对象而言一样适用):
- 内部连接
- 这类变量会保存在静态存储区,而且这类变量只会被初始化一次。
const与volatile:
c++中与c中的const意义不同。在c中会为const变量分配内存空间,在c++中不一定会这样。在c++中const变量可能并不存在内存中,因为在编译过程中编译器可能直接将其优化而消失。
volitile 与const相反,volatile通知编译器不要优化某个变量。
位运算符 & | ~ ^(异或) << >>
注意:
所有的位运算都是在寄存器中执行的并没有在变量的内存空间有所体现。那么为了对某个变量进行位操作,必须进行赋值操作。例如 i = i>>2 ;
所有的二元运算符都可以和等号相结合如: a += 3 ; b>>=2; ~是一元运算符,不能与等号结合。
逗号运算符:
逗号运算符是所有运算符中优先级最低的。
int main()
{
int a1 , a2 , b=2 , c=3 , d=4;
a1 = (b++,c++,d++);
a2 = b++ , c++ , d++;//赋值运算符 ' = ' 的优先级高于逗号故先赋值
}
逗号运算符用于分隔表达式,而逗号运算表达式的值是最后一个表达式的值。上面a1的值为4整个表达式的值是最后一个即d++ 。 a2的值为2 ,等号的优先级要高于逗号运算符。
c++中的显式转换:
- static_cast:用于非多态类型的转换。
- dynamic_cast:用于多态类型的转换。
- const_cast:用来消除const, volatile, __unaligned属性的转换。
- reinterpret_cast:用于空间的重新解释。
具体见:http://blog.csdn.net/callmeback/article/details/4040583
与c不同,c++中结构体的名字可以直接用来定义新的变量。
例如:定义struct s{};
在c中定义变量时需要使用:struct s vs;
在c++中可以直接使用: s vs ;
关键字this产生本结构体的地址。
java中不允许局部变量与全局变量同名,但c++可以。
第八章:常量 [Index]
c中可以使用宏定义,例如:#define pi 3.1415
在c++中使用const替代 常量的宏定义,这样可以增加程序的安全系数。
c++中cosnt默认为内部连接 ,而在c中cosnt默认为外部连接。
书中说编译器无法获得内存中的数据,这只是对某些编译器成立,在vs2013中下面的代码1无法通过编译。说明vs2013的编译器无法在编译时获得array1[1]中的数据。但在g++编译器中代码1可以通过编译并且输出为8 。 //代码1
int array1[] = {1, 2, 3, 4};
int array2[array1[1]];
提示:error c2133: 'array2' : unknown size 在vs2013中无法通过编译,
//代码2
cout<<sizeof(array2);
int b = 9;
char c[b];
提示:error c2133: 'c' : unknown size
//代码3
const int b = 9;
char c[b];
代码3可以通过编译,说明编译器对b做了优化(常量折叠)。
c中和c++中对const变量的定义有本质的差别:
- c:一个不可以被改变的变量,这个变量无法直接更改,但可以使用间接的方法,c中常量一定会被分配内存空间(不在只读存储空间)。在 c 中const默认为外部连接。在c中可以声明:const int bufsize ;不初始化,但c++不行。
- c++:一个常量,编译期间的常量。(const在c++中还有其他的意义,见下面)声明和定义一般必须同时完成(除了类中的常量),因为定义后无法更改。 在c++中const默认为内部连接。故需要加上关键字extern才能使其被外部文件连接。
c++中使用类似c中使用指针的方法改变const变量的行为没有定义(未定义行为),也就是更改结果与编译器实现有关。
例如:
-
代码1: c++
#include<iostream> using namespace std; #define pr(x) (cout<<#x##" = " <<x<<endl); int main() { volatile const int m = 9; pr(m) int *pint =(int *) &m; *pint = 6; pr(m) pr(*pint) system("pause"); }
-
代码2: c++
#include<iostream> using namespace std; #define pr(x) (cout<<#x##" = " <<x<<endl); int main() { const int m = 9; //不一定开辟空间 pr(m) //强迫编译器为常量开辟空间 int *pint =(int *) &m; *pint = 6; pr(m) pr(*pint) system("pause"); }
-
代码3: c
#include<stdio.h> using namespace std; #define pr(x) {printf(#x);printf("=") ; printf("%d\n" , x);} int main() { const int m = 9; //c编译器一定为常量开辟空间 pr(m) int *pint =(int *) &m; *pint = 6; pr(m) pr(*pint) system("pause"); }
- 代码1 c++编译输出:
m = 9 m = 6 *pint = 6
- 代码2 c++编译输出:
m = 9 m = 9 *pint = 6
- 代码3 c编译输出:
m = 9 m = 6 *pint = 6
c++中的常量是编译期间的一个定值,故编译器没必要为这个常量分配内存,而且const常量没有像字符串常量那样被分配在只读存储区。而且常量的值会保存在符号表中,编译器会将局部const变量进行常量折叠。故如果强迫编译器为常量分配内存,即使在c++中改变了常量在内存中的值,我们在代码中使用的常量的值依旧不会改变,故会出现上面代码2的情况。但是如果在常量的前面添加关键字volatile,告诉编译器不要对const常量进行优化即不做常量折叠,每次使用const常量时都从内存中读取,那么就会出现代码1中的结果。c中一定会为常量分配内存,而且每次使用时都会从内存中读取,故会出现上面的代码3的结果。从上面的结果可知,const常量没有像字符串常量那样被分配在只读存储区。
参考资料:http://blog.csdn.net/heyabo/article/details/8745942
const int *pint;
int const *pint; //这两个声明的意思是相同的,pint指向一个int型常量(这个变量中的值不能变)。
int * const pint; //pint是一个常量型指针(指针内容不可变),其指向一个int型变量。
- 字符串是默认的常量并且保存在只读存储区,字符串中的数据是无法更改的(一般更改字符串的代码可以通过编译,但在执行时会触发硬件中断)。
- 字符数组一般是变量,字符数组一般在栈中或静态存储区(静态存储区和只读存储区是不同的概念)保存,因为是变量所有其中的内容是可以更改的。
char *pchar = "hellow !"
这样的代码在技术上而言是错误的,因为赋值号的右侧是字符串而左侧却是一个指针。但编译器允许这样,编译器默认将字符串的首地址赋予指针。
在pass-by-value形式的函数前添加关键字const是没意义的,因为完全没有必要,原来的数据一定不会被更改。
如果一个函数返回一个const型的对象,那么这个函数不能作为左值,因为其返回的内存空间是不能被更改的,更不可能被赋值。当一个函数返回的是一个对象而且这个对象不是const型时,这个函数可以作为左值,例如:f(x1)=f(x2),在c中是看不见这样的赋值形式的,在c中左值永远都是变量,而不可能是函数的形式(这里涉及了C++中的临时变量的概念)。如果一个函数返回的是内建类型,那么编译器不允许其作为左值。允许返回值为对象的函数作为左值可以写出链式的表达式例如:f(x).fun(y) ,其中fun( y)为f(x)返回值的一个成员函数。可以将上面的代码作为右值,这样可以简化代码。
类中的常量有两种形式:
- 对象中的常量:对于每一个对象而言某一个变量是常量,由同一个类创建对象的常量值不同,一般这样的常量在类中声明的形式为const int data; 必须在构造函数初始化列表中以类似fun(...) : data(9)...{ function }的形式初始化这些数据(必须以函数的形式初始化,不能用等号的形式)。
- 类常量:对于由同一个类创建的对象而言,每一个对象中这个常量都是相同的,这样的常量的声明形式如:static cosnt data = t;这样的常量在声明的同时必须初始化,也可以使用无标记枚举来实现这样的效果。这类常量只是在编译期间存在,因为编译器没必要为这样变量分配内存。
const对象和成员函数:
const对象只能调用对应类中的const成员函数。其中cosnt成员函数的定义,必须声明和定义的时候在参数表之后函数体之前添加const关键字。如果一个成员函数被定义为const类型那么在函数体内将无法更改对象中任何数据成员(除非这个数据被被关键词mutable修饰),否则将无法通过编译。
const成员函数的声明和定义
class test
{
public:
test(int a);
void nchange(int b ) const; //const成员函数的声明
private:
const int m_a;
int m_c;
};
void test::nchange(int b )const //const成员函数的定义
{
//m_c = b;//无法通过编译
int c = b;//c 不是类中的成员数据,故可以通过编译
}
第九章:内联函数 [Index]
inline:
内联函数存在的理由:使用较小的空间代价获得较大的时间效率。
内联函数的函数体中不能出现循环和sewitch语句,否则编译器会自动将其转化为非内联函数。内联函数的函数名和函数体同时存在于符号表中。内联函数的函数体会嵌入调用内联函数的地方,而不会像其他函数那样使用call指令调用。
有两种情况编译器不会执行内联:
- 内联函数过于复杂,比如说内联函数中有循环或switch语句。
- 显式或隐式的取函数的地址。
内联函数的两种实现方法: 1、使用关键字inline声明函数。 2、在类中直接定义的函数,一般不推荐这种方法,因为这样做会破坏函数可读性。
c++规定只有在类声明结束后才会对内联函数进行计算。这样可以解决向前引用,即引用声明在自己之后的函数。
第十章:名字控制 [Index]
在c++ 中,那些常放在头文件中的名字如常量,内联函数默认是内部链接的。注:在C中常量默认是外部链接。
在程序的代码量达到一定的程度时,全局的名称就会发生重复,此时就需要名字空间来解决这个问题:
使用关键字namespace 来定义新的名字空间:
``` namespace mylib{ //... } //这里不加分号 ```
- namespace的定义和class,struct,union,enum不同,名字空间的定义后不加分号 ; namespace只能定义在全局中,但他们可以嵌套。
- 名字空间的作用范围与普通的函数和类是相同的。若将名字空间定义在大括号内,那么名字空间的作用域就是这个大括号内部。
- 名字空间和类的定义相同,在头文件中声名,在.cpp文件中实现。
- 同一个namespace可以在不同的头文件中定义。但类是不可以重复定义的。
namespace xalias = ...; //为名字空间添加别名。
- 可以在一个名字空间中的类声名中嵌入一个友元声名。
- 只要定义了名字空间(除了匿名名字空间),那么若想使用名字空间中的名字就必需显式的调用这个名字空间。
匿名名字空间:
namespace{
//...
}
- 如果把局部变量放在匿名名字空间中那么其为内部连接。
- 这种名字空间中的名字在当前的编译单元是无限有效的,每一个编译单元只能有一个匿名名字空间。
使用名字空间的方法:
- 作用域解析法,类似于std::cout 优先级最高
- 使用声名,类似于using std::cout例如:下面的代码中mylib1和mylib2中都有name。使用声名获得名字要比使用指令方法引入的名字“优先级”更高。优先级第二。
- 使用指令,类似于 using namespace std ;使用指令可以使我们直接进入整个名字空间。优先级第三
#include "fun1.h" using namespace std; int main() { {//名字空间的声名只给出了名字,没有类型信息,故声名引入了这个名字的重载集合。 using mylib2::name;//使用声名,那么在后面使用的name都是来自mylib2中。若想使用mylib1中的name,必须完整的限定。 using namespace mylib1; name();//这是mylib2中的name() mylib1::name();//完整的限定才能调用mylib1中的name。 } salute salute("china"); salute.sayhello(); // name(); }
类中的静态变量:
- 类中的静态变量对于对像而言意味着所有的由这个类实例化的对像都共享这个static变量的存储空间。因为这个变量并不仅仅属于某个对像,故将初始化这类变量的语句放在类中不是最好的解决办法。
- 类中static变量的初始化不能放在类的实例化时。必须在类的实例化之前对类中的static变量进行内存分配与赋值。
- 静态数据的内存分配是在类的定义文件中实现的,如果没有在定义文件中定义静态变量,那么即使类中声名类静态变量,链接器会报错。具体的语法为全局:int a::i = 9;其中类a中定义类static变量
static int i;
。 - 注意static在编译单元中的意义,故在定义类中的static变量时不应再在变量前加上static关键字进行修饰。
在类中的常量(注意与对像常量的区别)有两种形式:匿名枚举和const static in a。在C++11中支持非静态变量的类中初始化。在C++11类的定义中出现int i = 2;是合法的,但在较低版本的c++标准是非法的。
局部类和嵌套类:
- 局部类指在程序内定义的类,而嵌套类指类中定义的类。局部类不能有静态的数据,而嵌套类可以有静态数据成员。
- 这是因为在对类中的静态数据进行初始化时必须在类的外边进行定义,然而局部类无法获得相应的作用域,故局部类不能有静态成员。
类中的静态成员函数:
- 考虑在cpp文件中static的意义在于不允许其他的文件链接本文件中的名字,故不能在定义文件中再使用关键字static。编译器可以通过类中的声名知道哪些函数是静态的。
- 类中的静态成员函数服务于所有本类的对像故类中的静态函数不能操作类中普通的数据和函数,因为系统不知到应该操作哪个对像的数据。
- 虽然说类中的静态函数不能调用普通函数和普通变量。但普通成员函数和变量却可以使用静态的函数和变量,因为编译器知道访问什么。
- 可以认为静态函数保存在一个独立的存储空间,故使用this关键字是无法引用静态变量和静态函数的。最好的解释是系统无法指定对像。
- 类中的静态函数可以使用多种方法进行调用: 直接与类结合a::setstatic(...) 一般编译器也提供对像调用静态函数的语法支持即 . -> 可以利用静态数据的特点使某个类只能有一个实例。中文 thinking in c++:p233
全局变量初始化的相依性:(有相依性的全局变量存在的一些问题)
对于全局变量而言初始化顺序的不同会造成程序结果的不同,然而在不同编译单元中的全局变量的初始化次序是无法控制的。
在文件1中:
extern int y; x = y + 1 ;
文件2中:
extern int x y = x + 1 ;
编译器会默认将全局未赋值的变量初始化为0,如果上面的两个文件的编译顺序不同则x和y的值也将不同。在些程序时需要注意这些情况。
如果两个关联的全局变量无法避免,那么解决上面的问题有两种技术(技术2是最常用的):
这两种方法的核心思想是相同的:位于不同文件的有关联的静态数据要么不初始化要么就一起初始化,这样就可以限定初始化的顺序。
- 使用一个“哨兵”,使用类中的静态数据,这样就能通过哨兵确定是否所有相关联的变量已初始化,已初始化就就不用再初始化。例如:在类中设置一个静态的int变量这个变量的初始值是0,当初始化之侯,赋这个值为非零值这样就可以控制类的初始化次数。p235然而这个技术有个十分明显的缺点,那就是必须在有关联全局变量的文件中都定义一个这样的类。而下面这个技术就不用。
- 考虑static在程序内部的意义:函数内部的static变量只初始化一次。那么我们可以将所有关联的变量定义在函数内部,让函数返回引用。在引用这些变量时直接引用这些函数,因为这些变量在函数内部是静态的故只会初始化一次,由函数返回引用来调用这些变量。使用函数来调用这些变量,即使变量没有初始化,函数会自动的初始化这些变量。
在两个文件中定义两个相关联的int 型数据int x 和 y 为了强制化调用次序,我们定义两个函数:
- 文件1:
int &x(){static int x = 1;return x;}
- 文件2:
int &y(){static int y = 8;return y;}
在不同的文件中使用x和y时就使用这几个函数,那么变量的初始化次序只与代码有关。那么这些变量的初始化次序是可控的。
- 文件1:
替代连接説明:
C++ 和C 在编译时函数的名字的修饰方法是不同的,故在c++中使用c库时需要说明:extern "C" float foo(...)或者extern "C" { ... }
第十一章:引用和拷贝构造函数 [Index]
引用就是能自动的被编译器间接引用的常量型指针。引用可以用于函数的参数表也可以用于函数的返回值。编译器会对常量进行常量折叠。
- 引用的一般格式:
int &data =... ;
函数返回引用的格式是:return data(不再使用&,否则编译器无法解释&是取地址或其他)。引用的特点见第二章。 - 如果代码中明确声名返回了一个引用那么这个变量所分配的内存空间接近于堆的内存空间,否则变量内存一般在栈中分配。
在C中允许将void 类型的指针赋予其他类型的指针,但在C++是不允许的。
如果确定不改变引用的数据,最好声名引用为常量引用,这样函数的应用范围更广。void func(const int &data);
指针引用:
- 在C中如果想改变指针中的数据,我们一般声名一个指向指针的指针 例如:
int **pIntPtr;
而在改变指针内容的时侯使用的语法一般类似:*pIntPtr = ...; - 如果我们使用的是指针的引用那么使用时语法就简单一些:
int &*pIntPtr;
直接使用pIntPtr = ...
函数框架:
在C/C++中参数表的入栈顺序是由右到左。
int f(int x , char c);
int g = f(a , b);
对应的汇编类似于:
push b ;参数入栈,右到左
push a
call f() ;
add sp 4 ;清理栈中的数据
mov g , register a
对于内建类型,编译器知道类型的大小,且编译器可以将内建类型放入寄存器中进行计筭与返回。故f()的返回值在寄存器中。对于一般的函数调用,如果函数中只有内建类型,那么内存中函数框架类似于:
....栈顶|...函数参数...|返回地址|...局部变量...|栈底....
编译器对内建类型函数的调用过程:
- 编译器先将f()中的参数由右到左压栈。这是框架中的函数参数。
- 再将调用函数f()处的地址压栈,这样return语句就可以在执行完f()后返回调用f()处。这是框架的返回地址。
- 局部变量就是f()在运行过程中使用的变量。这就是框架中的局部变量。
在整个过程中,需要返回的值是在寄存器中存储的,返回过程中,将寄存器中的数据复制出即可。然而对于非内建类型而言有时寄存器无法放下这些数据。如果将f()的返回值放在局部变量中,那么当函数返回时栈指针将指向返回地址。如果此时有其他程序的中断发生那么栈指针下的空间将分配给哪些中断程序,而此时返回值就被复盖了,故这种方式行不通。所以返回值不能存放在栈中,因为重入的原因返回值也不能放在全局变量中。
为了解决这个问题,对于含有非内建类型的函数的调用,其函数框架与前者不太相同:
....栈顶|返回值地址|...函数参数...|返回地址|...局部变量...|栈底....
若B是一个类,B = f();
- 编译器首先要做的就是将B的地址压栈,这就是新的函数框架栈中比上一个多出的返回值地址。
返回值不再是由寄存器复制到目的地,而是由f()直接使用栈中的返回值地址将值复制到目的地。
拷贝构造:
如果知道一定会使用按值传递来传递一个对像时,那么就需要定义拷贝构造否则就没有必要定义拷贝构造。
声名一个私有的拷贝构造函数就可以避免按值传递一个对像。 默认的拷贝构造就是位拷贝。
拷贝构造函数的定义类似于:在类中定义 HowMany(const HowMany &h);
位拷贝与初始化:
int main()
{
HowMany h;
h.PrintCounts();
HowMany h2 = f(h);
}
若HowMany是一个类,且其中只有默认拷贝。那么这里使用的就是按位拷贝。
当将h拷贝到函数框架中的参数表位置时不会触发类的构造函数。
但当f()执行完成之后会触发在栈中的参数表位值的对像。 故此段代码中构造函数执行类1次而析构函数执行类3次。
如果我们没有定义拷贝构造函数,那么系统会认为我们使用按位拷贝。若类中有拷贝构造函数那么由此类创建新对像时编译器会调用拷贝构造。
临时对像:
考虑到当返回非内建类型时的函数框架,如果只调用函数而不使用返回值就像 f()。因为新的函数框架中有返回值地址而这个值是在调用函数之前就压栈的,故编译器需要知道一个这样的地址。一般的情况是编译器自动生成一个临时的对像并将这个地址传递给返回值地址。
指向成员的指针:
- 指向成员数据的指针: 一般的指针都指向一个确定的内存地址,然而指向类中成员的指针却没有指向一个实际的地址,成员指针只有相对于对像而言的偏移。故成员指针只有在和对像相结合后才能得倒实际的地址。
-
objectPointer->*pointMember;
先读
*pointMember
这表示pointMember所指向的成员,注意pointMember是指针,而*pointMember就是成员。 -
object.*pointMember;
与上面的语句的意义相同。
成员数据指针的定义:
int objectclass::*pointMember = &objectclass::Member;
// 虽然所成员指针没有地址,但使用&表示取偏移量。
指向成员函数的指针:
- 定义:
int (objectclass::*pointFunMember)(float )const = &objectclass::f;
在这里没有给出f的参数表但由成员指针的参数表可以确定所要定义的成员函数。
第十二章:运算符重载 [Index]
运算符重载只是另一种函数的调用方式。
- 其与普通的函数调用的方式的不同之处在于变量不在括号内而在运算符旁边。
- 在仅包含内建数据类型的语句中的运算符是不能被重载的。例如:7 + 9中的 + 是不能被重载的。
运算符重载中参数的个数由两个因素决定:
- 运算符是一元的还是二元的。
- 运算符是全局函数的还是成员函数。
- 説明:
当重载的运算符作为成员函数的时侯,当前对像
*this
已经默认作为一个参数了。故当运算符是一元的时侯,就不再需要参数类(例如++,--),而在使用时运算符在变量的左侧。还是作为成员,当运算符是两元的时侯,我们需要为运算符重载函数提供一个参数,而这个参数默认位于运算符之右。当运算符为全局时,运算符默认有几个参数,我们就应该提供几个参数,对于正号+
,我们应该提供一个参数:int &operator+(int &a)
。但当使用加号+
时我们应该提供两个参数:int operator+(int a , int b);
对于非条件运算符重载,如果其两侧的参数类型相同,则一般要将其返回类型也设置为与参数相同的类型,避免歧义的发生。
运算符重载注意:
- 不能重载语言当前没有的运算符,这样会使库的使用变得困难,而运算符重载就是为了方便
- 注意关键字this在运算符重载中的应用。
自增和自减:
- 全局自增 ++
- ++a 系统会调用operator++(a) 。看作一元运算符
- a++ 调用operator++(a,int)。看作二元运算符
- 成员自减 --
- --a 系统会调用operator--()。一元
- a-- operator--(int)。二元
- 説明:
对于自加自减运算符,在C++ 中前缀和后缀的实现方法是不同的,为了不产生歧义,在没有右值的情况下 x++ 会产生一个x的临时变量拷贝。然而对于前缀++x而言,即使没有右值,其也不会产生临时变量,这点是需要注意的。系统会自动的将临时变量设置为常量型,而我们只能调用静态常量中的静态函数成员,这点需要注意。
较为少见的运算符重载:
- operator[]
- operator,
operator->
<<
重载这个运算符时最好使用友元形式:class {... friend ostream operator<<(ostream &os ,const current_calss); ...}
指针间接引用运算符一定是成员函数,他有着额外的非典型的限制:他必须返回一个对像或对像的引用,而且这个对像也有一个指针间接运算符;或者返回一个指针被用于选择指针间接引用运算符箭头所指向的内容。这些限制主要可以表现在语法上。 P281
operator->*
P284
不能重载的运算符
- 成员运算符
.
- 成员指针间接引用
.*
- 自定义运算符。
成员运算符和非成员运算符 P286
- 当运算符的左侧是是当前类的对像时,系统会调用成员运算符。
赋值号的重载
C++ 中不允许存在全局的operator=,对于一个没有创建的对像而言,使用等号会触发拷贝构造函数,然而当等号左侧的对像已经存在,那么就会调用重载的赋值号等号。
MyType b
;MyType a = b
;//调用拷贝构造函数a = b
; //使用重载的运算符等号
自动类型转换
第十三章 动态对象创建 [Index]
大部分的new和delete是使用malloc和free实现的故使用delete删除malloc分配的内存的行为是没有定义的,因为这样做很可能没有调用析构函数而直接释放内存。
- void * malloc(unsigned int size);//malloc在分配内存时会记录所分配的内存大小,便于后续的删除。
- new
- MyType *fp = new MyType(1,2);
- MyType *fp = new MyType;//注意如何使用默认构造函数
- delete
- delete fp;//如果fp是NULL则不会发生任何事情。
迭代器 [Index]
-
可以将迭代器看作是高级的指针,但迭代器不能取其地址,同理指针也是迭代器,也有尾后指针。有效的迭代器要么指向某个元素,要么指向容器中的最后一个元素的下一个位置(不是有效的数据,这个迭代器称之为尾后迭代器)。
一般而言容器中都有返回迭代器的成员函数,例如begin()函数返回容器中第一个元素的迭代器,end()函数返回尾后迭代器。在C++11中内建的成员函数cbegin()和cend()函数用于返回常量型的迭代器。
因为在C++中很多的容器中没有重载运算符<
和>
故一般而言,我们使用 == 来判断两个迭代器是否相同。 -
因为迭代器的类和和具体的类有关,故一般在定义迭代器的时候需要配合类的名称来定义迭代器:
vector<int >::iterator it ;
或者vector <int>::const_iterator it ;
- 使用了迭代器的循环体内都不能向对应的容器中添加元素。
- 迭代器it的一个注意事项:it[n] 和(*it)[n] 并不是同一个意思。一个是元素之间,一个是元素内的元素之间。
- 迭代器的大小以迭代器的位置进行判断,与指针的形式相同。
- difference_type 是由string和vector定义的一种带符号整数类型,表示两个迭代器之间的距离,prtdiff_t是指针之间的距离。
C++ keywords [Index]
alignas (since C++11) else reinterpret_cast
alignof (since C++11) enum requires (concepts ts)
and explicit return
and_eq export(1) short
asm extern signed
auto(1) false sizeof
bitand float static
bitor for static_assert (since c++11)
bool friend static_cast
break goto struct
case if switch
catch inline template
char int this
char16_t (since C++11) long thread_local (since c++11)
char32_t (since C++11) mutable throw
class namespace true
compl new try
concept (concepts TS) noexcept (since c++11) typedef
const not typeid
constexpr (since C++11) not_eq typename
const_cast nullptr (since c++11) union
continue operator unsigned
decltype (since C++11) or using(1)
default(1) or_eq virtual
delete(1) private void
do protected volatile
double public wchar_t
dynamic_cast register while
xor
xor_eq