一、预处理指令&宏
- C++程序编译前会将程序中以 #开头的预处理指令进行特别处理。
- 宏(替换指令):一些由#开头的预处理指令组成,其功能时以简单的名称来代替常数、字符串、函数等,以节省开发时间
(1)预处理指令
#include 指令
- #include<>:编译时,程序会到默认的系统目录中寻找指定文件。
- #include" ":会首先在当前程序文件的工作目录中寻找指定文件。
#define指令
- 替换指令,用于定义宏名称,并替换程序中数字、字符等。
- define 宏名称 常数值/“字符串”/程序语句
- 还可以定义函数:
#define 宏名名称 函数名
#define NEWLINE cout<<endl; //; 是程序语句中的并不是宏指令的结束
#define COPYRIGHT owner
void owner();
int main(){
COPYRIGHT; //调用宏
NEWLINE://调用宏
COPYRIGHT;//....
return 0;
}
void owner(){
cout<<""<<endl;
...
}
** 宏函数**
- 宏函数可以传递参数。
- 用于替换哪些简单但又经常用到的函数
- #define 函数名(参数表){函数体} //不需要设置参数类型,因为是直接替换
#define RESULT(r1,r2,h)(r1+r2)*h/2.0;
** 条件编译指令 **
- 满足条件时执行。
- #if 和# endif搭配使用 相当于{ }
#if 条件表达式
程序语句块1;
#elif 条件表达式
程序语句快2;
#else
程序表达式3;
#endif
二、自定义数据类型
使用结构来集合各种不同的数据类型,形成新的数据类型。
(1)结构
-
声明方式
struct 结构类型名称
{
数据类型 结构成员呢1;
数据类型 结构成员呢2;
…
}; -
创建结构变量:
struct 结构类型名称
{
数据类型 结构成员呢1;
数据类型 结构成员呢2;
…
}结构变量1;
或:
struct 结构类型名称 结构变量2; -
结构的存取
结构变量.结构成员名称; -
结构指针:
- 首先强调:定义结构并非声明变量,而是定义一种结构类型。
- 以结构为数据类型声明指针变量:
① struct 结构类型名称 *结构指针; - 与一般指针类似,也必须指定结构变量的地址给指针才能间接存取其指向的结构变量的成员。
- 结构指针访问方式:
① 结构指针->结构成员名称;
②(*结构指针).结构成员名称;
-
结构&数组
数组可以做结构成员、也可以声明成结构行数组。 -
结构指针数组
-
结构做函数参数:
- 结构传值:函数类型 函数名(struct 函数类型名 结构变量)//结构变量可省
- 结构传址:函数类型 函数名(struct 函数类型名 *结构变量)//结构变量可省,*不可以
(2)类型定义指令
- 重新定义数据类型,为原有数据类型重新取名。
- typedef 原类型 新定义类型;
(3)枚举
- 枚举类型的定义:枚举类型(enumeration)是 C++ 中的一种派生数据类型,它是由用户定义的若干枚举常量的集合。
- enum 枚举类型名{
枚举成员1,
枚举成员2,
枚举成员3,
…
};
enum 枚举类型名 枚举类型变量;
int main(){
enum Drink
{
coffee=15,
milk = 10,
tea = 8,
water
}my_drink;
enum Drink c_drink;
c_drinf = milk;
cout<<"milk = "<<c_drink<<endl;
c_drinf = coffee;
cout<<"coffee = "<<c_drink<<endl;
}
三、面向对象
1、类
(1)
- class 类名
{
private:
私有成员;
public:
公共成员;
} - 创建类
类名 对象名;
对象名.类成员;
对象名.成员函数(参数表);
(2)范围解析运算符(::)
- 前面的声明中,成员函数在类内定义
- 其实,可以事先在类中声明成员函数的原型,再在类外编写成员函数函数体部分。需要用到 范围解析运算符(::)
class Student
{
private:
int id;
public:
int input_data();
void show_data();
}
void Student::show_data()
{
//程序代码
}
int Student::input_data()
{
//程序代码
}
(3)构造函数
- 初始化类对象的成员函数,用于对类内私有成员设数值;
- 每个类必须有构造函数
- 没有定义构造函数,会自动提供一个没有任何程序语句和参数的构造函数;
- 构造函数必须和类名相同;
- 不需要返回类型,也没有返回值
- 可以重载构造函数,以生成不同的对象。
(4)析构函数
- 对象创建时会在构造函数内动态分配若干空间。
- 对象释放或程序结束时,该空间不会自动释放。
- 必须经过析构函数执行内存释放操作。
- ~类名称()
{
}
2、对象数组&友元关系
(1)
类名 对象名[]
(2)友元函数
- 不是类的成员函数。
- 可以直接使用类的任何数据和函数(包括私有)
- 可以定义在类的任意位置
- friend 函数类型 函数名(参数行)
(3)友元类
- 在类A中使用关键字直接声明类B的原型,B可以使用A的任意数据
- B称为A的友元类。
class A
{
friend class B;
}
class B{
}
3、this指针&静态数据成员
- 创建对象时会自动创建属于它自己的指针
- 引用时使用 this 指令,this指针指向对象本身。
- this->数据成员;
- (*this).数据成员;
(2)静态数据成员
- 类中数据成员声明为静态类型后,该静态数据成员的值将会一直保留下来。直到程序结束或下一次改变。
- 一般类的数据成员不设初值,是在构造函数总初始化
- 静态数据成员必须在程序运行过程中要设初值,并且必须在类外部设置初值
数据类型 类名::静态变量 = 初值;
4、类做函数参数
- 传值调用
函数类型 函数名(类名1 参1,类名2 参2,…){} - 传址调用
函数类型 函数名(类名1 *参1,类名2 *参2,…){}
四、继承&多态
1、基类&派生类
- 基类中数据成员和成员函数可被派生类继承
- 派生类必须具有自己的构造函数和析构函数
- 友元类关系,仅止于基类,也不能被继承。
- 以下特性不能被派生类继承:
- 构造函数
- 析构函数
- 友元类
- 重载赋值运算符
2、单一继承
class 派生类: 继承关键字 基类
{
类定义
}
3、继承关键字
- public: 基类中原数据成员按原类型继承【意思是 权限是什么继承过来就是什么】
- protected:除public 权限降为 protected 外其他不变;但是派生类无法直接存取位于基类中private块中的成员。
- private:基类中所有数据成员和函数都会存储到派生类的private区块中。注意:非派生类的外部成员无法使用派生类的对象对基类进行存取或调用操作,必须通过派生类的public成员间接操作。
4、多重继承
- class 派生类:继承关键字 基类1,继承关键字 基类2,…
{}
5、派生类的构造函数和析构函数
派生类不能继承基类的构造函数和析构函数,必须有自己版本的构造、析构函数。但是针对继承而来的特性,派生类就会调用基类的构造函数和析构函数。
(1)单一继承中
- 创建对象时调用构造函数和析构函数的顺序:
创建:调用基类构造函数->调用派生类构造函数
销毁:调用派生类析构函数->调用基类析构函数
(2)多继承中
- 创建对象时调用构造函数和析构函数的顺序:
创建:调用基类1构造函数->调用基类2构造函数…->调用派生类构造函数
销毁:调用派生类析构函数->调用基类1析构函数->调用基类2析构函数…
6、多态和虚拟函数
- 一般程序中会在基类和派生类中声明相同名称但是功能不同的public成员函数,这些函数称为同名异式或多态
- 使用虚函数是因为:在某个基类创建了某成员函数;并创建了多个由基类所派生出的该成员函数。要选择使用哪个派生类的该成员函数;就需要在基类中把该成员函数声明为虚函数。
(1)静态绑定和动态绑定
(2)声明虚函数
- 虚函数即多态的实现,使我们可以调用相同的函数而执行不同的运算。因为这个成员函数所属的类实例可以被动态链接,而这些派生类又有相同基类。
- 创建虚函数用 “virtual” 来声明。
- 一旦声明为虚函数,则必须在派生类中重载该虚函数。
- 派生类虚函数的参数和返回值必须和基类中声明的虚函数相同。
#include <iostream>
using std::cout;
using std::cin;
using std::endl;
// 基类
class Fruit{
public:
virtual void print(){
cout<< "Fruit" <<endl;
}
};
class Banana: public Fruit{ // 一定要共有继承
public:
void print(){ // 此处可省略virtual关键字,但是函数原型要与Fruit中虚函数 void print(); 完全一致
cout<< "Banana" <<endl;
}
};
class Apple: public Fruit{
public:
void print(){
cout<< "Apple" <<endl;
}
};
class Pear: public Fruit{
public:
void print(){
cout<< "Pear" <<endl;
}
};
class Peach: public Fruit{
public:
void print(){
cout<< "Peach" <<endl;
}
};
int main(void)
{
Banana ban;
Apple ape;
Pear par;
Peach pch;
Fruit *frt[] = {&ban, &ape, &par, &pch};
for(int i = 0; i < 4; i++)
frt[i]->print(); // 一个基类指针,分别调用不同的子类对象(动态多态)
system("PAUSE");
return 0;
}
(3)纯虚函数
- 没有函数功能语句。
- 目的是提供接口。
- virtual 返回类型 函数名称(参数行) = 0;
(4)抽象基类
- 纯虚函数无法在单一类或派生类中声明,只能存在于拥有继承关系的基类中,这种基类称为“抽象基类”。
- 抽象基类包含最少一个或多个纯虚函数
- 派生类继承了抽象基类以后,必须在派生类中“重新定义”(override或覆盖)及“实现”(implement)所继承的虚函数。
(5)虚拟基类
在类的继承中,如果我们遇到这种情况:
“B和C同时继承A,而B和C都被D继承”
在此时,假如A中有一个函数fun()当然同时被B和C继承,而D按理说继承了B和C,同时也应该能调用fun()函数。这一调用就有问题了,到底是要调用B中的fun()函数还是调用C中的fun()函数呢?在C++中,有两种方法实现调用:
(注意:这两种方法效果是不同的)
(1)使用作用域标识符来唯一表示它们比如:B::fun()
(2)定义虚基类,使派生类中只保留一份拷贝。
五、文件
1、数据流
- 代表一个串行数据从源头流向终点。
- 屏幕输出可视为数据从程序流向屏幕
- 键盘输入可视为数据从键盘流向程序
2、文件
- 一种存储数据的单位,能把数据存放在非易失性的存储介质中。
- 文件包含:只读、日期、隐藏等存取信息
- c++文件读取可视为数据流的源头,文件写入就是数据流的终点
- 主文件名.拓展名
- 分类:
存储种类分为:文本文件、二进制文件
按存取方式:安旭式存取、随机式存取
3、文件的输入输出管理
- c++文件的输入输出管理必须包含头文件
- 三个可供文件存取的类:
fstream:可以输入数据到文件,也可以读文件数据到程序。
ofstream:只能把数据输入到文件;
ifstream:只能从文件读取数据 - 创建对象后可以用 ,中的open成员函数打开文件
open(“文件名或完整路径”)
open(“文件名或完整路径”,ios::打开模式) - 打开模式定义在ios类中,模式如下
in:只读,文件不存在发生错误
out:唯写,文件存在删除内容,否则新建文件
app:附加模式,从尾写入,文件不存在就新建
ate:打开并移动文件指针到文件末尾处
trunc:唯写,文件存在删除新建
binary:二进制模式打开
ifstream fileInput;
fileInput.open("finle.txt",ios::in);
4、文件关闭
- 文件执行完成,必须关闭文件
- 文件对象名.close();
5、文本文件操作技巧
(1)文本文件写入
- 将数据写入文件用插入运算符<<
- 文件对象<<写入数据;
ofstream fileOutput;
fileOutput.open("xxx.txt",ios::out);
fileOutput<<"这是写入的数据";
fileOutput.close();
(1)文本文件读取
- 读取文本文件用提取运算符>>
- 文件对象>>读取数据;
- 两个函数:
get(char ch):从文件读取一个字符到ch;
getline(char *str,int size):从文件读一行,遇‘\n’停止,size为str的大小。
char ch;
char data[20];
string str;
ifstream fileInput;
fileInput.get(ch);
fileInput.getline(data,sizeof(data));
fileInput.open("xxx.txt");
fileInput>>str;
fileInput.close();
6、二进制文件操作
(1)写入
- 不能用 << 直接输出
- 文件对象.write((char*)&写入变量,sizeof(写入变量));
- 文件打开模式使用:binary
(2)读取
- 文件对象.read((char*)&写入变量,sizeof(写入变量));
(3)随机读取
- ifstream成员函数
ifstream成员函数 | 说明 |
---|---|
seekp(pos) | 定义文件的写入位置是文件起始处的第pos字节 |
seekp(pos,seek_dir) | 写入位置为seek_dir后的第pos字节 |
pos=tellp() | 获取文件写入位置 |
- ofstream成员函数
ofstream成员函数 | 说明 |
---|---|
seekg(pos) | 定义文件的读取位置是文件起始处的第pos字节 |
seekg(pos,seek_dir) | 读取位置为seek_dir后的第pos字节 |
pos=tellg() | 获取文件读取位置 |
- seek_dir特定位置常数
特定位置常数 | 说明 |
---|---|
beg | 文件起始位置 |
cur | 文件当前位置 |
end | 文件结束位置 |
六、异常处理
- c++中当程序发生异常而无法处理时,此程序会在异常点抛出一个异常,此时,如果我们的代码中包含处理该异常情况的代码区块,系统就会捕获这个异常并加以处理。
- 使用 try…catch 语句捕获和处理异常。
- try 后可以有多个catch块,用以处理不同的异常。
- 发生异常时也可以使用throw关键字将异常抛出,throw后可以接收任意类型的操作数
- 异常的再拋出:
如果一个函数在执行过程中拋出的异常在本函数内就被 catch 块捕获并处理,那么该异常就不会拋给这个函数的调用者(也称为“上一层的函数”);如果异常在本函数中没有被处理,则它就会被拋给上一层的函数。 - 可以在try区块后使用“catch(…)”语句来捕获所有类型的异常
发现一个很好的文章,引用一下。
C++异常处理(try catch throw)完全攻略