C++学习笔记(更新)

C++学习笔记

第1章到第4章 简介、基本数据类型及运算、分支、循环
第5章 函数
第6章 数组
第7章 指针和C字符串
第8章 递归
第9章 对象和类
第10章 对象和类的更多内容
第11章 继承和多态
第12章 文件输入输出
第13章 运算符重载
第14章 异常处理
第15章 模板

第1章到第4章

不同于C, C++可以用cin >>从键盘读入,用cout <<输出到控制台。
<<>>表示数据流向,并以空格分割数据

cin >> x1 >> x2 >> x3;
  • 两种省略std::前缀的方法
  • using namespace std;
  • using std::cout;
    using std::cin;
    using std::endl;

命名空间的作用

解决命名冲突的问题

标识符的要求

1、包含字母、数字、下划线的字符序列;
2、不能以数字开头;
3、不能使用保留字做标识符;
4、理论上可以无穷长,但具体的编译器会有限制,最好在31位以内,以保证程序的可移植性。

标识符大小写敏感

注意
不要使用以下划线开头的标识符,以免混淆。原因是C++编译器可能为一些内部实体使用这类名字

变量的声明与初始化:

应尽量将变量的声明和初始化放在一条语句中。
另一种完成变量声明同时初始化的语法:
int i(1), j(1); <=> int i=1, j=1;
常量用const关键字声明,一个常量必须在一条语句中声明并初始化

C++提供的基本数据类型有:

数型、字符、布尔值

数据类型占用内存空间的大小可能是变化的,具体于计算机和编译器。

一般:
short 16位
int 32位
long 32位
float 32位
double 64位
long double 80位

使用sizeof()确定一个类型占用空间的大小:sizeof(long)
数型分整型浮点型:

  • 整型有:shortintlong,每种有signedunsighed两种情况
  • 浮点型有:floatdoublelong double

操作数类型的隐式转换规则

1、有long double,转long double
2、有double,转double
3、有float,转float
4、有unsigned long,转unsigned long
5、有long,转long
6、有unsigned int,转unsigned int
7、有int,转int

*手工类型转换

static_cast<type>(value);
int i = 0; int j = (long)i;// C风格的类型转换

类型扩展:由较小值域转向较大值域
类型收缩:与类型扩展相反

类型转换并不能改变被转换变量的值

char类型占用的空间大小是1字节
转意字符

字符含义
\b退格
\t制表
\n换行
\f换页
\r回车
\\反斜线
\'单引号
\"双引号

C++把char型按一个字节大小的整型处理,也就是说,intchar与其他数型之间的“转换中枢”。

<ctime>中的time(0)函数获取从格林威治标准时间1970.1.1.00:00:00至今的秒数

对于bool型,C++内部使用1代表true,使用0代表false
可以将数值赋予bool型变量,零值得到false,非零值得到true

&&||具有短路功能

// 像这样的编码风格是更好的:
bool even = number % 2 == 0;
// 它等价下面的语句:
if(number % 2 == 0)
    even = true;
else
    even = false;

cstdlib中有rand()srand()两个函数,前者用于生成一个正整数。若不改变默认的seed,则每次rand()的结果都相同。

<iomanip>头文件中的流格式控制符:

