下面总结的是初次接触C++时的一些总结,由于是以C语言为基础来认识C++语言的,所以大部分是记录了与C存在差异的地方,以及一些自己认为是比较困难而重要的地方,以此作为自我学习的一次记录。由于多是从自身角度出发的学习总结,内容不会面面俱到,并不建议作为学习材料,有兴趣的朋友建议去学习C++宝典《C++Primer》——
-------------------------------------小见识----------------------------
-
输入和输出语法“<<”和“>>”:
cin记作键盘或输入设备,cout记作屏幕或输出设备,就不会搞乱<<和>>。
cout << “Hello” << “World!”; 连续输出
cin >> a >> b >> c >> d; 连续输入
- C/C++程序中,cin为读入标准输入格式,直到遇到文件结束符时结束运行,而在Windows中CTRL+Z相当于文件结束符EOF,所以你键入CTRL+Z回车后跳出了While循环,才能执行到程序后面的内容。windows认为,如果缓冲中还有其它内容,ctrl+z不表示输入结束,仅代表当前行输入结束,只在单独一个ctrl+z的时候才表示输入结束。
- 即输入数据后按回车,之后在下一行输入Ctrl+Z继续执行程序。
- 断行——凡是能够插入空格的地方都能另起一行。
- 原始字符串字面值:cout << R”(C:\files\)”
- cout << “\n”和cout << endl作用类似,后者的作用是输出一个空行。
- 通过cin将输入读入char类型的变量时,计算机会忽略所有空白间隔和换行符,直到遇到第一个非空字符,并将其读入变量中。
- string类型,定义字符串变量:string day;字符串变量可以通过加号“+”连接并赋给一个新的字符串变量,形成更长的字符串:
string day,day1,day2; day1 = “Monday”; day2 = “Tuesday”; day = day1 + day2; cout << day; 输出:”MondayTuesday” |
- 用cin将输入的内容读入string变量时,除非遇到空白字符(空格,\n,\t),否则计算机会一直读下去。
- 在C++中,任何非零的整数都转换成true,0转换成false。(!time) > limit时,limit、time都是int类型非零值,由于要和int值比较,false会被转换回0,这个布尔表达式等价于:0 > limit
- 一个函数名具有两个或多个函数定义成为重载。对于重载函数,编译器会通过检查调用中的实参个数和实参类型来判断使用哪一个函数。换言之,重载函数名时,函数声明的形参必须有区别,如果两个函数的唯一区别就是返回类型,就不能重载这个函数名。
————————————I/O流——对象和类入门——————————————
- 文件流:头文件——fstream
(1) “输入文件流”变量类型名ifstream;”输出文件流”变量类型名ofstream.
#include<fstream>
……
ifstream inStream;
//将inStream声明用于文件的输入流,作用类似于cin,连接的是输入设备、文件
ofstream outStream;
//将outStream声明用于另一文件的输出流,作用类似于cout,连接的是输出设备、文件
(2) 流变量(如前面声明的inStream和outStream)必须连接到一个文件。这称为打开文件,用open函数实现该操作。假定希望输入流inStream链接到infile.dat文件,程序首先执行以下语句才能从该文件读取输入:
inStream.open(“infile.dat”);双引号下是相对地址或绝对地址
int num;
inStream >> num; //从infile.dat文件中读取一个数给num
num1 = num+1;
outStream.open(“outfile1.dat”);
//如果outfile.dat不存在将新建,存在则覆盖,原文件内容丢失
outStream << num1; //往outfile.dat 文件里输入一个数
- 用成员函数fail测试一个流操作是否失败。Ifstream和ofstream类都有名为fail的成员函数。fail函数不取任何参数,返回一个bool值。open调用失败,fail返回true。调用成功,fail返回false,程序继续。操作如下:
inStream.open(“stuff.dat”);
If(inStream.fail())
{
cout << “Input file opening failed.\n”;
exit(1);
//exit造成程序中止,exit将其实参返回给操作系统。需要include<cstdlib>
}
exit函数是与定义函数,它取一个整数作为参数。根据约定,如果是因为一个错误而调用exit,就使用1作为实参,反之用0;
- 对象是关联了函数的变量,像前面的inStream和outStream。这些函数称为成员函数。
- 类是一种类型,它的变量就是对象。对象所属的类(即对象的类型)决定了成员有哪些成员函数。如前面的ifstream和ofstream,都是类。类定义变量的方式与其他数据如int、double类型一样。(在Python中是通过“=”)
- 上面例子中的圆点叫“圆点操作符”,它前面的是调用对象。在程序中使用成员函数,需要指定一个对象。
- 可以同时打开多个流输入输出。
- 追加到文件:
ofstream outStream;
outStream.open(“important.txt”,ios::app);
存在则在末尾追加内容
不存在则新建
ios::app是特殊常量,需要include<iostream>
- 魔术配方:流变量(如前面的inStream).setf(<格式化标志>);
outStream.setf(ios::fixed); //不用科学计数法表示
outStream.setf(ios::showpoint); //始终显示小数点和其后的0
outStream.precision(2); //保留两位有效小数或保留小数点后两位,视编译器而定
setf的其他格式化标志:
ios::scientific 用科学计数法表示
ios::showpos 正整数输出时带正号“+”
ios::right 结合成员函数width使用,右对齐
ios::left 结合成员函数width使用,左对齐
一些输出流的成员函数:
precision(value);保留多少有效数字,小数点后几位都由value决定
width(value); 该函数告诉流一个输出项需要占多少字符位置,即域宽,默认右对齐,不够空格补,输出项始终不会被截短。
设置的任何标志都可以用unsetf(<要消除的标志>)消除。
- 操纵元:#include<iomanip>
cout << setw(5) << 10;输出域宽为5 的数字10;
cout << setprecision(2) << 1.236;将1.236保留两位小数或两位有效数字输出
- 流可以作为函数实参使用,唯一的限制就是形参必须传引用,流参数不能传值
- 检查文件尾(处理数值数据时):利用inStream >> next(变量),意思是将文件里的内容读入变量next中,如果还有可供读取的数字,返回true,反之(已到文件尾)返回false。多结合while循环做判断条件。
- 输入流的成员函数get和输出流的put:
(1) cin.get(ch);将一个字符读入到变量ch中,包括空白字符和换行符
(2) cout.put(‘a’);将一个字符输出
- 输入流成员函数putback():
该函数获取一个char参数,并将该参数的值放回输入流。该参数可以是能求值的任何表达式。putback()是将字符放回输入“流”,而不是放回输入文件,原始输入文件内容不会发生改变。例:代码一直读取字符,直到(但不包括)第一个空格
fin.get(next);
while(next != ‘ ‘)
{
fout.put(next);
fin.get(next);
}
fin.putback(next);
- 检查文件尾(处理文本时),eof成员函数:输入流成员函数,无参数,判断何时读完文件全部内容,没有更多的输入。例:fin.eof(),一般用来控制循环语句和if-else语句。到达文件尾返回true,反之返回false。例:
if( ! fin.eof() )
cout << “not yet\n”;
else
cout << “end of file”
注意,除非程序试图读取文件尾之后的一个字符,否则fin.eof()不会返回true。假如输入文件内容如下:
Ab
C
一共四个字符Ab<\n>C,当读到C时,fin.eof()依然为false,而当它再想往下读时,已没有字符可读了,这才返回true。文件末尾有一个特殊的“文件尾”标记。成员函数eof只有在读取了这个文件尾标记之后,才会返回true,但这个标记不是普通字符,不能把它与普通字符同样对待,虽然可读取,但不可写出来。系统自动将文件尾标记放在文件的末尾。
检查文件尾代码:
inStream.get(next);
while(! inStream.eof() )
{
cout << next;
inStream.get(next);
}
- toupper()和tolower()的返回值是参数字母大写(小写)的ASCII码。要以字母输出,就要将它指定一个char类型的变量——
char c = toupper(‘a’);
cout << c;
或者进行强制转换——
cout<< static-cast<char>(toupper(‘a’));
————————————————数组—————————————————————
- C++的一种新的for循环,基于范围的for循环,能简化对数组元素的遍历:
int arr[] = {2,4,6};
for(int x:arr){
cout << x;
cout << endl;
cout << "-------------";
}
输出:
246
------------
这里的x是传值参数,这个循环将数组arr里的元素一个个地赋予x,对x操作不改变原数组元素,但如果是传引用:for(int& x:arr) {};那么对x的修改会反映到数组。还可以用const指定修饰指定变量不能修改。输出循环则可以用auto数据类型自动判断数组元素数据类型:for(auto x:arr){};
- 使用部分填充的数组,程序要用另一个int类型的变量来跟踪了解实际使用了数组多少个储存位置。
————————————————字符串——————————————————
- atoi()函数(“alphabetic to int”)获取一个C字符串参数,并返回对应的int值,如atoi(“4”)返回整数4,如参数不对应一个整数则返回0,如atoi(“#4”)返回0,因为#并非整数。而atol()与之功能相似,只是返回的是一个long int值,类似的,atof()是将浮点数字符串转化为浮点数。两个函数在cstdlib库里,需预编译指令:#include<cstdlib>。
- string类:#include<string> using namespace std;
- string类的对象存储了字符串值,可以用=赋值,s1 = “Hello”;可以用+连接两个字符串,s3 = s1 + s2;
- string类的默认函数可以将一个string对象初始化成空字符串。String类的另一个构造函数可以获取一个C字符串参数,构造函数将string对象初始化成一个值,该值代表与C字符串参数相同的一个字符串:
string phrase;
string noun(“ants”);
第一行声明字符串变量phrase,把它初始化为空字符串。第二行将noun声明为string类型,并将它初始化为“ants”,等价于 string noun = “ants”;
- 使用cin和>>只能读取单词,前置空格省略,遇到空格读取结束。
- 使用独立函数getline可以将整行内容读入string类的变量。函数的参数是cin和string类型字符串变量:
string line;
getline(cin,line);
如果该行有前置空格也一样会被读入。getline()有第三个参数(定界符),默认是‘\n’,即遇到行结束符‘\n’时停止读入,以下语句在遇到第一个问号时停止读入:
string line;
getline(cin,line,’?’);
不能用cin和>>读入空白字符串。
使用cin >> n时,会忽略输入中的前置空白符,但会将行内剩下的内容(这里的是’\n’)留作下一个输入,而getline默认以’\n’为定界符结束读入,因此没有newline时,getline读入了‘\n’然后直接结束了。
为避免这种情况除了用自定义函数newline还可以用iostream的成员函数ignore。
其功能:函数用于输入流。它读入字符,直到已经读了num 个字符(默认为1)或是直到字符delim 被读入(默认为EOF).其调用形式为cin.ignore(n,终止字符)
原型:istream &ignore( streamsize num=1, int delim=EOF );
函数作用是跳过输入流中n个字符,或在遇到指定的终止字符时提前结束(此时跳过包括终止字符在内的若干字符)。
如cin.ignore(1000,’\n’);函数一边读取一边丢弃字符,直到读入了’\n’,如果在读取并丢弃1000个字符后还是没遇到’\n’,则函数结束调用。
- string类允许进行各种字符串处理。可采取与访问数组元素一样的方式访问string对象的字符,line[i]就是字符串line的第i个字符(也是从0开始数)。string对象的成员函数length可以返回string对象的长度,line.length().
- 用成员函数at检查非法索引,如果i<str.length() ,str.at(i)等同于str[i],否则越界后返回错误信息,程序异常终止。
- C字符串和string对象之间不能用“=”,
char cstr[] = “Hello”;
string s1;
s1 = cstr; //非法
也不可以在C字符串与string对象之间调用string.h库里的函数,而且string对象不能自动转换成C字符串,如这样的代码是非法的:
strcpy(cstr,s1); //非法
strcpy(cstr , s1.c_str()); //合法,用成员函数c_str()进行显式转换
这时依然有:
cstr = s1.c_str(); //非法
- 字符串和数字之间的转换:
stoi() |
数字字符串变int型 |
stoi(“142”) = 142 |
stof() |
数字字符串变float型 |
stof(“1.42”) = 1.42 |
stod() |
数字字符串变double型 |
stod(“1.2369”) = 1.2369 |
stol() |
数字字符串变long int型 |
stol(“123”) = 123 |
to_string() |
数值类型变字符串,返回string类字符串 |
s = to_string(123) s的值为“123” |
————————————————向量——————————————————
- 向量可视为能在程序运行时改变长度(增大或减小)的数组。
#include<iostream>
#include<vector>
using namespace std;
- 向量的声明:
vector<int> v1; //声明为int型向量
vector<int> v2(10); //声明一个int型,长度为10的向量,各元素被初始化为0
更改向量编号为i的元素:
v[i] = 42; //只能用于更改编号为i的元素,不能用于初始化
在向量中添加元素,要依次添加,用成员函数push_back:
vector<double> sample;
sample.push_back(0.0); //为元素0添加初始值
sample.push_back(0.5); //为元素1添加初始值
sample.push_back(3.2); //为元素2添加初始值
初始化:
vector<double> sample = {1.2 , 3.2 , 6.25};
向量的元素个数称为向量的长度,用成员函数size()确定向量的元素个数,返回一个unsigned int类型的值:
sample.size();
- 向量容量是当前实际分配内存的元素的个数。用成员函数capacity()判断。容量≥长度
- 增大容量:
(1) 下列语句将容量设为至少32个元素:
v.reserve(32);
(2) 以下语句将容量设为当前向量长度加10:
v.reserve(v.size()+10);
当reverse的参数值小于当前容量,不改变当前容量
(3) 成员函数resize()用于更改向量长度,以下语句将向量长度变成24个元素:
v.resize(24);
如果当前长度小于24,新增元素会自动初始化为对应类型的0值;
反之,除了前24个元素,后面的元素全部丢失。
————————————————指针——————————————————
- 操作符new创建一个指定类型的新动态变量,返回新变量的指针。
int *p,*q,*r;
p = new int;
q = new int(12); //将q初始化为12
创建动态数组:
r = new int[10];
//创建一个基类型为int,长度为10的动态数组,当[]内无数字时,默认长度为1
C++规定,当没有足够内存创建新变量时,new默认h会终止程序。
- 用操作符delete销毁动态变量,假如p是指向动态变量的指针:
delete p;
p变成野指针。
删除动态数组r,回收内存:
delete [] r;
- int *p,q; 等价于int* p,q;
- 创建多维动态数组,以二维为例(二级指针):
int *p = new int*[3];
int i;
for(i=0;i<3;i++)
p[i] = new int [4];
最终的数组p是一个3*4的动态数组
————————————————定义类——————————————[1] ————
- 类的成员函数:需在类结构内声明,然后在类结构外定义
class Days
{
public:
void output(); //成员函数声明
int today; //成员函数先于成员变量列出
}
……
void Days::output(): //注意 ::作用域解析操作符
{
cout << “today is “ << today << endl;
//成员函数定义,类中的成员变量可直接使用,不需要圆点操作符
}
::作用域解析操作符,两个冒号之间不能有空格,它指出一个成员函数具体是谁的成员,符号之前的便是函数(output())的所属类型(Days类)
- 理想的类定义应该能自由更改类的实现细节。private:标签之后的变量称为私有成员变量,只能在成员函数的定义中使用。public:指定公共成员,可以在任意地方使用。
- 定义类时,通常将所有成员设为私有。这意味着只能使用成员函数来访问或更改成员变量。取值函数是访问私有成员变量值的成员函数,最好每个类都弄一套完整的取值函数,通常在函数名里带上“get”。赋值函数也应该具备,带上“set”。
- 一个类可将另一个类作为自己的成员变量的类型使用;函数参数可以是类类型;函数可以返回对象,换言之,类可以是函数返回值的类型(返回一个类类型的值)。
- [2] 构造函数用于初始化类的成员变量,它的特点是:构造函数必须与类同名;构造函数定义时不设返回类型,也不能返回值,声明时也不指定任何返回类型,但声明对象时必须提供构造函数要求的参数——BankAccount account1(100,2.3); 。
构造函数在声明类的对象时自动调用,但不能像调用普通成员函数那样调用构造函数, 放在public小节内。
构造函数的定义及声明除了与普通函数有上述的差别外,其他基本一致;对于有初始化 区域的构造函数,它还有另一种定义方式:
BackAccount::BackAccount(): balance(0), interestRate(0)
{
……
}
冒号之后,{ 之前即为初始化区域,上述操作等同于——
BackAccount::BackAccount():
{
……
balance = 0;
interestRate = 0.0;
}
当没有定义构造函数时,编译器会自动生成一个什么事都不做的默认构造函数。
可在无实参的情况下调用的构造函数称为默认构造函数,声明对象而不提供任何实参就会默认调用它。
- 声明对象时,如希望C++使用无参构造函数,决不能加圆括号,用无参构造函数声明对象account:
BackAccount account(100,2.3); ×
BackAccount account; √
而如果在赋值语句中显式调用构造函数(会用到 = ),就必须带圆括号,下列语句将account的所有成员变量初始化为对应的零值:
account = BackAccount()
- 定义类为抽象数据类型的规则:
(1) 所有成员变量都设为私有成员
(2) 用户程序员需要的每个基本操作都设为类的公共成员函数,并完善的规定如何适用每个公共成员函数。
(3) 任何辅助函数都设为私有成员函数
- 类的[3] 友元函数本质上是普通函数,只是能访问那个类的对象的私有成员。为使函数成为类的友元函数,必须在类定义中为友元函数列出函数声明。函数声明前要附加关键字friend。函数声明可放在private或public区。但无论如何他都是公共函数。
- 选择成员函数和非成员函数:
(1) 函数要执行的任务只涉及一个对象,就使用成员函数
(2) 要执行的任务涉及多个对象,就是用非成员函数。
- 类作为参数使用时,有必要使用传引用参数,而不要使用传值参数,如要保证参数不被函数改变,可以在前面加修饰符const,这样定义的参数叫常量参数。
- [4] 重载操作符:要用操作符作为函数名,前面还要加上关键字operator,在前面就要加上相应的基类型。操作符函数的实参要在操作符前后列出;普通函数的实参则是在函数名之后的括号里列出。
- 操作符重载规则
(1) 重载操作符时,至少一个实参必须是类类型。
(2) 重载的操作符可以是类的友元,操作符函数可以是类的成员,也可以是普通函数。
(3) 不能新建操作符,只能重载现有的操作符+,-,*,/等。
(4) 不能改变操作符获取的实参数量。比如,重载%时,不能把它从二元操作符变成一元操作符。
(5) 不能改变操作符的优先级。重载的操作符与原操作符有着一样的优先级。
(6) 以下操作符不能重载:圆点操作符(.)、作用域解析操作符(::)、以及.*和?: 。
(7) “=”“[]”“->”的重载比较特别,要注意区分。
(8) “--””++”的重载的定义只适用于前缀版本,后缀版本要用不同的处理方式。
- 只要操作符返回流,就必须在返回类型名称末尾添加&。为返回类型加&,意思是操作符(或函数)需要返回引用。如果返回类型是流,就不能简单地返回流的值。对于一个流,它的只可能是一个完整的文件、键盘、屏幕,返回这些东西可能是没有意义的。所以我们只希望返回流本身。对于>>和<<的重载,返回值必须是流,返回值名称后必须添加符号&。
- 析构函数是成员函数,在类的对象离开作用域时自动调用。如正确调用了析构函数,析构函数就会调用delete销毁由对象创建所有动态变量。析构函数与它的类同名,但前面要加上~,以便和构造函数区分。与构造函数相似:
(1) 析构函数不能指定返回值类型,甚至不能指定void
(2) 析构函数无参
(3) 每个类只能有一个析构函数,不能为类重载析构函数
(4) 在example类中定义它的析构函数如下:
example::~example()
{
delete [] (theString.)value;
}
theString为example类对象,一般省略。
- 拷贝构造函数在三种情况下会自动调用:
(1) 声明类的对象,并由同类型的另一个对象初始化
(2) 函数返回类类型的值
(3) 在传值形参的位置“插入”类类型的实参。
- 拷贝构造函数定义时是新建一个动态数组,并将一个动态数组的内容拷贝到新建的动态数组中,两个动态数组字符串值相等,但相互独立,互不影响(目的),这叫深拷贝。
- 拷贝构造函数获取(仅仅是)一个传引用参数,该参数具有与类相同的类型。参数必须传引用,通常,该参数也是常量参数,前加const.只要某个函数返回类类型的值,就会自动调用那个类的拷贝构造函数。
- 但凡使用了指针和操作符new的类,都应该有一个拷贝构造函数。
================独立编译和命名空间==================
- ADT类将类的接口和实现区分开。遵循以下三个规则可彻底分离:
(1) 是所有成员变量都成为类的私有成员
(2) 使ADT类的每一项基本操作都成为类的公共成员函数、友元函数、普通函数或重载操作符,任何辅助函数都成为类的私有成员函数
(3) 确保使用ADT的程序员访问不到基本操作的具体实现。实现包括函数定义以及重载操作符的定义(另外包括任何辅助函数或者这些定义的其他项)
(4) 上述解读:
① 隐藏:让用户或其他程序员看到的只是与类有关的函数的名称和参数表,放在接口文件中,其中应添加相应的使用说明。具体的函数主体及实现方法被放到另一个文件(称为实现文件)中
② 私有:所有变量只能通过成员函数访问
③ 接口文件中的内容并不全是实现,private部分就属于实现,私有变量、函数对于其他程序员来说是不能直接使用的,所以也相当于隐藏
- 所有接口文件扩展名需是.h,表明这是一个头文件。预编译指令: #include “bedtime.h”.头文件如果是预定义的,就用<>,如果是自定义的就用””.这两个符号会指导编译器在那寻找到相应的头文件。
- 按照传统,接口文件应与实现文件同名,只是扩展名不同。
- 在接口文件中,用到预定义的库时也需要用#include,此外,调用自定义库时,只需引入.h头文件,即接口文件名。
- 接口文件、实现文件、使用了自定义类的应用程序文件的构成的完整程序的运行:首先编译实现文件,然后编译包含程序main部分的应用程序文件,不需要编译接口文件,因为编译器认为这个接口文件的内容已经被包含在其他两个文件中。实现文件和应用程序文件都包含以下编译指令(以自定义的bedtime库为例):
#include “bedtime.h”
- 命名空间是名称的定义的集合。
- 创建命名空间:
namespace Name
{
Some code;
}
- 当要使用命名空间ns1的fun1,命名空间ns2中的fun2,而不使用命名空间的其他任何东西时,以下语句称为using声明:
using ns1::fun1;
using ns2::fun2;
假定要使用ns1中定义的fun1,但只是用1次(或用少数几次),这种情况下,可在函数名前直接附加命名空间的名称以及作用域解析操作符,如下:
ns1::fun1();
这种形式常见于指定参数类型的时候:
int getNumber(std::istream inputStream)
……
该函数的参数的类型为命名空间std里的istream类型。
- Using声明和using指令的区别:
(1) Using声明只让命名空间中的一个名称进入可用状态,而using预编译指令使那个命名空间中所有名称都进入可用状态
(2) Using声明在代码中引入一个名称,不允许这个名称有其他用途,哪怕是来自不同的命名空间。但using预编译指令,只要在实际中不在代码中使用冲突名称,就不会引发错误。如
using namespace ns1;
using namespace ns2;
哪怕ns1和ns2有着冲突名称,也不会出错
但下列语句是非法的:
using ns1::fun1;
using ns2::fun1;
- 无名命名空间:
namespace //分组1 相当于声明无名命名空间里的函数fun1
{
Void fun1;
}
namespace //分组2 相当于定义无名命名空间里的函数fun1
{
Void fun1
{
……
}
}
无名命名空间的定义的所有名称对编译单元(定义该无名命名空间的文件和文件中被include的其他文件)来说是局部的,所以,这些名称可在编译单元的外部重用于其他用途。
===========================指针和链表===================================
- 当要给未与某一内存地址相关联的指针初始化时,不再用NULL,而用nullptr;当要结束链表时,末位节点也是以nullptr为结束标记。仅对于指针变量。
- 创建链表,与C不同的是,C++创建动态变量只需要用new操作符(创建一个新的动态变量,并返回其首地址)。
Head = new Node;
Head 是表头指针,指向第一个节点,Node为结构体名称
若链表为空,Head=nullptr;
- 迭代器允许遍历数据结构中储存的数据项,针对每个数据项执行指定操作。迭代器可以是某个迭代器类的对象,或者是某种更简单的东西,比如数组索引或指针。
- 删除节点需要两个指针,一个(discard)指向被删除的节点,另一个(before)指向被删除节点的前一个节点。
before -> next = discard -> next;
delete discard; //释放被删除的节点
- 用类构成链表:
//这是一个节点类的接口
namespace linkedlistofclasses
{
class Node
{
public:
Node();
Node(int value, Node *next);
//初始化节点的构造函数
int getDate() const;
//检索这个节点的值
Node *getLink() const;
//检索链表下一个节点
void setDate(int value);
//修改链表中储存的值
void setLink(Node *next);
//更改对下一个节点的引用
private:
int date;
Node *link;
};
typedef Node* Nodeptr;
}//类构成的链表
===========================继承===========================================
- 派生类自动拥有基类的所有成员变量和函数,并可根据需要添加更多的成员函数、成员变量,但一些特殊函数,如构造函数,是不会自动继承的。继承的成员函数和成员变量不在派生类的定义中列出,但它们会自动生成派生类的成员。但要在派生类中更改继承的成员函数的定义,就要在派生类中列出它。派生类可以任意添加新的成员函数和成员变量,新成员函数和成员变量的声明只需在派生类中列出。
- class child : public father
{
……
};
- 重定义:在派生类的定义中,在原基础上,对继承的成员函数做出一些修改、添减;或者干脆使用另一种方法实现。注意的是重定义和基类的函数是有一样的参数表的,如果参数表不一样,那就是重载,派生类中有两个同名函数。
- 假如当你在派生类(child)中重定义了一个名为fun()函数,现在又想调用基类(father)版本的fun函数,那么方法如下 :
child example; //声明一个派生类child的对象
example.fun(); //调用派生类版本的fun函数
example.father::fun(); //调用基类版本的fun函数,关键是“::”
- 基类构造函数不背派生类继承,但可在派生类构造函数的定义中调用基类的构造函数。基类构造函数会初始化从基类继承的所有数据,所以派生类最先干的活就是调用基类构造函数。
- 私有成员函数不会继承。在类的成员函数和成员变量添加限定符protected,那么对于 除派生类之外的其他类或函数,它的效果等同于用private标记成员。也就是说,如果在私有成员函数前面添加限定符protected,那么该类的派生类就算不能继承,也可以通过名称直接访问,现在该类的被限定的私有成员变量也可以在派生类中通过名称访问。但因为这破坏了“隐藏类实现细节”的原则,所以不建议对私有变量进行限定。
- 函数签名:“函数名+参数表中的类型序列”,一般不包括const。
- 不继承的函数:构造函数,私有成员函数,析构函数,拷贝构造函数,重载的赋值操作符也不继承。但重载的赋值操作符和构造函数可以在派生类的重载赋值操作符及拷贝构造函数的定义中使用。
- 虚函数是某种意义上能在定以前使用的函数。将函数指定为virtual,相当于告诉编译器:“我现在不知道这个函数该如何实现。等它在程序中使用时,再从对象实例中获得它的实现方法。”
下列代码来自菜鸟教程:
#include <iostream>
using namespace std;
class Shape {
protected:
int width, height;
public:
Shape( int a=0, int b=0)
{
width = a;
height = b;
}
virtual int area() //虚函数area()
{
cout << "Parent class area :" <<endl;
return 0;
}
};
class Rectangle: public Shape{
public:
Rectangle( int a=0, int b=0):Shape(a, b) { }
int area () //这是area函数在Rectangle类的实现
{
cout << "Rectangle class area :" <<endl;
return (width * height);
}
};
class Triangle: public Shape{
public:
Triangle( int a=0, int b=0):Shape(a, b) { }
int area () //这是area函数在Triangle类的实现
{
cout << "Triangle class area :" <<endl;
return (width * height / 2);
}
};
// 程序的主函数
int main( )
{
Shape *shape;
Rectangle rec(10,7);
Triangle tri(10,5);
// 存储矩形的地址
shape = &rec;
// 调用矩形的求面积函数 area
shape->area();
// 存储三角形的地址
shape = &tri;
// 调用三角形的求面积函数 area
shape->area();
return 0;
}
运行结果:
Rectangle class area
Triangle class area
- 纯虚函数:在基类中的一个公共成员函数如下:virtual int area() = 0;
= 0 告诉编译器,函数没有主体,上面的虚函数是纯虚函数。
- 在C++中顺利使用虚函数:
(1) 在派生类的函数声明中,则可以不添加virtual。函数在基类中virtual,在派生类中自动virtual,但为了澄清,最好也在派生类中virtual。
(2) 保留字virtual必须在函数声明中添加,不要在函数定义中添加
(3) 除非使用保留字virtual,否则不能获得虚函数
- 派生类的对象赋给基类的对象,会发生切割问题,即在派生类中基类所没有的会被切割掉。假如Pet是Dog的基类,那么下面语句通过创建一个新的动态变量,可以避免切割问题:
Pet *pPet;
Dog *pDog;
pDog = new Dog;
pDog -> name = “Tiny”;
pDog -> breed = “Great dane”;
pPet = pDog;
- 析构函数需是虚函数,因为基类的析构函数只会释放基类型的动态空间,而无法释放派生类的。另一方面,把基类的析构函数标记为virtual,那么它会根据动态空间的类型而改变,如果是基类的那么析构函数也是基类的,如果是派生类的,那析构函数也自动变为相应派生类的,从而保证所有的动态空间能够释放。
析构函数的定义:
example::~example()
{
delete [] value;
}
析构函数类型example会根据要删除的动态空间而改变,只要定义好相应析构函数。
========================异常处理=================================
- 异常处理三段式:try - throw - catch:
这三个关键字构成异常处理的核心,在try模块中,程序运行至if语句,如果milk<=0,则抛出异常。执行throw语句后,该语句所在try语句就会停止。try语句之后紧跟的catch块捕捉,并开始运行catch块,若无异常,throw语句不会执行,catch块也不会执行。catch块称为异常处理程序。
标识符e称为catch块参数(但事实上e不是参数,也可以用任何合法的标识符代替),catch块参数:
(1)catch块参数之前要附加类型名,指出catch块能捕捉什么类型的抛出值,故要求抛出值与catch块参数的类型须一致;
(2)catch块参数为捕捉的抛出值指定了名称(e),所以在catch块中写代码,可对捕捉的抛出值的值进行处理。
- 多个catch块:根据抛出值类型,按顺序比较catch块参数,并运行相应的catch块。
try - (略)throw (略)- catch(int x)- catch(double y) - catch(char ch) - catch(...)
最后一个catch(...)是一个默认catch块,如果前面的catch块不没有捕捉到抛出值,就有它来捕捉,不限类型,如果catch(...)放在中间,那么它后面的catch块都不执行,故应放在最后。
- 异常类不设成员函数和成员变量(默认构造函数除外),当异常类作为抛出值时,catch块参数可以不需要,只在catch的()里填写该类型名称。
- 在函数中抛出异常,函数放入try块中,throw放入函数定义中。这时的函数声明应展现一个异常规范:
double safeDiviside(int top, int bottom) throw(DivideByZero);
……
double safeDiviside(int top, int bottom) throw(DivideByZero)
{……}
异常规范要同时放在函数声明和函数定义中,在throw()内用逗号隔开多个异常类。如果没有任何异常规范(throw列表),就连一个空白的异常规范都没有,void fun(); ,效果等同于在异常规范中自动列出所有可能的异常类型,;换言之,抛出的任何异常都会得到正常处理。
另一方面,如果异常规范为空,即throw列表为空,void fun() throw(); ,那么当有异常抛出时,程序直接停止,这种异常称为未处理异常。
派生类的对象也是基类的对象,所有如果类D是类B的派生类,异常规范中有类B,那么当抛出D类时,也会得到正常处理,但不会进行自动类型转换。
对于一些编译器,异常规范不起作用。
- 在派生类重定义或重写时,不可在异常规范中添加新异常。这是因为,能使用基类对象的任何地方都能使用派生类对象。(小集合推出大集合)
- 在catch块中抛出新的异常是合法的。
100.异常能不用就不用,切忌滥用。(参考goto)
==========================模板=========================
101.模板前缀template<class T>
#include<iostream>
using namespace std;
//交换两变量的值
template<class T>
//模板前缀,ANSI建议的是将class换成typename,但class已被大多数人习惯
void swapValues(T& v1, T& v2)
{
T temp;
temp = v1;
v1 = v2;
v2 = temp;
}
//模板要放在main前面
int main()
{
int a = 1,b = 2;
char ch1 = 'a', ch2 = 'D';
cout << "a = " << a << "\tb = " << b
<< endl;
swapValues(a,b);
cout << "Now a = " << a << "\tb = "<< b
<< endl << endl;
cout << "ch1 = " << ch1 << "\tch2 = " << ch2
<< endl;
swapValues(ch1,ch2);
cout << "Now ch1 = " << ch1 << "\tch2 = "<< ch2
<< endl;
swap(ch1,ch2);
//注意swap是C++的自带函数 ,和swapValues功能一样
cout << "Now ch1 = " << ch1 << "\tch2 = "<< ch2
<< endl;
return 0;
}
102.在模板前缀中用T做类型参数,但不做强制要求。函数模板可以有多个类型参数:template<class T1,class T2>,绝大多数模板都只需要一个类型参数,不能有未使用的模板参数。
103.可以将模板的定义和实现都放在同一个文件中,然后在用到的程序中include。
104.用于数据抽象的模板,一样要在前添加template<class T>,类模板的定义与普通类的差不多,而在声明类的对象时,要指明要为T填充的是什么类型:
Pair<int> score;填充int类型
Pair<char> seats;填充char类型
类成员函数的定义:
template<class T>
void Pair<T>::fun(int i){
……
}
作用域解析操作符之前的类型是Pair<T>
模板类做参数:
template<class T>
T addUp(const Pair<T>& thePair){
……
}
==================标准模板库================================
105.迭代器是指针的泛化形式,设计迭代器是为了隐藏实现细节,提供在所有容器类中都一致的迭代器接口。
106.每种容器类都有自己的迭代器类型,虽然迭代器不是指针,但可以把它当指针用。操作迭代器如下:
(1) 前递增和后递增操作符++,将迭代器一直下一个数据项
(2) 前递减和后递减操作符--,将迭代器一直上一个数据项
(3) 相等操作符==和不等操作符!=,测试两个迭代器是否指向同一个数据位置
(4) 提领操作符*,假如p是迭代器变量,使用*p可访问p处的数据。
107.许多容器类都提供以下成员函数来返回迭代器对象(迭代器值),指向数据结构中特定数据元素。
(1) c.begin()返回容器c的迭代器,该迭代器指向容器c的“第一个”数据项
(2) c.end()返回某个东西来测试迭代器在什么时候越过容器c的最后一个数据项
(3) 可以用这样一个for循环遍历容器对象c中所有的元素:
for(p = c.begin(); p != c.end(); p++){}
108.106.声明vector类型的迭代器变量 p : vector<int>::iterator p;
109.迭代器类别:正向迭代器(支持++),双向迭代器(支持++,--),随机访问迭代器(支持++,--和随机访问) ,不同的容器类拥有不同的迭代器,vector(向量)的迭代器是随机访问迭代器。上述三种迭代器又分两个类别:常量迭代器和可变迭代器。前者只能访问迭代器所指的内容*p,不可做出修改;后者可通过*p赋值等操作修改容器中对应元素。可变迭代器能变为常量迭代器,反之不可行。
常量迭代器:std::vector<char>::const_iterator p ;
逆向迭代:for(rp = c.rbegin(); rp != c.rend(); rp++){}
110.顺序容器:slist,list,vector,deque.
111.stack栈类
112.队列类
113.关联容器set和map:一种简单数据库。它们负责储存数据,比如struct或其他数据类型的数据。每一个数据都有一个关联值,称为该数据的键。数据通过键来检索。键的类型和要储存的数据的类型不一定有任何关系。每个数据可以同时是它自己的键。特别的,在每一个set中,每个元素都是自己的键。
114.set模块类是最简单的容器。它储存不重复的元素。(就像集合)。可像这样指定元素的储存顺序:set<T, Ordering> s; ,其中,Ordering应该是一个具有良好行为的排序关系,它获取T类型的两个参数,返回一个bool值。T是储存的元素的类型。未指定Ordering,就家丁Ordering是<(小于)关系操作符。
115.
116.map实现的是映射。map<string,int> numberMap; numberMap对象可为string值(‘键’)关联不重复的int值。如numberMap[“C++”] = 5; ,把C++ 和 5 关联起来。
117.map对象根据键来排序并储存元素。尖括号<>中可指定第三项,也就是键的Ordering。不指定就用默认Ordering。(map就类似于python中的字典)
118.map类程序
/*map模板类的程序*/
#include<iostream>
#include<map>
#include<string>
using std::cout;
using std::cin;
using std::endl;
using std::map;
using std::string;
int main()
{
string theFruit;
char flag;
map<string,string> fruit;
fruit["Apple"] = "Red";
fruit["Banana"] = "Yellow";
fruit["Watermalon"] = "Green";
fruit["Pear"] = "White";
do{
cout << "Enter a kind of fruit:\n";
cin >> theFruit;
if(fruit.find(theFruit) == fruit.end() )
cout << theFruit << " isn't here." <<endl;
else
cout << theFruit << " is here.\n";
cout << "Continue? (Y/N)" << endl;
cin >> flag;
}while(flag == 'Y' || flag == 'y' );
cout << "Iterating through all fruits: " << endl;
map<string , string>::const_iterator iter;
for(iter = fruit.begin(); iter != fruit.end(); iter++)
{
cout << iter->first << " - " << iter->second << endl;
}
//iter->first是键;iter->second是值
return 0;
}
119.map的初始化与遍历。初始化数据放到一对花括号中,即为初始化列表。还可使用auto和基于范围的for循环来轻松遍历map容器:
map<int,string> personIDs = {
{1,”Walt”},
{2,”Ken”},
{3,”Blunt”}
};
set<string> colors = {“red”,”green”,”blue”};
for(auto p : personIDs)
cout << p.first << “ - “ << p.second << endl;
for(auto p : colors)
cout << p << “ “ ;
输出如下:
1 - Walt
2 - Ken
3 - Blunt
blue green red //按<关系给元素排序,然后输出,b<g<r
120.
121.正则表达式:
122.多线程:
/*多线程hello world*/
#include<iostream>
#include<thread>
using std::cout;
using std::endl;
using std::thread;
void func(int a)
{
cout << "Hello world: " << a << " id = "
<< std::this_thread::get_id() << endl;
}
int main()
{
thread t1(func,10);
thread t2(func,20);
t1.join(); //join()让main函数等待每个线程结束再继续
t2.join();
return 0;
}
输出结果:
Hello world: 10 id = 2Hello world: 20 id = 3
/*多线程hello world与mutex*/
#include<iostream>
#include<thread>
#include<mutex>
using std::cout;
using std::endl;
using std::thread;
using std::mutex;
mutex globalLock;
void func(int a)
{
globalLock.lock();
cout << "Hello world: " << a << " id = "
<< std::this_thread::get_id() << endl;
globalLock.unlock();
}
int main()
{
thread t1(func,10);
thread t2(func,20);
t1.join(); //让main函数等待每个线程结束再继续
t2.join();
return 0;
}
//mutex锁定线程,一次只允许一个线程进入一个代码区域,避免线程输出相互覆盖
输出结果:
Hello world: 10 id = 2
Hello world: 20 id = 3
123.经常需要两个以上的线程。这时可使用线程数组:
//生成10个线程
thread tArr[10];
for(int i = 0; i<10; i++)
tArr[i] = thread(func,i);
for(int i; i<10; i++)
tArr[i].join();
124.可用线程运行一个类:thread t1(r); r为某个类的对象。
125.若有参数,得先初始化:
example r(target,position,10,100);
t = thread(r);
t.join();
example是类,t是线程,有多少个线程,后面就必须跟有多少个join().
126.智能指针:shared_ptr是一个新类,是从自由储存分配对象的包装器。包装器通过引用计数来跟踪其他还有多少个指针在引用对象。计数从零开始,每次有一个新变量引用对象就递增1.类似地,每次有一个变量不再引用对象就递减1.换言之,变量被删除或重新赋值,计数器就递减。计数器归零,对象就可以安全删除,分配的内存就可以归还给自有内存。全部自动进行。
127.shared_ptr<class> next; class是对象的类型,假如有一个Node类的链表,那么shared_ptr<Node> next;就表示next是Node类型的智能指针,注意,这里没有*,但next一样是指针。声明了一个shared_ptr后可以用reset函数重置它,next.reset(new Node(30,nullptr); 这里30是数据域的数据,nullptr是指next所指向的是nullptr。
128.内联函数,当成员函数定义很短时,可以直接写在类定义内,不需要包含类名和作用域解析操作符。
129.为类定义成员函数时,若要引用调用对象,可以用this指针。this指针是指向调用对象的预定义指针,哪怕还没有给类声明对象。假如类的一个私有成员变量为stuff,那么在类定义中,this->stuff 是等同于stuff的。
最难点:创建类,派生类,独立编译,结合命名空间和派生类
构造函数
友元函数
操作符重载
虚函数