目录
正文
一、已经有C了为什么还要有C++
C是面向过程的而C++是面向对象的语言,能够用封装、继承、多态的知识解决实际问题,有时候用C或C++都能解决同一问题,但是用 C++做C的补充可能会好些。
二、C++基本语法
1. 数据类型、常量和变量、语句,运算符和表达式
这部分兼容C,另外多出一种数据类型:类类型,最典型的就是向命令行输入输出的<iostream>了。
2. new和delete
动态内存分配和销毁,C语言用的是malloc()和free(),主要是在动态数组的地方会用到;C++的则是使用new和delete,为指针pointer分配内存空间。一个好的习惯是new和delete成对出现;局部变量在一个作用域结束后会被销毁掉,但是new定义的指针变量的数据是放在堆内存中、程序终止后才被销毁掉
(1) 用法
new 类型 分配一个该类型的对象
new 类型(初始值) 分配一个该类型的对象,并初始化
new 类型[数组大小] 指针将得到该数组的首地址,而且数组的类型、长度已经确定
三、函数
析构函数不能在类里面使用,构造函数在类的外面只能使用一次
1. 函数的基本概念
友元函数:友元函数不是类中的成员函数。
常函数:函数定义的最后有个const,比如int func()const,这种函数不能修改类中的成员变量(除非是mutable声明的变量)、只做简单的返回。
函数的参数:const后面的变量不能做左值;数组、指针做参数会用到*,其他情况尽量用引用&;如果不做左值,返回类型尽量是“const 类型&”。
有副作用:会改变当前类中成员变量,如果要返回用“*this”。
2. 缺省函数
C++函数的参数是可以有默认值的,而且默认值只能是从左往右依次缺。
3. 内联函数
对于语句操作非常少但重用性高的的函数,频繁调用会造成多余的开销,C的解决办法是宏定义、C++的解决办法是内联函数(在函数返回值的前面加上关键字inline);内联函数可以传入带自增自减的实际参数,宏定义的函数这样做可能会导致逻辑上的错误
4. 重载函数、运算符重载
(1)重载函数
函数名相同,但是参数表不同的一组函数,是不是重载函数和返回类型无关;参数表不同指的是参数类型或者参数个数不一样。
(2)运算符重载
有两种重载方式,成员函数重载、全局函数重载,它们的参数都有一定的对应顺序。成员函数重载最多能有一个参数、全局函数重载必须有两个参数并且要定义成友元,比如输入输出重载。
①可以重载的运算符
一元运算符 | ++ -- |
算术运算符 | + - * / % |
关系运算符 | == != < <= > >= |
逻辑运算符 | ! && || |
位运算符 | & | ^ ~ >> << |
赋值运算符 | = += -= *= /= %= &= ^= |= >>= >>= |
其他 | [] () -> ->* new delete |
②一元运算符重载的特殊情况
//一元运算符前缀形式
const 类型& operator++ ()
{
某些成员++;
return *this;
}
//一元运算符后缀形式,before调用的构造函数
const 类型 opetator++(int)
{
类 before(某些成员变量);
某些成员变量++;
return before;
}
//自定义类型转换
operator 类型()
{
return static_cast<类型>(某些成员变量);
}
③输入输出重载
必须使用友元、全局函数重载,因为重载的参数是有顺序的,输入输出重载的左操作数是流对象、不是当前对象。
friend istream& operator>>(istream&,type&);
friend ostream& operator<<(istream&,const type&);
之后在类外面定义函数,由流到对象(返回表达式)或是对象到流(返回流)
④重载赋值运算符
深拷贝:完全复制一模一样的数据内容,这样读写就是自由的、因为读写自己的数据不会影响到同类对象的读写;缺点是需要内存做开销。
浅拷贝:编译器的默认拷贝方式,简单的按成员对象依次拷贝;缺点是遇到指针、结构体等复杂对象可能会出错。
引用计数、写拷贝:这是一种结合深拷贝和浅拷贝的拷贝方式,用指针得到想要的资源并记录引用数、当引用数为0的时候销毁资源;当引用数大于1而且需要写的时候才做深拷贝。
OwnArray<T>& operator=(const OwnArray<T>& obj)
{
if(this!=&obj)
{
//销毁原来的数据
ExdOwnArray();
//更新数据,运算符重载一个特别的地方是,可以访问对方的保护甚至私有对象
arrLen=obj.arrLen;
arr=(T*)malloc(arrLen*sizeof(T));
int i;
for(i=0; i<arrLen; ++i)
{
arr[i]=obj.arr[i];
}
}
return *this;
}
5. 命名空间
为避免函数名冲突引入的一种机制。
定义一个命名空间namespace 空间的名称{…},使用的时候是空间名称::对象,或者提前声明using namespace 空间名称;。
四、类
类体现了封装、继承、多态的特点,定义好类后声明对象即可使用
1.构造函数、析构函数
2.访问限定
(1)访问限定字
public、protected、private,public在类中类外都可以被调用,protected和private在类中可以调用但是受保护、在类的继承中可以体现这一点。另外如果在代码中对象不声明访问限定字,那么它默认是private的。
(2)友元
在类的声明中有些对象前面有关键字friend,这样的对象可以访问这个类的私有数据
(3)句柄
3.const和static限定字
4.类的继承和包含
继承和包含主要目的是代码的复用
(1)继承的用法
class 派生类名称: [访问限定字] 基类名称, [访问限定字] 基类名称,…, [访问限定字] 基类名称
{…初始化基类、其他类、成员对象…};
访问限定字默认是private;基类的初始化写在派生类的初始化列表(函数名:函数或对象(传入数据),…{})中,否则基类将调用默认的构造函数;用于多重继承的基类之间如果有相同的函数名,调用的时候可能出现二义性的问题,可以通过“继承者.父类::函数”的方法来解决;同一个基类如果多次间接继承,避免二义性问题还有一种解决办法,就是虚继承(在访问限定字的前面加上virtual);析构函数的执行顺序和构造函数的调用顺序是相反的。
(2)不同的访问限定字造成的继承差别
基类的private对象只能是基类自己和它的友元可以访问、也就是说private对象不能被继承,所以有了protected这种类型;继承得到的变量也是可以改变它的访问权限的,比如继承得到protect的对象、可以再把它声明成private,方法是“private: 基类::对象”。
访问限定字 | 基类中的public | 基类中的protected |
public | 继承得到public | 继承得到protected |
protected | 继承得到protected | 继承得到protected |
private | 继承得到private | 继承得到private |
(3)不能被继承的对象
构造函数、基类赋值重载函数
(4)包含
这是另一种代码复用的方法,做法是将其他类作为新类的数据对象,显然要实现相同的功能还是要写不必要的代码。所以相比继承,包含通用不易出错、继承常用但有时候难用。主要是多继承存在着复杂的初始化顺序,如果有些类还没有初始化就调用它,这是有问题的。一种解决的办法是用包含,另一种解决办法是所有的基类都有自己的默认构造函数,继承的时候重新初始化它们的数据即可。
五、模板
1. 用法
模板的定义 | template <class T0,T1,...,Tn> | 定义和参数相关的类、函数或结构体(其中T0,T1,...,Tn是自定义的参数类型) |
模板的实例化 | 类或结构体<T0,T1,...,Tn>,函数不需要这样的实例化,因为填入参数的时候就知道对应的类型了 | 其中T0,T1,...,Tn是自定义的参数类型 |
六、输入输出流
需要头文件#include <fstream>;输入输出流最终都需要用close()关闭文件;对错误的输出cerr,ctrl+z中止程序运行、结束符EOF,ctrl+c结束程序运行。
1.istream对象
比如cin、读文件ifstream、字符串输入流istringstream
cin.get(ch); | 接收任意字符,包括不可见字符,输入先写到ch,再到输出流 |
ch=cin.get(); | EOF判断是否输入结束 |
cin.get(char*,int,char delim=’ ’); | 从输入中读取一定长度的字符到字符数组中,读到delim停止读取 |
cin.getline(char*,int,char delim=’ ’); | 从输入中读取一定长度的字符到字符数组中,读到delim停止读取 |
getline(istream&,string&,char delim=’ ’); | 将读到的内容写到字符串,这种方式更实用些 |
cin.read(char*,int); | 从输入中读取一定长度的字符到字符数组中 |
cin.gcount(); | 实际读取的字符数 |
2.ostream对象
比如cout、写文件ofstream、字符串输出流ostringstream
cout.put(ch); | 打印任意字符,包括不可见字符 |
cout.write(char*,int); | 写一定长度的字符串 |
ofstream obj(“文件名”,模式); | 初始化一个ofstream对象并打开文件,模式可以是ios::in只读、ios::out只写、ios:app追加,模式视情况可以省略 |
ofstream obj; obj.open(“文件名”,模式); | 初始化一个ofstream对象并打开文件 |
3.字符串流
需要头文件#include <sstream>,字符串流可以起到一种桥的作用(类型转换,如果stringstream需要循环使用一定要先clear()),写到字符串流再从字符串流中读出;其中.str();将字符串流转换为字符串string,.str(const string&);在字符串流中写入新的字符串内容;string对象.c_str()可以得到char*,char*转换成string是直接赋值。
//这是一个类型转换的示例,其他类型转换成string,string转换成char*就可以进一步变为其他类型
int a=1;
float b=3.14;
double c=8.848;
string str="I am str ";
stringstream format;
format<<a<<" "<<b<<" "<<c<<" "<<str<<endl;
printf("%s",format.str().c_str());
4. 操纵符
一般写在要操作对象的前面
showbaseboolalpha | 布尔值文字输出 |
showbase | 显示进制的前缀 |
showpoint | 总是显示小数点 |
showpos | 正数显示+ |
uppercase | 十六进制0X、科学计数法显示E |
*dec或者dec | 十进制显示 |
hex | 十六进制显示 |
oct | 八进制显示 |
endl | 换行 |
七、异常处理
exit()可以终止程序的运行,但是已经构造的静态对象的析构函数会被调用;abort()也可以终止程序的运行,已经构造的静态对象的析构函数不会被调用;抛出异常会调用terminate(),它的默认行为是调用abort()
1.assert()函数
将正常情况的条件语句写到assert()中,这样条件异常的时候会终止程序的运行并报告错误
2.throw-try-catch
这种方式和上面不同的地方在于发现异常、程序结束前可以自定义一些操作;try{}catch(…){}将捕获所有异常
(1)定义异常,这个地方也可以是类
//定义异常,这是个枚举对象
enum EHstate
{
noError,
zeroOp,
negativeOp,
severeError
};
enum EHstate st=noError;
(2)抛出异常,throw 对象或对象的构造函数;
//抛出异常
int mathFunc(int iv) throw(EHstate)
{
if(iv==0)
{
st=zeroOp;
throw st;
}
printf("iv: %d ",iv);
return iv;
}
(3)处理异常
//处理异常,修改异常后再抛出(有些异常操作不能一次就完成)
void calculate(int op) throw(EHstate)
{
try
{
mathFunc(op);
}
// //拷贝出来的局部变量e
// catch(EHstate e)
// {
// printf("EHstate异常:%d ",e);
// }
//被抛出的异常对象的引用
catch(EHstate& e)
{
printf("EHstate异常:%d ",e);
e=severeError;
//因为修改了引用也就修改了原来的异常对象
throw;
}
}
八、标准模板库STL
1.顺序容器
容器 | 头文件 | 说明 |
vector | #include <vector> | 动态数组 |
list | #include <list> | 列表 |
deque | #include <deque> | 双端队列 |
stack | #include <stack> | 栈 |
queue | #include <queue> | 队列 |
priority_queue | #include <queue> | 优先队列 |