函数作用
set(width)指定打印字段的宽度
setprecision(n)设置浮点数的精度,n为数字总位数,一直持续到下一个setprecision(n)`
fixed不以科学记数法方式输出,小数部分位数默认为6
showpoint将一个浮点数以带小数点、带结尾0的形式输出,即使它没有小数部分
left左对齐
right右对齐
  • setprecision用于fixed之后时,n代表小数位数
  • showpointsetprecision一起使用
cout << setprecision(6);
cout << 1.23 << endl;
cout << showpoint << 1.23 << endl;
cout << showpoint << 123.0 << endl;

enum声明枚举类型

例子:
enum Day { MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATERDAY, SUNDAY } ;

枚举类型定义的是枚举值列表,每个值是标识符而非字符串

enum Day {...} day = MONDAY;

枚举类型值不能重复声明

枚举值在内存中以整数形式存储,默认是0、1、2、3、4…
可以指定枚举值的整数值:

enum Color {RED = 20, GREEN = 30, BLUE = 40};

如果只部分的为枚举值赋予整型值,则其他枚举值对应的整型值将被默认为前一个值的后继

可以将枚举值赋予到一个整型变量
可以在枚举值之间进行比较,这实际上是进行对应的整型值的比较

哨兵值:使用一个特殊的值控制循环

不要在循环继续条件中使用浮点值的相等性判定,因为计算机中的浮点值只是近似值,这种近似会导致不准确的记数值和结果

ofstreamifstream<fstream>中定义
向一个文件写入数据:

ofstream output;
output.open("numbers.txt");
output << 95 << " " << 56 << " " << 34 << endl;
output.close();

从一个文件读取数据:

ifstream input;
input.open("numbers.txt");
input >> score1;
input >> score2;
input >> score3;
input.close();

使用input对象的eof()函数检测文件尾。

为了正确读取数据,应当知道数据是如何保存的

第5章 函数

void函数不需要return,但可以用“return;”结束该函数

当调用一个函数时,该函数的参数与变量会被保存在位于内存的一个栈中。

引用变量:使形参成为原变量的一个引用,两个变量引用同一个值:

int count = 2;
int & refCount = count;
cout << "count is " << count << endl;
cout << "refCount is " << refCount << endl;    //输出都是2

C++编译器根据函数签名(function_signature = function_name + parameter_list)对函数进行重载

C++编译器总是使用更精确匹配的函数,如有max(int, int)max(double, double),则对于表达式max(1, 2.3)将调用第二个函数。

function prototype:没有函数实现的单纯函数声明。在调用一个函数之前,必须在程序中声明它。

C++允许为函数参数制定默认值,该变量应置于parameter list的末尾:

void printArea(int a, double radius = 1){ }

头文件中不能包含主函数。

cstdlib中的数学函数:
abs()rand()srand()
cmath中的数学函数:
ceil()floor()exp()pow(x,y)log()log10()sqrt()sin()cos()tan()fmod(x,y)

fmod 返回x/y旳余数,如 fmod(2.4,1.3) = 1.1

inline function 不会被调用,而是直接被编译器复制到相应的调用点上,所以可以避免函数调用的开销:

inline void f(int month, int year){
    cout << "month is " << month << endl;
    cout << "year is " << year << endl;
}

C++允许编译器对于过长的函数忽略关键字 inline,具体由编译器决定。

第6章 数组

数组声明及初始化:

double myList[4] = {1.9, 2.9, 3.4, 3.5}; //此时数组大小可以省略不写

数组大小必须是常量。数组仅声明不初始化时值不定。

可以在函数的数组参数前加上关键字 const,告知编译器数组是不允许修改的:

void p(const int list[], int arraySize){
	/** Do something **/;
}
int main(){ 
	int numbers[5] = {1, 4, 3, 6, 8};
	p(numbers, 5);
	return 0;
}

如果 f1 中定义了一个 const 参数,这个参数又被传给了 f2,那么应该将 f2 中的参数也声明为 const

void f2(const int list[], int size){
	/** Do something **/
}
void f1(const int list[], int size){
	/** Do something with f2(list,size) **/
}

选择排序算法:每次循环都将未排序部分的最大元素置于最后或最前。
插入排序算法:反复地将一个新元素插入到一个已排序的子列表中。

二维数组的列是必须要指明的

第7章 指针和C字符串

常量指针:声明与初始化必须在同一条语句中

// pValue中的值不可改变,但 *pValue 中的值可以改变
double * const pValue = &value; 

注意与下面语句的区别

const double * pValue = &value;

返回指针的函数:

int * reverse(int const * list, const int size);

动态内存分配:

int * pValue = new int;
double * list = new double[10]; //这里数组的大小可以是变量

newheap中建立变量空间,而不是stack

释放new分配的空间:

delete pValue;
delete [] list;

内存泄露:某一段内存无法访问也无法释放。

局部变量没有默认值,全局变量默认值为0。

C++处理字符串的两种方式:基于指针、基于string类,前者又称为C字符串。

  • 头文件<cctype>中的常用字符检测函数:

isdigit(c)isalpha(c)isalnum(c)islower(c)isupper(c)isspace(c)isprint(c)isgraph(c)ispunct(c)iscntrl(c)tolower(c)toupper(c)

读取字符串:

char * city;
cout << "Enter a city: ";
cin >> city;
// 上面的方式是以空格作为字符串的结束符,也就是不能读入"New York"。
// 如果想读入空格,可以使用如下函数
cin.getline(char arrary[], int size, char delimitchar)
// 其中 delimitchar 默认为'\n'
cin.getline(city, 30, '\t');
头文件<cstring>中的字符串函数:
int strlen(char *)
char * strcpy(char *, const char *)
char * strncpy(char *, const char *, size_t n)
char * strcat(char *, const char *)
char * strncat(char *, const char *, size_t n)
int * strcmp(char *, const char *)
int * strncmp(char *, const char *, size_t n)
int atoi(char *)
double atof(char *)
long atol(char *)
void itoa(int, char *, int radix)

strcmpstrncmp在比较cg时,返回 -4。

第8章 递归

递归会耗费大量的内存与运行时间。

第9章 对象和类

在一个C++类中,用变量定义data field,用函数定义behavior。
类的constructor应与类名相同,且没有返回类型。

// 定义类:
class Circle{};    //定义类时,最后必须加上“;”
// 创建对象:
Circle circle(5);

类对象的成员的可见性默认为 private

类成员的data field不能在声明时初始化。

 //得到两个不同的对象,这两个对象的data field相同
Circle circle2 = circle1;  
//先创建一个匿名对象,再把这个对象复制给circle2
Circle circle2 = Circle();  

创建无实参的匿名对象时必须在constructor后面加上(); 创建无实参的命名对象时不能有()

// 注意下面两条语句的差别
Circle circle2 = Circle();
Circle circle2;

类的声明与实现可以相互分离,前者描述了类的约定,后者实现这一约定。可实现类的重用。

声明

// Circle.h
class Circle{
public:
    double radius;
    Circle();
    Circle(double);
    double getArea();
};

实现

// Circle.cpp
#include"Circle.h"
Circle::Circle(){
    radius = 1;
}
Circle::Circle(double newRadius){
    radius = newRadius;
}
double Circle::getArea(){
    return radius * radius * 3.1415926;
}

:: 称为二元作用域解析运算符

将类成员函数声明为 inline

  1. 类声明中实现的函数自动 inline;
  2. 在类实现文件中指明成员函数是 inline 函数。
inline double Circle::getArea(){
  return radius * radius * 3.1415926;
}

用命令行编译带有上述辅助文件 Circle.cpp 的主程序时,应指明所有辅助文件:

$ g++ Circle.cpp TestCircleWithDeclaration.cpp -o Test

对象指针

Circle *pCircle = &circle1;

通过指针访问对象成员:

(*pCircle).radius     // pCircle->radius
(*pCircle).getArea()  // pCircle->getArea()

在函数中声明的对象存于 中,在函数返回后,该对象会被销毁。
动态对象存于 中,函数返回后其内部的动态对象不会被销毁:

Circle *pCircle1 = new Circle();

删除动态对象:

delete pCircle1;

string 类中常用的构造函数与成员函数:

// 构造一个字符串:
char chars[] = {'H', 'e', 'l', 'l', 'o', ' ', 'C', '+', '+', '!', '\0'};
string message1;
string message2("Hello C++!");
string message3(chars);            //注意这里chars是字符串的地址
string message('a', 5);            //message.data()="aaaaa"(const)
// 追加字符串:
message.append(message2); //message.data()="aaaaaHello C++!"
message.append(message2, 1, 5); //message.data()="aaaaaHello C++!ello "
message.append(message2, 5); //message.data()="aaaaaHello C++!ello Hello"
message.append(3, 'B');//message.data()="aaaaaHello C++!ello HelloBBB"
// 字符串赋值:
message.assign(chars);//message.data()="Hello C++!"
message.assign(chars, 1, 5);//message.data()="ello "
message.assign(chars, 5);//message.data()="Hello"
message.assign(5, 'G');//message.data()="GGGGG"
// 函数at、clear、erase、empty:
message2.at(3)                     //'l'
message2.erase(5, 4)               //"Hello!"
message2.clear()                   //'\0'
message2.empty()                   //1,检测message2是否为空
// 函数length、size和capacity:
// 前两者作用相同,
message3.length()                  //10
message3.capacity()                //10
message3.erase(5, 4)               //"Hello!"
message3.length()                  //6
message3.capacity()                //10
// 字符串的比较:
string s1("Welcome");
string s2("Welcomg");
s1.compare(s2);                    //-2
s2.compare(s1);                    //2
s1.compare("Welcome");             //0
// 获得字串:
s1.substr(3);                      //"come"
s1.substr(3,3);                    //"com"
// 字符串搜索:
s1.find(char/string, [indexStart]) //从indexStart开始向后查找char或者string,indexStart不设置时默认为0
// 字符串插入与替换:
s1.insert(index, [numberForChar], string/char);
s1.replace(index, numberOfChar, string);

字符串运算符:
[]=++=<<>>、(==,!=,<,<=,>,>=)

<<将一个字符串插入到流,>>从流中提取一个字符串,分界符是 空格空结束符

数据域封装(data field encapsulation):

class Circle{
public:
    Circle();
    Circle(double);
    double getArea();
private:
    double radius;
};

私有数据 只能在定义它的类的内部访问,不能被直接修改。
良好的风格是将类的所有数据域都声明为 private

数据域和成员函数在类中的声明顺序可以是任意的,但最好是先声明 public 再声明 private

类中一个数据域只能声明一个成员变量,成员函数内部的局部变量优先权高于数据域

this 是一个C++内置的特殊指针。

对象作为函数参数具有“按值传递”与“按引用传递”两种方式,注意两种方式的区别。

// 按值:
void printCircle(Circle c){//Do something with the copy of c}
// 按引用:
void printCircle(Circle &c){//Do something with c}
void printCircle(Circle *c){//Do something with point c}

对象数组:

Circle circles[3] = {Circle(1.0), Circle(2.0), Circle(5.9)};

类抽象:将类的实现与使用分离,可视为类的声明
类约定:类之外可以被访问的数据域和成员函数,以及对这些成员的预期行为的描述,用UML图表示
类封装:将类实现的细节封装起来,可视为类的实现

构造函数初始化列表,使用场景:一个数据域是对象,并且该对象没有无实参构造器。

数据域在声明时不能被赋值。
如果一个数据域是对象类型,无实参构造函数会被自动调用,以创建该数据域的对象

class Time{
public:
    Time(int hour, int minute, int second){}
private:
    int hour;
    int minute;
    int second;
};
class Action{
public:
    Action(int hour, int minute, int second)
    :time(hour, minute, second){}
private:
    Time time;
}

第10章 对象和类的更多内容

不可变类

  1. 所有数据域均为私有;
  2. 没有更改器函数;
  3. 没有访问器函数返回指向可变对象数据域的引用或指针。

inclusion guard:使用 #ifndef ... #endif 防止一个头文件被加载两次,例如不确定是否已经有Date.h,可这样写:

#ifndef DATE_H
#define DATE_H
//code here
#endif

静态变量/类变量:使该类的所有实例共享该变量的数据,可用类直接访问。
C++支持静态函数,可使用类直接调用静态函数。
使用 static 声明类变量与类函数,在实现时不用为已经声明 static 的函数重复声明 static

建议直接使用类名访问静态成员,以提高可读性:

totalNumber = Circle::getNumberOfObjects();

析构函数destructor:与constructor相对,用来销毁对象。在没有显式定义析构函数时,会被编译器定义一个默认的析构函数。
析构函数名与构造函数名相同,仅仅在前面多一个“~”(单波浪线)。
destructor没有返回类型与参数。

当程序终止时,所有对象都会被销毁。
栈中的对象不能被人工显式销毁
堆中的对象(使用new)可以被人工delete显式销毁,delete会调用析构函数。
析构函数只能有一个。

拷贝构造函数copy constructor:使用指定的对象初始化一个新的对象:

Circle circle2(Circle &);

默认的拷贝构造函数:将参数对象的每个数据域复制给新建对象中相应的副本。

在没有显式的定义拷贝对象时,编译器会使用默认的拷贝对象。

浅拷贝:仅拷贝源对象的“表层”数据。
深拷贝:拷贝源对象的“多层”数据。

实现深拷贝,需自定义拷贝构造函数。

使用 friend 声明一个类的 友元函数友元类,两者可以直接访问该类的 private 成员:

class Date{
public:
	friend class AccessDate;
	friend void p();
	private:
	int year;
	int month;
	int day;
};
class AccessDate{
public:
	static int getYearOfDate(){
		return Date.year;
	}
};
void p(){
	//do something with Date
}
int main(){
	//undirectly access the field of Date with it's friend p();
}

对象合成:使一个对象包含另一个对象。

合成聚合aggregation(has-a) 的特殊情况,聚合对象、聚合类、被聚合对象、被聚合类。
一对一的聚合是合成,一个对象可以被多个聚合类所拥有。

向量类:包含在<vector>中,比数组灵活,其大小可以按需要自动增长:

vector<int> intVector;

常用的方法:
void push_back(dataType element)
void pop_back()
unsigned int size()
dataType at(int index)
bool empty()
void clear()
void swap(vector v2) //交换两者的内容
[int index] //访问内容,下标从0开始

第11章 继承和多态

Circle继承GeometricObject类:

class Circle: public GeometricObject{};

继承用于建模is-a关系

泛型程序设计:使一个函数可适用于较大范围的对象实参

如果一个函数的参数类型是基类,则该函数可以接收任何一个该基类的派生类对象

派生类 并不继承基类的构造函数,而是仅仅在自己的构造函数中调用基类的构造函数来初始化属于基类的数据域。
程序必须在创建派生类对象前,创建出其基类对象。C++使用成员初始化列表的语法完成这项工作。
只能在类的实现中调用基类的构造函数,不能在类的声明中调用。

// 成员初始化列表,
// Circle在被创建时,首先创建出其基类GeometricObject
Circle::Circle(double radius, string color, bool filled): GeometricObject(color, filled){
	this->radius = radius;
}

派生类的构造函数必须要调用基类的构造函数,若未显式调用,编译器会调用基类的无实参构造函数

构造函数链:每构造一个类实例,所有基类的构造函数会沿着继承链依次被调用,每个基类构造函数的调用均优先于其派生类。
析构函数链:在销毁对象时,析构函数的调用顺序与构造函数链相反。

如果想要把一个类设计为扩展类,则最好要有一个无实参构造器。

// 如果想在派生类中重定义基类的函数,
// 需要在派生类的头文件中添加函数的原型,
// 并在派生类的实现文件中提供函数的新的实现。
string Circle::toString(){
	return "Circle Object";
}
// 派生类对象调用基类中定义的函数
// (注意,该函数已经在派生类中被重定义):
circle.GeometricObject::toString();

动态绑定:在运行时确定调用哪个函数的能力,别名:多态polymorphism
多态:一个函数有多种实现。
使用虚函数与对象指针实现多态

class C{
	public virtual string toString(){return "class C";}
};
class B: public C{
	public string toString(){return "class B";}
};
class A: public B{
	public string toString(){return "class A";}
};
void displayObject(C *p){
	cout << p->toString().data() << endl;
}

如果没有关键字 virtual,程序将根据 引用类型 或者 指针类型 选择方法;如果使用了 virtual,程序将根据引用或指针 指向的对象的类型 选择方法。

虚析构函数:保证在释放派生对象时,能够按照正确的顺序调用析构函数。

函数覆盖: 在C++中,在派生类中重定义一个虚函数

使一个函数能动态绑定

  1. 在基类中,函数必须声明为虚函数;
  2. 函数中引用对象的变量必须包含对象的地址。

基类中的虚函数会在派生类中默认为virtual,所以派生类中重定义的基类虚函数不用添加virtual关键字。

匹配函数签名:利用函数的参数类型、参数个数、参数顺序匹配被重载的函数。
动态绑定一个函数实现:由变量引用的对象的实际类型决定。

动态绑定会花费更多的时间和系统资源。

类中的保护数据域或保护函数可被派生类访问,这种情况用 protected 声明。

可见性关键字/可访问性关键字publicprotectedprivate

抽象函数:因依赖于派生类具体类型,而不能在基类中实现的函数
在C++中,抽象函数被称为纯虚函数,包含纯虚函数的类为抽象类
使用=0指明一个函数是否为纯虚函数

virtual double getArea() = 0;

抽象类不能实例化对象

使用dynamic_cast< objectType >(object)向下转型:

void displayGeometricObject(GeometricObject &obj){
	GeometricObject *p = &obj;
	Circle *p1 = dynamic_cast<Circle*>(p)
	...
}

注意
objectTypeobject类型必须相同,只能是指针。
在类层次间进行向上转型时,dynamic_caststatic_cast的效果是一样的;
在进行向下转型时,dynamic_cast具有类型检查的功能,比static_cast更安全。
不安全的向下转型返回空指针。dynamic_cast允许交叉转型,此时认为不安全,static_cast不支持交叉转型。

NULL是C++的一个常量,值为0,表示一个未指向任何对象的指针。

可以使用 typeid 运算符获得给定对象的类的信息(一个type_info类对象的引用),如显示对象 x 的类名:

string x;
cout << typeid(x).name() << endl;

必须使用<typeinfo>标头文件才能使用type_info类,不能实例化该类,只能通过 typeid 得到其对象,接口:

class type_info {  
public:  
    virtual ~type_info();  
    size_t hash_code() const  
    _CRTIMP_PURE bool operator==(const type_info& rhs) const;  
    _CRTIMP_PURE bool operator!=(const type_info& rhs) const;  
    _CRTIMP_PURE int before(const type_info& rhs) const;  
    _CRTIMP_PURE const char* name() const;  
    _CRTIMP_PURE const char* raw_name() const;                //返回修饰名称,比name更快
}; 

向上转型可以隐式

第12章 文件输入输出

ofstream用于从文件读取数据,类ifstream用于向文件写数据,类fstream用于更新文件中的数据。

可以写入的数据有:基本数据类型,数组,字符串,对象

#include<fstream>

ofstream output;
output.open("scores.txt");
output << "John" << " " << "T" << " " << "Smith" << " " << 90 << endl;
output.close();

cout是预定义的特殊输出流对象

C++中使用文件路径名时应注意,将\转译后使用,如:c:\\example\\scores.txt

考虑到平台的移植性,最好使用相对路径名

ofstream操作一个已经存在的文件后,原有内容将被清除。

读取数据:

#include<fstream>

ifstream input;
input.open("scores.txt");
char firstName[80], mi, lastName[80];
int score;
input >> firstName >> mi >> lastName >> score;
input.close();

cin 是预定义的特殊输入流对象

输入对象和输出对象分别从流中读取数据或输出数据

关闭输入文件不是非必须的操作,但有利于释放文件占用的系统资源

输入流:输入对象
输出流:输出对象

读取数据前,检测文件是否存在:调用输入流的fail()函数,如果文件不存在,其返回true

input.open("test.txt");
if(input.fail()){ /*do someting*/}

读取数据时,检测文件末尾:调用输入流的eof()函数,到达文件末尾时返回true

while(! input.eof()){/*Do something*/}

eof() 函数通过操作系统得知是否已经到达文件末尾

同样可以使用类iomanip格式化输出流:

#include<iomanip>
#include<fstream>
ofstream output;
output.open("formatFile.txt");
out << setw(6) << "Jhon" << setw(2) << "T" << setw(6) << "Smith" << " " << setw(4) << 85;
output.close();

使用输入流对象的getline(array, size, delimitChar)获取文件的字符串
delimitChar会被读入,但不会被保存至array
delimitChar的默认值是'\n'
字符串数组的大小是 size-1

ifstream中的get()读取单个字符:

char get() //return a char
istream * get(char &ch)//read a character to ch
char get(char array[], int size, char delimitChar) //read into array, NO '\0'

istreamifstream的一个基类

ofstream中的put()向文件写入一个字符

void put(char ch)

input.get()从输入文件读入最后一个字符时,input.eof()仍为false,随后若程序试图读取下一个字符时,input.eof()变为true,但get()仍然重复的返回最后那个字符。

使用fstream必须指定文件打开模式

#include<fstream>

fstream inout;
inout.open("city.txt", ios::out);
inout << "Dallas" << " " << "Houston" << " " << "Atlanta" << " ";
inout.close();
inout.open("city.txt", ios::out | ios::app);
inout << "Savannah" << " " << "Austin" << " " << "Chicago";
inout.close();

文件模式有:

标识符描述
ios::inin
ios::outout
ios::appappend
ios::ate打开文件用于输出,如果文件已经存在,移动到文件末尾,数据可写入到文件任何位置
ios::truct如果文件已存在,丢弃文件内容,ios::out的默认方式
ios::binary二进制输入输出

某些文件模式可以用于ifstreamofstream对象

可使用 | 运算符组合多个模式

每个流都包含一个位集合,起到标识位的作用,位的值(0/1)指明流的状态。

标识位描述
ios::eofbit文件末尾
ios::failbit操作失败
ios::hardfail不可恢复性错误
ios::badbit非法操作
ios::goodbit操作成功

I/O操作的状态通过上述标识位表示,可以直接使用,C++提供I/O流对象的成员函数检测这些标识位:

函数描述
eof()fail()bad()good()检测相对应的标识位
clear()将所有标识符置位

为了读写二进制文件,必须使用readwrite函数

fstream binaryio;
binaryio.open("city.dat", ios::out | ios:binary);
char s[] = "Atlanta";
binaryio.write(s, 5);    //s只能是字符数组指针
binaryio.close();

reinterpret_cast运算符是用来处理无关类型之间的转换;它会产生一个新的值,这个值会有与原始参数有完全相同的比特位。
向文件写入非字符数据:首先使用reinterpret_cast将该数据地址转换为二进制I/O需要的字符数组指针:

int value = 199;
binaryio.write(reinterpret_cast<char * >(&value), sizeof(value));

read函数:

binaryio.read(s, 5);    //s被指向读出的数组

可以使用读取非字符数据:

int value;
binaryio.read(reinterpret_cast<char *>(&value), sizeof(value));
cout << value;        //输出value中的整数值

C++允许流对象使用seekpseekg成员函数,随机地向前或向后移动文件指针。前者用于输出流(seek put),后者用于输入流(seek get)

input.seekg(index)
input.seekg(index, ios::baseAddr)
output.seekp(index)
output.seekp(index, ios::baseAddr)

三个定位基址:

ios::beg    // begin
ios::end    // end
ios::cur    // cur

使用tellptellg返回文件指针的当前位置
seekp/g的函数示例:

seekg(100L, ios::beg);
seekg(-100L, ios::end);
seekp(41L, ios::cur);
seekp(100L);

使用如下方式更新数据:

binaryio.open("object1.txt", ios::in | ios::out | ios::binary);

使用write在文件当中的某个位置插入内容时,被插入位置的原内容会被删除。

第13章 运算符重载

可以在类中定义运算符函数,使用operator接运算符命名

是否可以重载运算符
可以重载+-*/%^&|~!=<>+=-=*=/=%=^=&=!=<<>>>>=<<===!=<=>=&&||++--->*,->[]()newdelete
不可重载?:..*::

例:

Rational Rational::operator+(Rational &secondRational){
	return this->add(secondRational);
}

运算符重载不能改变运算符的优先级和结合率,也不能改变运算符操作的预算对象数目。

[]运算符重载:

long Rational::operator[](const int &index){
	if(index == 0)
		return numerator;
	else if(index == 1)
		return denominator;
	else{
		cout << "subscript error" << endl;
		exit(0);
	}
}

[]运算符函数声明为返回一个引用,可以实现对相关元素的赋值,也就是使object[]成为左值:

long & operator[](const int index){}
r2[0] = 5;
r2[1] = 6;

+- 作为 一元运算符时(即表示正负号) 可以被重载,并且该函数没有参数:

Rational Ratioanl::operator-(){
	numerator *= -1;
	return *this;
}

++--分为前缀后缀两种,使用int型的伪参量实现后缀运算,前缀运算不需要任何参数:

Rational Rational::operator();
Rational Rational::operator(int pseudoparameter);

重载流插入流输出运算符(<<>>):

friend ostream & operator<<(ostream &stream, Rational &ratinal);
friend istream & operator>>(istream &stream, Rational &ratinal);
ostream &operator<<(ostream &stream, Rational &ratinal){
	stream << rational.numerator << " / " << rational.denomirator;
	return stream;
}
istream &operator>>(ostream &stream, Rational &ratinal){
	cout << "Enter numerator: ";
	stream >> rational.numerator;
	cout << "Enter denominator: ";
	stream >> rational.denominator;
	return stream;
}

C++中 定义类型转换函数 的特殊语法:无返回类型、函数名就是目标类型、无参数值。

operator double();
Rational::operator double(){
	return doubleValue;
}
Rational r1(1, 4);
double d = r1 + 5.1;
cout << "r1 + 5.1 is " << d << endl;  //r1 + 5.1 is 5.35

默认情况下,赋值运算符=执行从一个对象到另一个对象的逐成员拷贝,且是浅拷贝

// 重载`=`运算符:
const Person operator=(const Person &person)

重载赋值运算符时,必须准确地返回赋值数据的类型

第14章 异常处理

可以抛出任何类型 的值作为异常,如int
当异常被抛出后,程序的正常执行被中断
catch捕获异常,并用catch块中的代码处理异常,处理完毕后继续执行catch块之后的语句

throw类似于函数调用,但调用的是catch块; catch块类似于一个函数,其参数与被抛出的异常值类型匹配。
catch块执行完毕后,继续执行catch块后面的语句

try-throw-catch块模板:

try{
	// code to try;
	// throw an exception with a throw statement or from function if necessary;
	// more code to try;
}
catch(type e){
	// code to process the exception;
}

catch块参数可以忽略,此时catch针对所有被抛出的异常

异常处理允许一个函数为它的调用者抛出一个异常

所有异常的基类是位于头文件<exception>exception
exception的派生类有:

类名描述
runtime_error派生的类有:overflow_errorunderflow_error
bad_allocnew运算符无法分配内存时抛出
bad_castdynamic_cast抛出
bad_typeidtypeid在运算对象为空指针时抛出
bad_exception未知类型的异常
logic_error派生的类有:invalid_argumentlength_errorout_of_range

以上的异常类位于头文件<stdexcept>

  • 抛出的异常不在抛出列表时,C++会调用unexpected函数
  • e.what()函数用来输出错误信息
  • 应尽量使用内置的异常类,可以创建新的异常类,但应派生自已有的异常类
  • C++允许try后接多个catch块,以捕获多种类型的异常
  • 应注意catch块中不同类型的异常的顺序,如果catch块参数属于基类异常,那么它会捕获所有该基类的派生类异常,所以基类对应的catch块应放置在派生类的catch块之后
  • C++会从抛出异常的当前函数开始,沿函数链逆向寻找相对应的异常处理程序,如果在整个函数链中都未找到匹配的异常处理程序,C++会终止程序,并在控制台打印一条错误信息

捕获异常:查找异常处理程序的过程
C++允许异常处理程序重抛出捕获的异常:

catch(TheException &ex){
	perform operations before exits;
	throw;           //重抛出异常
}

异常规约,又称为抛出列表,是函数声明中的一个列表,列出了该函数可以抛出的异常类型,有利于开发者提高程序的健壮性。

returnType functionName(parameterList) throw (exceptionList){
	/*function body*/
}
  • 异常规约要在函数头中声明
  • 如果函数头后的异常规约为throw(),则代表该函数不会抛出任何类型的异常
  • 不带异常规约的函数可以抛出任何类型的异常

使C++调用unexpected函数的两个条件:

  1. 异常规约为throw()的函数内部试图抛出异常;
  2. 函数试图抛出不被包含在异常规约类型中的异常。
  • 如果希望函数调用者能处理异常,则应在函数中抛出异常;
  • 如果能在发生异常的函数内部处理异常,则没必要抛出或使用异常;
  • 不要使用try-catch块来处理简单的、意料中的情况,比如简单的逻辑检测。

第15章 模板

模板template:使函数或类将类型作为参数
函数模板:以关键字template开始,后接参数列表,每个参数前必有class或者typename关键字:

template<typename T>
template<class C>

上述语句又被称为模板前缀T类型参数,在函数中,T可以像普通类型一样使用:

template<typename T>
void sort(T list[], int arraySize){
	//code to sort the list here
}
int list1[] = {3, 5, 1, 0, 2, 8, 7};
sort(list1, 7);
string list2[] = {"Atlanta", "Denver", "Chicago", "Dallas"};
sort(list2, 4);

模板类:用模板前缀声明的类

template<typename T>
class Stack{
	public://function here
	private:
		T elements[100];
		int size;
};

模板类的函数都是模板函数,在具体定义的时候要加上模板声明,且在作用域解析运算符::之前的类名为ClassName<T>

template<typename T>
Stack<T>::Stack(){}
template<typename T>
Stack<T>::empty(){}
template<typename T>
Stack<T>::peek(){}

对于模板类, 声明与定义应当放在一起,原因是某些编译器不支持将两者分离。

在声明一个模板类对象的时候,应当为类型参数T指定一个具体的参数:

Stack<int> intStack;

C++允许为模板类中的类型参数指定一个默认类型:

template<typename T = int>
class Stack{};

使用带有默认类型的模板类声明一个带有默认类型的对象:

Stack<> stack;

在模板前缀中,可以使用非类型参数:

template<typename T, int capacity>
class Stack{
	...
	private:
	T elements[capacity];
	int size;
};
Stack<string, 500> stack;

非模板类与模板类之间可以相互派生
模板类使用友元的使用方式与非模板类相同
模板类中可以有静态成员,每个模板特例化都拥有独立的静态数据域拷贝

  • 28
    点赞
  • 134
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值