1 字符串与指针
1.1 处理字符与字符串
1.2 标准C++的String类
1.3 指针的概念、指针变量的定义和使用
1.4 指针与数组
2 指针与函数
2.1 参数的传递方式
2.2 返回指针的函数及指向函数的指针
2.3 指针数组与指向指针的指针
2.4 内存的动态分配与释放
2.5 void 与 const 修饰指针变量
3 结构体与列表
3.1 结构体的建立
3.2 结构体的应用
3.3 typedef 定义类型名
3.4 链表
4 文件操作
4.1 文件的打开与关闭
4.2 文本文件输入输出
4.3 文件出错检测方法
4.4 多文件操作
4.5 操作简单二进制文件
4.6 读写结构体文件
4.7 随机访问文件
5 类的基础部分
5.1 类的基本概念
5.2 类的多文件组织方式
5.3 构造函数与析构函数
5.4 重载构造函数
6 类的高级部分
6.1 静态数据成员
6.2 静态成员函数
6.3 友元函数
6.4 对象的赋值问题
6.5 什么是拷贝构造函数
6.6 调用拷贝构造函数的情况
6.7 赋值运算符重载与this指针
6.8 双目运算符重载
6.9 单目运算符重载
6.10 关系运算符重载
6.11 流操作符重载
6.12 类型转换运算符重载
6.13 下标运算符[]重载
7 类的继承、多态、虚函数
7.1 继承
7.2 保护成员和类的访问
7.3 保护成员和类的访问
7.4 初始化列表的作用
7.5 覆盖基类的函数成员
7.6 虚函数与纯虚函数
7.7 多重继承与多继承
7.8 类模板
8 异常处理
9 标准模板STL
9.1 STL概述
9.2 容器
9.3 迭代器
9.4 算法
求字符串的长度
char name[10] = {'T', 'o', 'm', '\0', 'P', 'e', 't', 'e', 'r', '\0'};
cout << strlen(name) << sizeof(name) << endl;
注意:求字符串长度时,到’\0’就会结束
字符串的拷贝
方式一
char* strcpy(char s1[], char s2[])
例如
char src[80] = {"I am a student"};
char dst[80];
strcpy(dst, src);
注意:不能使用‘=’号进行直接赋值
方式二
char* strncpy(char s1[], const char s2[], int len)
例如
char src[80] = {"I am a student"};
char dst[80];
strncpy(dst, src, 10);
dst[10] = '\0';
注意:必须加上’\0’字符串结束标志
字符串的连接
char* strcat(char s1[], const char s2[], int len)
将s2字符串连接到s1的尾部,修改了s1,返回的是s1的首地址
注意:s1的空间要足够大
例如:
char s1[20] = "You";
char s2[20] = "&Me";
strcat(s1, s2);
字符串的比较
方式一
int strcmp(const char s1[], const char s2[])
比较两个字符串的大小,就是从左到右逐个比较对应字符的ASCII码
若s1 > s2,则返回1
若s1 < s2,则返回-1
若s1 = s2,则返回0
方式二
int strncmp(const char s1[], const s2[], int len)
比较两个字符串的前len个字符,若字符串s1或者s2的长度小于len,则与strcmp无异
例如:
char s1[10] = "China";
char s2[10] = "Chinese";
cout << strncmp(s1, s2, 5) << endl;//输出结果为-1
字符串的大小写转换
大写变小写
char* strlwr(char s[])
小写变大写
char* strupr(char s[])
字符串的子串查找
char* strstr(const char s1[], const char s2[])
如果字符串s1包含要查找的子串s2,则返回s2在s1中第一次出现的地址,否则直接返回NULL
字符串转换为整数的函数
int atoi(const char str[])
例如:
char s1[80] = "789123",
s2[80] = "789X123",
s3[80] = "X123";
int i = atoi(s1);//789123
int j = atoi(s2);//789
int k = atoi(s3);//0
转换成别的数据类型就自己类比啦
atof,atod…
整数转换为字符串
char* itoa(int value, char str[], int radix)
例如:
int n = 123;
char s1[20], s2[20];
itoa(n, s1, 3);//处理3进制
itoa(n, s2, 10);//处理10进制
包含的头文件
string
读取整行
string name;
getline(cin, name);
注意:要和char类型做区分
char name[10];
gets(name);
cin.get(name, 10);
cin.getline(name, 10);
string对象的比较
string对象也可以与字符串比较,例如
string name1 = "John";
char name2[10] = "Jone";
cout << (name1 > name2);//0
cout << (name1 < name2);//1
cout << (name1 == name2);//0
string对象的初始化
string test1; //空串
string test2 = "内容"; //使用=
string test3("内容"); //使用引用字符数组作为参数传给构造函数
string test4(test2); //用一个string初始化另一而string
string test5(test2,pos,num); //从test2中的第pos个位置开始,拷贝个数为num个字符
string test6 = test2 + "内容" + test3 //混合初始化
string test7 = test2.substr(pos,num); //从test2中的第pos个位置开始,拷贝个数为num个字符
string test8 = test2.substr(); //参数列表为空则会拷贝test2的整个对象(复制test2的简便方法)
string test9(num,ch); //拷贝num个字符型ch到test9
string类型常用的操作符
=, assign() //赋以新值
swap() //交换两个字符串的内容
+=, append(), push_back() //在尾部添加字符
insert() //插入字符
erase() //删除字符
clear() //删除全部字符
replace() //替换字符
+ //串联字符串
==, !=, <, <=, >, >=, compare() //比较字符串
size(), length() //返回字符数量
max_size() //返回字符的可能最大个数
empty() //判断字符串是否为空
capacity() //返回重新分配之前的字符容量
reserve() //保留一定量内存以容纳一定数量的字符
[ ], at() //存取单一字符
>>,getline() //从stream读取某值
<< //将谋值写入stream
copy() //将某值赋值为一个C_string
c_str() //将内容以C_string返回
data() //将内容以字符数组形式返回
substr() //返回某个子字符串
find() //查找字符
begin() end() //提供类似STL的迭代器支持
rbegin() rend() //逆向迭代器
get_allocator() //返回配置器
指针变量就是存放地址的变量
定义指针变量
例如:
int* pInt;
注意:
指针不知向内存的0号单元,若指针变量值为0或者NULL,则表示空指针
地址值与整型数值不同
无论何种类型的指针都占用4个字节的内存空间
运算符*与&
*:通过指针访问所指变量的数值
&:访问指针内储存的地址
引用指针变量
int x = 30, y = 90;
int *p1 = &x, *p2 = &y, t;
t = *p1;
*p1 = *p2;
*p2 = t;
上述例子实现了p1与p2指向变量的值的交换
访问数组的方式
下标形式
指针形式
两种类型数组
全局数组和静态数组:在静态储存区中被创建
局部变量:在栈上被创建
数据名对应一块内存,其地址与容量在其生命周期内保持不变,只有数组的内容可以改变
指针可以指向任意类型的内存块,其特征是“可变”
指向一维数组元素的指针
数组名代表该数组的开始地址,数组名即时一个指针常量,例如
int a[10], *p;
p = a;
p = &a[0];//二者等价
指针比较
指向同一个数组的两个指针可以进行比较
逆序存放的代码
for(p1 = set, p2 = set+length-1; p1 < p2; p1++, p2--) {
t = *p1;
*p1 = *p2;
*p2 = t;
}
指针变量之间的加法无意义
指向二维数组元素的指针
二维数组元素a[i][j]的表示
//表示地址
&a[i][j]
a[i]+j
*(a+i)+j
//表示数值
a[i][j]
*(a[i]+j)
*(*(a+i)+j)
(*(a+i))[j]
注意:a[0] == a[0][0]
一个通用的二维数组的输出例子
void Print(int* p, int row, int col) {
int i;
for(i = 0; i < row*col; i++, p++) {
if(i % col == 0) {
cout << end;
}
cout << setw(4) << *p;
}
cout << endl;
}
基本类型的变量作函数形参
交换变量x与y的值,采用基本类型的变量做函数形参
void swap(int a, int b) {
int t;
t = a;
a = b;
b = t;
}
这是单向的值传递,形参的变化不会影响实参
引用类型作为函数形参
交换变量x与y的值,采用引用类型做函数的形参
void swap(int& a, int& b) {
int t;
t = a;
a = b;
b = t;
}
通过定义被调用中的参数为引用类型,将主调函数的值改变
指针类型作为函数形参
交换变量x与y的值,采用指针类型做函数形参
void swap(int* px, int* py) {
int t;
t = *px;
*px = *py;
*py = t;
}
通过定义被调用函数中的参数为指针类型,通过间接存取将主调函数的值改变
注意:指针类型做参数形参的另一种形式是数组名做函数参数
指针型函数
一个函数的返回值是某种数据类型的地址值就是指针型函数
int* function(int x, int y) {
return x+y;
}
函数指针
函数的入口地址。函数指针变量就是指向函数入口地址的变量。
int (*fun)(int , int);
int max(int x, int y) {
return x > y?x : y;
}
fun = max;
用法示例
int process(int x, int y, int (*fun)(int, int)) {
return fun(x, y);
}
指针数组
一个数组里面所有的元素都是指针变量称为指针数组
指针数组与数组指针的本质区别就是*与[]的优先级顺序不同
int (*p)[M]; //p是一个数组指针,一个行指针,指向拥有M个元素的一维数组
int* p[M]; //p是一个指针数组,包含有M个指针
main函数的参数
在main()函数头部声明的格式为
int main(int argc, char* argv[])
int main(int argc, char** argv)
//argc表示命令行中字符串的个数
//argv[]指向命令行中的各个字符串
举个栗子,通过命令行参数计算输入数据的和
int main(int argc, char* argv[]) {
int sum, i;
cout << "Command name:" << argv[0] << endl;
for(sum = 0, i = 1; i < argc; i++) {
sum += atoi(argv[i]);
}
cout << "Sum is:" << sum << endl;
}
指向指针的指针
举个栗子
int x, *p = &x, **pp = p;
变量存储空间的分配是系统完成的,不需用户干预
静态变量:编译时分配空间
动态变量:系统运动时分配空间
程序动态申请空间是由用户在编程时安排的
申请多少空间由运行时情况而定,一般通过指针访问空间
new用于动态申请储存空间,delete用于释放new申请的存储空间
//分配单个元素空间
int* iptr;
iptr = new int;
*iptr = 25;
//也可以这样
iptr = new int(25);
delete iptr;
//分配一片连续的空间
int* a;
a = new int[100];
delete []a;
注意:如果函数的参数是一个指针,不要用指针去申请动态内存,否则会因为申请的空间无法释放而造成内存泄漏
void修饰指针
void修饰指针代表是一种不确定类型的指针
任何类型的指针都可以直接赋给它,无需类型转换
void* p1;
int* p2;
p1 = p2;
不能对void指针进行算数操作
指向常量的指针
可以改变指针所指的空间,但不可以通过指针改变现在所指的内容
int i = 6;
const int* p1 = &i;
const int m = 16;
*p1 = 16; //wrong
p1 = &m; //true
常量指针
可以改变指针所指向空间中的内容,但是不能改变指针的指向
char stringA[10] = "abcd", stringB[10] = "xyz";
char* const sp = atringA;
sp = stringB; //wrong
*(sp+1) = 't'; //true
指向常量的指针常量
既不可以修改指针所指的内容又不可改变指针的指向
结构体就是通过定义一种数据类型,把不同的数据作为一个整体来处理
结构体是一种由程序员创建的抽象数据类型
先定义结构体
struct<结构体名称> {
<成员列表>
}; //这里不要忘记了;号
再定义变量
student John, Mary;
struct student John, Mary;
也可以使用无结构体名构造一次性结构体
struct {
int ID;
char name[10];
}John, Mary;
初始化结构体类型的变量
用{}括起来的值对结构体变量进行初始化,例如
student John = {666, "Joe"};
用同类型的结构体变量初始化另外一个结构体变量
student Merry = John;
结构体类型变量及其成员的引用
引用结构体变量的成员
John.ID = 2333;
整体引用结构体变量
student John = {...};
student Merry;
Merry = John;
注意
不能将结构体作为一个整体进行输入或者输出
结构体变量可以用作函数的参数,属于按值传递
函数可以返回一个结构体变量
使用结构体变量作为函数参数的效率较低,因为结构体作为形参需要赋值
结构体数组与指针
定义结构体数组和初始化
student studentList[4];
student studentList[4] = {{...}, {...}, {...}, {...}};
使用结构体数组
//引用元素
for(int i = 0; i < 4; i++) {
cout << studentList[i].name;
}
//指针
student *ps;
ps = stud;
ps++;
语法格式
typedef <原类型名> <自定义的类型名>
栗子:
typedef char NAME[100];
NAME Joe;
//等价于下面
char Joe[100];
注意typedef与#define的不同
关键字typedef在编译阶段有效,具有类型检查的功能
#define是宏定义,发生在预处理阶段,只会进行简单的字符替换,不做任何检查
#define没有作用域的限制,只要是之前预定义过的宏,在以后的程序中都可以使用
typedef则具有自己的作用域
用typedef定义新类型名
typedef只能用于为已知数据类型名定义新的类型名,并没有增加新的数据类型
typedef用于软件移植
比如定义一个叫REAL的浮点类型,在目标平台一上,让它表示最高精度的类型为
typedef long double REAL;
而在不支持long double的第二平台上,改为
typedef double REAL;
在连double都不支持的平台上,改为
typedef float REAL;
链表的引入
数据空间是连续的
实际应用无法确定数组的大小
定义足够大————空间浪费
链表的结构
struct student {
int ID;
char name[20];
student* next; //链表与结构体的区别
};
单向链表
带有头节点的单向链表
不带头节点的单向链表
链表的应用示例
#include "stdafx.h"
#include
#include
using namespace std;
typedef struct node
{
int data;
node *next;
} NODE;
NODE* initlist()
{
NODE *head;
head = new NODE;
head->next = NULL;
return head;
}
NODE *create()
{
NODE *p1, *p2, *head;
int a;
p2 = head = initlist();
cin >> a;
while ((a!=-1))
{
p1 = new NODE;
p1 -> data = a;
p2 -> next = p1;
p2 - p1;
cin >> a;
}
p2->next = NULL;
return(head);
}
void print(NODE* head)
{
NODE *p;
p = head->next;
if (p != NULL)
{
cout << "Output list : ";
while (p != NULL)
{
cout << setw(5) << p->data;
p = p->next;
}
cout << "\n";
}
}
NODE* search(NODE* head, int x)
{
NODE *p;
p = head->next;
while (p != NULL)
{
if (p->data == x)
return p;
p = p->next;
}
return NULL;
}
NODE *insert(NODE *head, NODE *s)
{
NODE* p;
p = head;
while (p->next != NULL&&p->next->data < s->data)
p = p->next;
s->next = p->next;
p->next = s;
return head;
}
NODE *create_sort()
{
NODE* p, *head = NULL;
int a;
head = initlist();
cin >> a;
while (a != 1)
{
p = new NODE;
p->data = a;
head = insert(head, p);
cin >> a;
}
return head;
}
NODE *delete_one_node(NODE *head, int num)
{
NODE* p, *temp;
p = head;
while (p->next != NULL&&p->next ->data!=num)
p = p->next;
temp = p->next;
if (p->next != NULL)
{
p->next = temp->next;
delete temp;
}
else cout << "NOT found";
return head;
}
void free_list(NODE *head)
{
NODE *p;
while (head)
{
p = head;
head = head->next;
delete p;
}
}
int main()
{
//return 1;
NODE *st, *head = NULL;
int num; char c;
cout << "\n creat a list:\n";
head = initlist();
while (1)
{
cout << "\n\t D:Delete I:Insert P: Print S:Search E: Exit\n";
cin >> c;
switch (toupper(c))
{
case'I':
st = new NODE;
cout << "please input a number to be inserted:";
cin >> st->data;
insert(head, st);
break;
case 'D':
cout << "please input a number to be deleted:";
cin >> num;
delete_one_node(head, num);
break;
case 'S':
cout << "please input a number to be search:";
cin >> num;
if (search(head, num) != NULL)
{
cout << "it is in the list. \n";
}
else
cout << "It is not in the list. \n";
break;
case 'P':
print(head);
break;
case 'E':
free_list(head);
exit(0);
default:
break;
}
}
return 0;
}
文件流类型
#include //文件流头文件
ifstream a//输入文件流,只读取文件流
ofstream b//输出文件流,只写入文件流
fstream c//文件流
文件使用过程
ifstream inputFile;
inputFile.open("d:\\costumer.dat");
//or
char fileName[20];
cin>>fileName;
inputFile.open(fileName);
dataFile.open(“info.dat”, ios::out);//只能用于写
dataFile.open(“info.dat”, ios::in);//只能用于读
dataFile.open(“info.dat”, ios::noreplace);//表明如果文件已存在,则不能打开该文件,如果省略该模式,文件内容将被刷新
ios::nocreate//如果文件不存在,则不能创建
ios::app//追加模式
ios::ate//如果已存在,直接跳转到文件尾部
ios::binary//二进制方式
ios::trune//如果文件存在,删除其内容
//上面展示的是先定义对象,再打开文件
//也可以在定义流对象时直接打开文件
fstream dataFile("name.dat",ios::in|ios::out);
ifstream dataFile
dataFile.open("cust.dat",ios::in);
if(!dataFile)
{
cout<<"fail to open";
exit(0);
}
dataFile.close();
为何要关闭文件
文件缓冲区是一块小的内存空间
操作系统限制同时打开的文件数量
使用<<向一个文件写入信息
#include
#include
#include
using namespace std;
int main()
{
fstream dataFile;
dataFile.open("demoFile.txt",ios::out;
dataFile<<”Confucius\n”;//写入一个单词,换行
dataFile<<”Mo-tse\n”;//写入,换行
dataFile.close();
return 0;
}
文件的格式化输出
#include
#include
#include
using namespace std;
int main()
{
fstream outFile(“number.txt”,ios:out);
int nums[3][3]={1234,3,567,34,8,6789,124,2345,89};
for(int row=0;row=3;row++)//向文件输出三行
{
for(int col=0;col<3;col++)
{
outFile<<setw(10)<<nums[row][col]<<” ”;
}
outFile<<endl;
}
outFile.close();
}
检测文件结束
eof()成员函数:检测文件是否已经结尾,无数可读。
int main()
{
fstream dataFile;
char name[81];
dataFile.open(“demofile.txt”,ios::in);
while(!dataFile.eof())
{
dataFile>>name;
if(dataFile.fail())
break;
cout<<name<<”\n”;
}
dataFile.close();
}
采用函数成员读写文件
采用>>读文件的缺陷:空白字符(空格、跳格、换行、回车)是数据之间的分界符,采用>>操作符进行读取时,会忽略空白字符。
dataFile.getline(str,81,’\n’)
str: 从文件中读取的数据将存储在该空间中
81:从文件中最多能读取80个字符
’\n’界符:如果在读满最大字符之前,遇到了界符,那么将停止读取(注意:该参数可选)
#include
#include
#include
using namespace std;
int main()
{
fstream readFile;
char input[81];
readFile.open(“myTextFile.txt”,ios::in);
while(!readFile.eof())
{
readFile.getline(input,81);//从输入获取最多81个字符进入readFile
if(readFile.fail())
{
break;
}
cout<<input<<endl;
}
readFile.close();
return 0;
}
#include
#include
#include
using namespace std;
int main()
{
fstream dataFile("sentence.txt",ios::out);
char ch;
while(cin.get(ch))
{
if(ch=="!")
{
break;
}
dataFile.put(ch);
}
dataFile.close();
return 0;
}
出错检测
流对象 | 内容 |
---|---|
ios::eofbit | 当遇到了输入流的尾部时,设置该位 |
ios::failbit | 当操作失败时,设置该位 |
ios::hardfail | 当出现不可恢复错误时,设置该位 |
ios::badbit | 当出现无效操作时,设置该位 |
ios::goodbit | 当上述所有标记都未设置时,设置该位,表明流对象处于正常状态 |
函数检测状态位
函数名称 | 意义 |
---|---|
eof() | 如果设置了eofbit状态位,该函数将返回true否则返回false |
fail() | 如果设置了failbit或hardfail状态位,返回true否则返回false |
bad () | 如果设置了badbit状态位,该函数将返回true否则返回false |
good () | 如果设置了goodbit状态位,返回true否则返回false |
clear () | 调用该函数,将清楚所有状态位 |
实例
#include
#include
#include
using namespace std;
void showstate(fstream &);
int main()
{
int num=10;
fstream testFile("stuff.dat",ios::out);
if(testFile.fail())
{
cout<<"打开文件失败!";
exit(0);
}
cout<<”向文件中写数据!\n”;
testFile<<num;
showState(testFile);//第1次测试
testFile.close();
testFile.open(“stuff.dat”,ios::in);
if(testFile.fail())
{
cout<<”打开失败\n”;
exit(0);
}
testFile>>num;
showState(testFile);//第2次测试
test>>num;
showState(testFile);//第3次测试
testFile.close();
}
void showState(fstream &file)
{
cout<<”当前文件的状态为:\n”;
cout<<”eof bit:”<< file.eof()<<” ”;
cout<<”fail bit:”<<file.fail()<<” ”;
cout<<”bad bit:”<<file.bad()<<” ”;
cout<<”good bit:”<<file.good()<<endl;
file.clear();//清除出错标记位
}
#include
using namespace std;
#include
int main()
{
ifstream inFile;
ofstream outFile(“out.txt”);
char fileName[81],ch,ch2;
cout<<”请输入文件名:”;
cin>>fileName;
inFile.open(fileName);
if(!inFile)
{
cout<<”打开失败”<<fileName<<endl;
exit(0);
}
while(!inFile.eof)
{
inFile.get(ch);
if(inFile.fail())
break;
ch2=toupper(ch);
outFile.put(ch2);
}
inFile.close();
outFile.close();
}
二进制文件
二进制文件是按照在内存中存储的形式存储,不是按照ASCII纯文本方式存储,文件中
存储的数据是非格式化的。
file.open("stuff.dat",ios::out|ios::binary);//缺少binary则以文本打开
file.read((char *)buffer,sizeof(buffer));
file.write((char *)buffer,sizeof(buffer));
实例
void main()
{
fstream file;
int buffer[10] = {1,2,3,4,5,6,7,8,9,10};
file.open("a1.txt", ios::out | ios::binary);// 创建一个二进制文件
file. write ((char*)buffer,sizeof (buffer));
file.close();
file.open("a1.txt", ios::in|ios::binary);
file.read ((char*)buffer; sizeof(buffer));
for(int count = 0; count < 10; count++)
cout<<setw(6)<<buffer [count];
file.close();
file.open(“a2.txt”, ios::out); // 创建文本文件
for(int i = 0; i < 10; i++)
file<<buffer[i];
file.close();
}
读写结构体记录
结构体数据可以采用定长块存储到文件中
因为结构体中可以包含不同类型的数据,所以当打开这种类型文件时,必须以二进制方式
打开
实例
#include
using namespace std;
#include
#include
#include
struct Info
{
char name[21];
int age;
char address[51];
char phone[14];
char email[51];
};
int main()
{
fstream people("people.dat", ios::out | ios::binary);
Info person;
char again;
if (people.fail())
{
cout << "打开文件people.dat出错! \n";
exit(0);
}
do
{
cout << "请输入数据:\n";
cout << "姓名:"; cin.getline(person.name, 21);
cout << "年龄:"; cin >> person.age;
cin.ignore(); // 略过换行符,why?
cout << "联系地址:"; cin.getline(person.address, 51);
cout << "联系电话:"; cin.getline(person.phone, 14);
cout << "E-mail:"; cin.getline(person.email, 51);
people.write((char*)&person, sizeof(person));
cout << "还要再输入一个同学的数据吗?";
cin >> again;
} while (toupper(again) == 'Y');
people.close();
while (!people.eof())
{
people.read((char*)&person, sizeof(person));
if (people.fail())
{
break;
}
}
people.close();
}
seekp 和 seekg函数:
seekp 函数用于输出文件(写,put)
seekg 函数用于输入文件(读,get)
file.seekp(20L,ios::beg);//把file对象相对于文件头向后偏移20个字节
//文件随机访问模式
ios::beg;//从文件头开始计算偏移量
ios::end;//从文件尾开始计算偏移量
ios::cur;//从当前位置开始计算偏移量
seek实例:
#include
#include
#include
using namespcae std;
int main()
{
fstream file("digit.txt",ios::in);//假设内容是1234567890
char ch;
if(!file) exit(0);
file.seekg (1L, ios::beg );//调整到2位置
file.get(ch); cout<<ch << endl;
file.seekg(-3L, ios::end );//调整到8位置
file.get(ch); cout<< ch << endl;//自动后移
file.seekg(1L, ios::cur );//偏移到0
file.get(ch); cout<< ch << endl;
file.close();
}
tellp 和 tellg 函数:
tellp用于返回写位置
tellg用于返回读位置
tell实例:
#include
using namespace std;
#include
#include
#include
int main()
{
fstream file(“digit.txt”,ios::in);//假设内容是1234567890
long offset;
char ch, again;
if(!ffle)
{
exit(0);
}
do
{
cout<<”当前位置为:”<< file.tellg( )<<endl;
cout<<”输入一个偏移量:”;
cin>>offset;
file.seekg (offset, ios::beg);
file.get(ch);
cout<<"当前字符是:"<< ch;
cout<<”\n是否继续?”;
cin>>again;
}
while(toupper(again) = ‘Y’);
file.close();
}
过程化程序设计的缺陷
1、大量的全局变量;
2、程序复杂:程序员难以理解成百,上千的函数;
3、程序难以进行修改和扩充。
4、过程化设计是以过程为中心(函数)。
类是一种用户自定义类型,声明形式为
class 类名
{
内容;
}
类的内容
public:可以在类外通过对象访问
private:只能通过类的成员函数访问
protected:与private类似,但在继承时保持功能
定义成员函数:
class Rectengle
{
private:
float width;
float length:
float area;
public:
void setData(float w,float l)
{
width=w;
length=l;
}
void calcArea( );
float getWidth( );
float getLength( );
float getArea( );
};
class Rectengle
{
private:
float width;
float length:
float area;
public:
void setData(float,float);
void calcArea( );
float getWidth( );
float getLength( );
float getArea( );
};
void Rectangle::setData(float w, float l )
{
width=w;
length=l;
}
float Rectangle::getWidth( )
{
return width;
}
定义对象:
int main()
{
Rectangle box;
float wide,boxLong;
cout <<"请输入长和宽?";
cout <<"请输入长和宽?";
box.setData(wide, boxLong);
box.calcArea( );
cout << "矩形的数据:\n";
cout << "宽: "<< box.getWidth() << endl;
cout << "长:" << box.getLength( ) << endl;
cout << "面积: " << box.getArea( ) <<endl;
}
组织方式:
类的定义存储在头文件里(类定义文件)
成员函数定义存储在.cpp文件(类的实现文件)
应用程序通过#include包含头文件,将类的实现文件和主程序进行联编,从而
生成一个完整的程序
定义
构造函数:是一个函数成员,在对象创建时,采用给定的值,自动调用该
函数将对象中的数据成员初始化
析构函数:也是一个函数成员,当对象终止时,将自动调用该函数进行
“善后”处理
构造函数的特点
1.构造函数是与类同名的函数成员;
2.没有返回值类型,也没有void;
3.如果构造函数没有参数,则称为缺省构造函数;
4.如果程序中未声明,则系统自动生成一个缺省形式的构造函数;
5.构造函数允许为内联函数、重载函数、带缺省形参值的函数。
构造函数实例:
class Invoiceltem
{
char *desc;
int units;
public:
InvoiceItem( )
{desc = new char [51]; }
void setInfo(char *dscr, int un)
{
strcpy(desc, dscr);
units = un;
}
char *getDesc( ) { return desc;}
int getUnits( ) { return units:}
};
int main()
{
Invoiceltem stock;//定义时候自动调用
stock.setInfo( "鼠标"
, 20);
cout << stock.getDesc( ) << endl;
cout << stock.getUnits( ) << endl;
}
InvoiceItem *ptr;
ptr=new InvoiceItem;//此时调用构造函数
析构函数的特点
1、析构函数也与类同名,前面多一个波浪号(~)
2、当一个对象终止时,系统自动调用析构函数对此对象做“善后”处理。
3、与构造函数一样,析构函数也没有返回值类型。
4、析构函数无参数。
5、一个类只能有一个析构函数。
6、delete对象时,将调用析构函数。
析构函数实例
class Invoiceltem
{ char *desc;
int units;
public:
InvoiceItem( )
{desc = new char [51];
cout << “构造函数\n;} ~InvoiceItem( )
{delete []desc;
cout << “析构函数\n“;}
//其他函数略
int main()
{
Invoiceltem stock;
stock.setInfo( "鼠标"
, 20);
cout << stock.getDesc( ) << endl;
cout << stock.getUnits( ) << endl;
}
带参数构造函数
常常需要把一些数据传递给构造函数,用于初始化对象的成员。
构造函数可以有缺省参数。
实例
class Sale
{
float taxRate,total;
public:
Sale(float rate)
{
taxRate=rate;
}
}
一个类中可以定义多个构造函数
缺省构造函数的表现形式
1、如果类中没有定义构造函数,系统将提供一个无参构造函数(属缺省构造函数),该函数不实现任何功能。如果用户自定义了一个构造函数,那么系统缺省的构造函数将失效。
2、如果类中定义有无参的构造函数,那么该构造函数也是缺省的构造函数。
3、如果类中定义有带参的构造函数,并且所有形参均具有缺省值,那么该构造函数也属于缺省的构造函数。
4、一个类只能有一个缺省构造函数,否则将产生二义性。
出错实例:
class A;
{
int a;
public:
A(){a=0;}
A(aa=1){a=aa;}//两构造函数产生歧义,因为有缺省值,也属于缺省构造函数
}
创建对象数组时,数组中每个元素(对象)都将调用构造函数。
如果没有为数组元素指定初始值,元素便使用缺省值来初始化,即调用缺省构造函数。
当数组中每一个对象被删除时,都要调用一次析构函数。
用关键字static声明;同一个类中的所有对象都共享该变量;
必须在类外定义和初始化,用(::)来指明所属的类。
静态变量不依赖于对象而存在,无论是否定义该类的对象,这种类型的变量都存在。
静态数据成员实际上是在类外定义的一个变量,它的生存期和整个程序的生存期一样,在定义对象之前,静态数据成员就已经存在。
实例
class StaticDemo
{
static int x ;
int y ;
public:
void putx( int a){ x=a ; }
void puty( int b ){ y=b ; }
int getx( ) { return x ; }
int gety( ) { return y ; }
} ;
int StaticDemo::x ;// 静态变量x将被StaticDemo类的所有对象共享
int main()
{
StaticDemo obj1, obj2 ;
obj1.putx(5) ;
obj1.puty( 10) ;
obj2.puty(20 ) ;
cout << "x: "<< obj1.getx( ) << " " << obj2.getx( ) << endl ;
cout << "y: "<< obj1.gety( ) <<" "<< endl;
}
静态函数成员是类中的一个函数,有static修饰。
静态函数成员和静态数据成员类似,在对象生成之前也已经存在。这就是说在对象
产生之前,静态的函数成员就能访问其它静态成员。
类外代码可以使用类名和作用域操作符来调用静态成员函数。
静态成员函数只能引用属于该类的静态数据成员或静态成员函数。
对于静态的函数成员,是通过类名和作用域分辨符调用的。
也可以采用对象点的方式调用
实例
class Budget
{
static float corpBudget;//预算的总额
float divBudget;//非静态成员
public:
Budget( ) { divBudget = 0; }
void addBudget( float b)
{
divBudget += b;
corpBudget += divBudget;
}
static void mainOffice( float );//静态的函数成员
float getDivBudget( ) { return divBudget; }
float getCorpBudget( ){ return corpBudget;}
};
float Budget::corpBudget = 0 ;//静态数据必须在类的外部进行定义和初始化
void Budget::mainOffice(float moffice)
{
corpBudget += moffice;//只能使用静态变量或其他静态函数或自己的局部变量
}
void main( )
{
float amount;
int i;
float bud;
cout << "Enter main office's budget request: ";
cin >> amount;
Budget::mainOffice(amount);//调用静态成员函数
Budget divisions[4]; //定义一个数组,一个总公司有4个子公司
for ( i = 0; i < 4; i++)
{ cout << "Enter the budget for Division ";
cout << (i + 1) << " " ;
cin >> bud;
divisions[i].addBudget(bud);
}
cout << "\n Here are the division budget :\n";
for ( i = 0; i < 4; i++)
{
cout << "\t 子公司" << (i + 1) << "\t 预算";
cout << divisions[i].getDivBudget( ) << endl;
}
cout<<''\t 公司总预算:'';
cout<< divisions[i].getCorpBudget( )<<endl;
友元函数不是类中的函数成员,但它和类的函数成员一样,可以访问类中定义的私有成员。
友元函数可以是一个外部函数,也可以是另外一个类的函数成员。
若一个类为另一个类的友元,则此类的所有成员都能访问对方类的私有成员。
friend 类型 函数名字(数据类型)
外部函数作为类的友元实例:
class Point
{
int xPos, yPos ;
public:
Point(int xx=0, int yy=0 )
{ xPos=xx; yPos=yy; }
int GetXPos( ) { return xPos; }
int GetYPos( ) { return yPos; }
friend double Distance(Point &a, Point &b); //表面上看是类的成员函数
};
double Distance( Point & a, Point & b)//注意没有作用域分辨符
{
double dx=a.xPos-b.xPos;
double dy=a.yPos-b.yPos;
return sqrt(dx*dx+dy*dy);
}
class A;//超前引用
class B
{
public:
void set(int x,A &o);
}
class A
{
int data;
void show(){cout <<data<<endl;}
public:
friend void B::set(int x,A &o); //该函数是类A的友元,须在A后定义
}
void B::set(int x, A &o)
{
o.data=x;o.show();
}
void main()
{
A a;
B b;
b.set(6,a);
}
采用赋值运算符“=”可以将一个对象赋值给另外一个对象,或者采用一个对象初始化另外一个对象
在缺省的情况下,这两个操作执行的是对象成员之间的拷贝,也被称为按位拷贝或浅拷贝
通过赋值运算符进行对象赋值时,是把一个区域中的内容拷贝过来,覆盖另外一个区域,只影响值的变化,对内存区域没有任何影响
赋值和初始化的区别:赋值出现在两个对象都已经存在的情况下,而初始化出现在创建对象时
当采用一个对象初始化另外一个对象时,对象成员之间的赋值也是按位拷贝
编译器会区分赋值与初始化,赋值的时候调用重载的赋值运算符,初始化的时候调用拷贝构造函数。若类中原来没有,则生成的默认构造函数只会简单地赋值类中的每一个成员
通常采用按位拷贝操作也能正常赋值,但当类成员变量含有指针时往往不能正常运行
class PersonInfo {
char* name;
int age;
public:
PersonInfo(char* n, int a) {
name = new char[strlen(n)+1];
strcpy(name, n);
age = a;
}
~PersonInfo() {...}
};
int main() {
PersonInfo person1("Jones", 20);
PersonInfo person2 = person1; //会导致两个name指向同一个区域
}
解决办法:引入拷贝构造函数,当采用一个对象初始化另一个对象时,将自动调用该函数
PersonInfo(const PersonInfo &Obj) {
name = new char[strlen(Obj.name)+1];
strcpy(name, Obj.name);
age = Obj.age;
}
用对象初始化同类的另一个对象
PersonInfo st2(st1), st3 = st1;
如果函数的形参时对象,当进行参数传递时将调用拷贝构造函数
理解:拷贝构造函数的参数一定是个引用,防止拷贝构造函数被调用后又实例化了一个对象,再次调用了拷贝构造函数,形成无限递归
如果函数的返回值是对象,函数执行结束时,将调用拷贝构造函数对无名临时对象初始化
赋值运算符重载
operator= 函数的参数是常引用,优点是:
效率高:采用引用可以防止参数传递时生成对象拷贝,节省了对象初始化和析构的过程。
可以防止函数无意间修改对象right的内容。
符合赋值运算的常识。
注意:如果对象中有指针成员,采用拷贝构造函数可以解决对象初始化问题,但并不能处理对象赋值。
假设有一个类PersonInfo:
class PersonInfo { char *name; int age; }
要进行一个类对另一个类的赋值:
PersonInfo p1,p2;//p1=p2 等价于 p1.operator=(p2)
需要对赋值运算符进行重载:
void operator = (const PersonInfo &right)
{
delete [ ] name; //先删除原来的name
name = new char[strlen(right.name) + 1];
strcpy(name, right.name);
age = right.age;
}
this 指针
this是一个隐含的内嵌指针,它指向调用成员函数的当前对象。
例如:
PersonInfo &operator=(const PersonInfo &right) //第一个&:返回对象的引用,节省空间,提高效率
{
delete [ ] name;
name = new char[strlen(right.name)+ 1];
strcpy(name, right.name);
age = right.age;
return *this;
}
• this指针是以隐含参数的形式传递给非静态的函数成员
• this可用于解决局部变量与成员变量同名的问题
例如:
PersonInfo( char *name, int age )
{
this->name = new char[ strlen( name ) + 1];
strcpy( this->name, name );
this->age = age;
}
任何一个双目算术运算符 A 被重载以后,当执行二元运算时:
Obj1 A Obj2 完全等价于:Obj1.operator A( Obj2 )
双目算术运算符有+、-、*、/、+= 等
例如:对+运算符进行重载
class FeetInches
{
int feet, inches;
public:
FeetInches operator + (const FeetInches &);//const使得程序无法修改参数,&用于提高效率
}
FeetInches FeetInches::operator + (const FeetInches &right )
{
FeetInches temp;
temp.inches = this->inches + right.inches;
temp.feet = feet + right.feet;
return temp;
}
单目算术运算符有 ++、–、!、~(按位取反)等
例如:对++运算符进行重载
class FeetInches
{
int feet, inches;
public:
FeetInches operator++( );//前置++
FeetInches operator++(int);//后置++,其中int为哑元,用作区别标记
}
//前置++ 重载
FeetInches FeetInches::operator++( )
{
++inches;
return *this;
}
// 后置++ 重载
FeetInches FeetInches::operator++(int)
{
FeetInches temp(feet, inches); //先把当前对象的值留下,赋给temp,然后对当前对象进行操作,返回的是当前对象修改之前的数据。
++inches;
return temp;
}
关系算术运算符有 >、<、<=、>=、==、!= 等
重载关系运算符,实现两个对象的比较,其中关系运算符函数要返回一个布尔值(true或false):
例如:对>运算符进行重载
class FeetInches
{
int feet, inches;
public:
bool operator> (const FeetInches & );
}
bool FeetInches::operator > (const FeetInches &right )
{
if (feet > right.feet)
return true;
else if (feet==right.feet && inches> right.inches)
return true;
else
return false;
}
流操作符有 >>、<< 等
由于cout本身不支持类对象的处理,如果要让它同样能打印类对象,必须得重载操作符<<。
注意:如果要为FeetInches类重载流插入符<<,那么必须通过友元函数的形式实现函数重载。因为cout是一个标准的输出流对象,不是类中的对象,不能重载为类中的成员函数。
例如:对<<进行重载如下
class FeetInches
{
int feet, inches;
public:
friend ostream &operator<<( ostream &,FeetInches &);
}
ostream &operator<<(ostream &strm, FeetInches &obj)
{
strm << obj.feet << "feet, " << obj.inches <<" inches";
return strm;
}
distance1和distance2分别为FeetInches的两个对象:
cout << distance1 <<" "<< distance2 << endl ;
等价于如下过程:
(1)首先调用重载函数<<,执行cout << distance1,返回cout对象;
(2)执行cout << “ ”,返回值是cout对象;
(3)以(1)的方式,执行cout << distance2;
(4)以(2)的方式,执行表达式中的cout << endl;
类型转换运算符()
对于一个对象,通过重载类型转换函数,可实现类型转换功能。
例如:对<<进行重载如下
class FeetInches
{
int feet, inches;
public:
operator float( );//函数无返回值类型
operator int( ) { return feet; }
}
FeetInches::operator float( )
{
float temp = feet;
temp += (inches / 12.0f); return temp;
}
应用如下:
void main( )
{
FeetInches distance(3, 6);
int i;
float f;
f = distance; // f = distance.operator float( );
cout << f << endl;
i = distance; // i = distance.operator int( );
cout << i << endl;
}
注意:对象到整形、浮点型的类型转换没有返回值
下标操作符 [ ] 通常用于访问数组元素。重载该运算符用于增强操作 C++ 数组的功能。
例如:
string name = "John";
cout << name[0]; //代表的是值
name[0]='a'; //代表的是空间
下面的实例演示如何重载下标运算符 [ ]:
class IntArray
{
int* aptr;
int arraySize;
public:
IntArray(int s)
{
arraySize = s;
aptr = new int[s];
for (int i = 0; i < arraySize; i++)
*(aptr + i) = 0;//让每一个值都为0
}
~IntArray() { delete[] aptr; }
int& operator[ ](const int& sub)//第一个&可以作为空间又可以作为数值,既可以放在赋值运算符的左边又可以放在右边
{
return aptr[sub]; //返回一个单元,必须返回一个引用
}
};
void main()
{
IntArray table(10);
int x;
for (x = 0; x < 10; x++)
table[x] = table[x] + 5;//前一个table[]指的是空间,后一个指的是值
for (x = 0; x < 10; x++)
cout << table[x] << " ";
cout << endl;
cout << "store a value in table[11].\n";
table[11] = 0;
}
继承易于扩充现有类以满足新的应用。将已有的类称之为父类,也称基类;将新产生的类称为子类,也称为导出类或派生类。
导出类不做任何改变地继承了基类中的所有变量和函数(构造函数和析构函数除外),并且还可以增加新的数据成员和函数,从而使导出类比基类更为特殊化。
class Grade
{
char letter;
float score;
public:
void setScore(float s);
};
Test 子类公有继承 Grade父类
class Test : public Grade
{
int numQuestions;
public:
Test( int, int );
};
父类中的公有成员在子类中仍是公有的,它们可以和子类中的公有成员一样被访问。但反过来是错误的,基类对象或基类中的某个函数不能调用子类中的函数。
基类中的保护成员和私有成员比较类似,唯一的区别是:子类不可访问基类中的私有成员,但可访问基类中的保护成员。
在公有继承或保护继承的情况下,子类能访问基类的protected成员。
不同继承方式,基类成员在子类中的表现:
继承方式 | 基类成员在子类中的表现 |
---|---|
private | 1.基类的私有成员在子类中不可访问; 2.基类的保护成员变成了子类中的私有成员; 3.基类的公有成员变成了子类中的私有成员。 |
protected | 1.基类的私有成员在子类中不可访问; 2.基类的保护成员变成了子类中的保护成员; 3.基类的公有成员变成了子类中的保护成员。 |
public | 1.基类的私有成员在子类中不可访问; 2.基类的保护成员变成了子类中的保护成员; 3.基类的公有成员变成了子类中的公有成员。 |
实例化表现:
基类成员在基类中的表现 | 继承方式 | 基类成员在子类中的表现 |
---|---|---|
private: x protected: y public: z |
private继承 | x is inaccessible private: y private: z |
private: x protected: y public: z |
protected继承 | x is inaccessible protected: y protected: z |
private: x protected: y public: z |
public继承 | x is inaccessible protected: y public: z |
注意:
(1)如果省略了继承修饰符,那么就是私有继承,如下:
class Test : Grade
(2)不要将继承修饰符与成员的访问修饰符相混淆:
继承下的构造函数和析构函数
当基类和子类都有构造函数时,如果定义一个子类对象,那么首先要调用基类的构造函数,然后再调用子类的构造函数;
析构函数的调用次序与此相反,即先调用子类的析构函数,然后再调用基类的析构函数。
例子:
class BaseDemo
{
public:
BaseDemo( ) { cout << "In BaseDemo constructor.\n"; }
~BaseDemo( ) { cout << "In BaseDemo destructor.\n"; }
};
class DerivedDemo : public BaseDemo
{
public:
DerivedDemo( ){ cout << "In DerivedDemo constructor.\n"; }
~DerivedDemo( )
{ cout << "In DerivedDemo destructor.\n"; }
};
向父类的构造函数传参数
如果基类和子类都有缺省的构造函数,它们的调用是自动完成的,这是一种隐式调用。
如果基类的构造函数带有参数,那么必须让子类的构造函数显式调用基类的构造函数,并且向基类构造函数传递适当的参数。
实例:
class Rectangle
{
protected:
float width , length, area;
public:
Rectangle( ) { width = length = area = 0.0f ; }
Rectangle ( float w, float l )
{
width = w;
length = l;
area = width * length;
}
};
//子类Cube公有继承基类Rectangle
class Cube : public Rectangle
{
protected:
float height, volume;
public:
Cube(float, float, float);
};
//初始化列表
Cube::Cube(float w, float l, float h) : Rectangle(w, l)
{
height = h ;
volume = area * height ;
}
初始化列表的作用
如果类之间具有继承关系,子类必须在其初始化列表中调用基类的构造函数。如:
class Base
{
Base( int x );
};
class Derived : public Base
{
Derived(int x, int y): Base(x)
{ /* … */ }
};
类中的const常量只能在初始化列表中进行初始化,而不能在函数内用赋值的方式初始化。如:
class Base
{
const int SIZE ;
Base(int size) : SIZE(size)
{ /* … */ }
};
Base one(100);
对象类型的成员的初始化放在初始化列表中,则效率较高,反之较低。基本类型变量的初始化可以在初始化列表中,也可在构造函数中,效率上没区别。如:
class Base
{
Base( );
Base(const Base &other);
};
class Derived
{
Base B_Member;
public: Derived(const Base &a);
};
构造函数的实现:
Derived::Derived(const Base & b ) :B_Member(b)
{ /* … */ }
也可这样实现,但效率较低:
Derived::Derived(const Base &b)
{ B_Member = b; }
覆盖与重载的区别
重载的特点:
重载表现为有多个函数,它们的 名字相同,但参数不全相同;
重载可以出现在同一个类中,也可出现在具有继承关系的父类与子类中;
重载也可表现为外部函数的形式。
例如:
class Base
{
public:
int fun(int x)
{
cout<<x<<”n”;
}
};
class Derived: public Base
{
public:
int fun(int x,int y)
{
cout<<x+y<<”\n”<<endl;
}
};
覆盖的特点:
覆盖一定出现在具有继承关系的基类和子类之间;
覆盖除了要求函数名完全相同,还要求相应的参数个数和类型也完全相同;
当进行函数调用时,子类对象所调用 的是子类中定义的函数;
覆盖是C++多态性的部分体现。
class Base
{
public:
int fun(int x)
{
cout<<x<<”n”;
}
};
class Derived: public Base
{
public:
int fun(int x)
{
cout<<x+y<<”\n”;
}
};
虚函数
函数覆盖体现了一定的多态性。但是,简单的函数覆盖并不能称为真正的多态性。
不支持多态性的语言不是一个正在的OOP语言。
错误实例:
class MileDist
{
protected:
float miles;
public:
void setDist(float d) { miles = d; }
float getDist() { return miles; }
float square()
{
return getDist() * getDist();//谁的?
}
};
class FeetDist : public MileDist
{
protected:
float feet;
public:
void setDist(float);
float getDist() { return feet; }
float getMiles() { return miles; }
};
void FeetDist::setDist(float ft)
{
feet = ft;
MileDist::setDist(feet / 5280);
}
void main()
{
FeetDist feet;
float ft;
cout << "请输入以英尺为单位的距离:";
cin >> ft;
feet.setDist(ft);
cout << feet.getDist() << " 英尺等于 ";
cout << feet.getMiles() << " 英里\n";
cout << feet.getDist() << " 平方等于 ";
cout << feet.square() << " \n";
}
错误的原因:
C++编译器在缺省情况下,对函数成员的调用实施的是静态连编(也称静态绑定)。
注意:父类中调用函数是提前确定的,没有根据当前对象类型确定。
OOP: 覆盖和重载不能体现真正的多态性,只有虚函数才是多态性的表现。不支持多态 性的语言,就不能称为OOP。
class MileDist
{
protected:
float miles;
public:
void setDist(float d) { miles = d; }
virtual float getDist() { return miles; }
float square() { return getDist() * getDist(); }
};
纯虚函数
纯虚函数是在基类中声明的虚函数,没有函数体,要求继承基类的子类必须覆盖它。
带有纯虚函数的类称为抽象类,不能定义抽象类的对象。
派生类可以根据自己的需要,分别覆盖纯虚函数,从而实现真正意义上的多态性。
格式如下:
virtual void showInfo( ) = 0;
实例:
class Student
{
protected:
char name[51];
int hours;
public:
Student() { name[0] = hours = 0; }
void setName(char* n) { strcpy(name, n); }
// Pure virtual function
virtual void setHours( ) = 0;
virtual void showInfo( ) = 0;
};
class CsStudent : public Student
{
int mathHours, csHours;
public:
void setMathHours(int mh) { mathHours = mh; }
void setCsHours(int csh) { csHours = csh; }
void setHours()
{ hours = mathHours + csHours; }
void showInfo();
};
void CsStudent::showInfo()
{
cout << " Name: " << name << endl;
cout << "\t Math: " << mathHours << endl;
cout << "\t CS : " << csHours;
cout << "\n\t Total Hours: " << hours;
}
void main()
{
CsStudent student1;
char chInput[51];
int intInput;
cout << "Enter the following information:\n";
cout << "Name: ";
cin.getline(chInput, 51);
student1.setName(chInput);
cout << "Number of math hours completed: ";
cin >> intInput;
student1.setMathHours(intInput);
cout << "Number of CS hours completed: ";
cin >> intInput;
student1.setCsHours(intInput);
student1.setHours();
cout << "\nSTUDENT INFORMATION\n";
student1.showInfo();
}
关于抽象类和纯虚函数小结
如果一个类包含有纯虚函数,那么它就是抽象类,必须让其它类继承;
基类中的纯虚函数没有代码;
不能定义抽象类的对象,即抽象基类不能实例化;
必须在子类中覆盖基类中的纯虚函数。
指向父类的指针
指向基类对象的指针可以指向其子类的对象;
如果子类覆盖了基类中的成员,但通过基类指针所访问的成员仍是基类的成员,而不是子类成员。
实例:
class Base
{
public:
void show() { cout << "In Base class.\n"; }
};
class Derived : public Base
{
public:
void show() { cout << "In Derived class.\n"; }
};
void main()
{
Base* bptr;
Derived dobject;
bptr = &dobject;
bptr->show();
}
多重继承
类C继承类B中所有的成员,包括B从A中继承所得的成员
如:class C->class B->class A
多继承
如果一个子类具有两个或多个直接父类,那么就称为多继承。
对父类构造函数的调用,是按照继承的顺序进行。
实例:
class DateTime : public Date, public Time{}
类模板用于创建类属类和抽象数据类型,从而使程序员可以创建一般形式的类,而不必编写处理不同数据类型的类。
类模板的定义和实现必须在同一个文件中,通常是头文件。编译器看到模 板实现时才展开模板。
实例:
template < class T >
class FreewillArray
{
public:
FreewillArray( ) { aptr = 0 ; arraySize = 0 ;}
FreewillArray( int ) ; // 构造函数
FreewillArray( const FreewillArray & ) ; // 拷贝构造函数
~FreewillArray( ) ; // 析构函数
int size( ) { return arraySize ; }
T &operator[ ]( const int & ) ; // 对 [ ] 进行重载
private:
T *aptr ; // 采用模板参数T替换过去的int
int arraySize ;
void memError( ) ; // 处理内存分配错误
void subError( ) ; // 处理下标越界错误
} ;
// FreewillArray类模板的构造函数。设置数组的大小,并对数组分配内存
template < class T >
FreewillArray < T >::FreewillArray( int s )
{
arraySize = s ;
aptr = new T [s] ;
if( aptr == 0 )
memError( ) ;
for( int count = 0 ; count < arraySize ; count++ )
*( aptr + count ) = 0 ;
}
// FreewillArray类模板的拷贝构造函数。
template < class T >
FreewillArray < T >::FreewillArray( const FreewillArray &obj )
{
arraySize = obj.arraySize ;
aptr = new T [arraySize] ;
if( aptr == 0 )
memError( ) ;
for( int count = 0 ; count < arraySize ; count++ )
*( aptr + count ) = *( obj.aptr + count ) ;
}
// FreewillArray类模板的析构函数。
template < class T >
FreewillArray < T >::~FreewillArray( )
{
if( arraySize > 0 )
delete [ ] aptr ;
}
// memError 函数。当内存分配出错时,显示错误信息,并终止程序
template < class T >
void FreewillArray < T >::memError( )
{
cout << "错误:无足够的内存空间.\n" ;
exit( 0 ) ;
}
// subError 函数成员。当数组下标越界时,显示错误信息,并终止程序
template < class T >
void FreewillArray < T >::subError( )
{
cout << "错误:数组下标越界\n" ;
exit( 0 ) ;
}
// 重载运算符[ ],函数的参数是一个下标,在正常情况下,函数返回
// 下标指定的数组元素的引用,否则调用subError函数终止程序。
template < class T >
T &FreewillArray < T >::operator[ ]( const int &sub )
{
if( sub < 0 || sub > arraySize )
subError( ) ;
return aptr[sub] ;
}
int main( )
{
FreewillArray <int> intTable(10); //intTable和floatTable都是对象
FreewillArray <float> floatTable(10) ;
int x;
for( x = 0 ; x < 10 ; x++ ) // 在数组中存储值
{
intTable[x] = x ;
floatTable[x] = x ;
}
// 显示数组中的值
cout << "intTable中的值是:\n\t" ;
for( x = 0 ; x < 10 ; x++ )
cout << intTable[x] << " " ;
cout << endl ;
cout << "floatTable中的值是:\n\t" ;
for( x = 0 ; x < 10 ; x++ )
cout << floatTable[x] << " " ;
cout << endl ;
// 对数组元素采用内嵌+操作
for( x = 0 ; x < 10 ; x++ )
{
intTable[x] = intTable[x] + 1 ;
floatTable[x] = floatTable[x] + 1.5f ;
}
// 显示数组中的值
cout << "intTable中的值是:\n\t" ;
for( x = 0 ; x < 10 ; x++ )
cout << intTable[x] << " " ;
cout << endl ;
cout << "floatTable中的值是:\n\t" ;
for( x = 0 ; x < 10 ; x++ )
cout << floatTable[x] << " " ;
cout << endl ;
// 对数组元素采用内嵌++操作
for( x = 0 ; x < 10 ; x++ )
{
intTable[x]++ ;
floatTable[x]++ ;
}
// 显示数组中的值
cout << "intTable中的值是:\n\t" ;
for( x = 0 ; x < 10 ; x++ )
cout << intTable[x] << " " ;
cout << endl ;
cout << "floatTable中的值是:\n\t" ;
for( x = 0 ; x < 10 ; x++ )
cout << floatTable[x] << " " ;
cout << endl ;
return 0;
}
基本异常
异常是在程序执行期间的突发性事件
异常与错误不同,错误可以通过编译系统处理
抛出异常
float divide(int number, int div) {
if(div == 0) {
throw "ERROR: divided by zero";
}
else {
return float(number) / div;
}
}
处理异常
try {
<可能出现异常的代码>
}
catch (exception param1) {
<处理异常类型1的代码>
}
catch (exception param2) {
<处理异常类型2的代码>
}
异常处理失败的原因
try语句块中世纪产生的异常,与catch语句圆括号指定要捕捉的异常类型不匹配
try语句的范围太小,在try语句之前就已经产生了异常,那么后面的try语句块将不再执行
基于对象的异常处理
C++除了支持基本类型的异常处理外,还支持面向对象的异常处理
C++在处理多种类型的异常时,要求这些异常对象必须属于不同类型,并且对于每种类型的异常都要编写一段对应的catch代码
栗子:
class IntRange {
int input, lowest, highest;
public:
class tooLow {};
class tooHigh {};
InRange() {
lowest = lowest;
highest = highest;
}
int getInput() {
cin >> input;
if(input < lowest) {
throw tooLow();
}
else if(input > highest) {
throw tooHigh();
}
return input;
}
};
int main() {
InRange range;
int uerValue;
try {
userValue = range.getInput();
}
catch(IntRange::tooLow) {...}
catch(IntRange::tooHigh) {...}
return 0;
}
还可以通过异常对象将异常信息传递给异常处理者
class IntRange2 {
int input;
public:
class OutOfRange {
public:
int value;
OutOfRange(int i) {
value = i;
}
};
int getInput() {
cin>>input;
if(...) {
throw OutOfRange(input);
}
return iuput;
}
};
int main() {
IntRange2 range;
int userValue;
try {
userValue = range.getInput();
}
catch (IntRange2::OutOfRange ex) {
cout << ex.value;
}
return 0;
}
注意事项
一旦程序抛出异常,即使在异常处理以后,程序也不能回到原来的抛出点继续执行
一旦程序抛出异常,执行throw语句的函数将立即停止运行
对象的函数成员抛出了异常,那么将立即对该对象调用析构函数
在try块中创建了对象,并且这些对象还未来得及析构,那么将对这些对象立即调用析构函数
再次抛出异常
try {
...
}
catch(exception param1) {
...
throw; //经过处理后再次抛出异常交给函数调用链的上层函数处理
}
catch(exception param2) {
...
}
标准模板库:常用数据结构和算法的模板的集合
容器:可以容纳各种数据类型的通用数据结构,是类模板
迭代器:可以用于依次存取容器中的元素,类似于指针
算法:用来操作容器中的元素的函数模板
顺序容器
vector
动态数组。元素在内存连续存放,随机储存任何元素都能在常数时间完成
deque
双向队列,元素在内训连续存放,随机存取任何元素都能在常数时间完成(次于vector)
list
双向链表,元素在内存不能连续存放,在任何位置增删元素都能在常数时间完成,不支持随机存取
array
forward_list (C++ 11中介绍)
关联容器
元素是排序的,插入任何元素,都应按相应的排列规则来确定位置,因此具有较好的性能
通常以平衡二叉树的方式实现,插入于检索的时间都是O(log(N))
set/multiset
set就是集合,set中不允许相同的元素,multiset中则允许存在相同的元素
map/multimap
map与set的不同之处在于map中存放的元素有且仅有两个,第一个是first,另一个是second,并且根据first的值来进行排序和检索,multimap中则允许相同的first元素
在关联容器前加上 unordered_ 则是非排序(C++ 11中介绍)
容器适配器
stack
栈,是项的有限序列,并且满足序列中被删除、检索和修改的项只是最近插入序列的项,后进先出
queue
队列,插入只可以在尾部进行,删除、检索和修改只允许从头部进行,先进先出
priority_queue
优先队列,最高优先级元素总是第一个出列
用于指向顺序容器和关联容器中的元素,用法和指针类似
有const和非const两种,通过非const还能修改其指向的元素
通过迭代其可以读取它指向的元素
迭代器示例
int main() {
vector<int> v;
v.push_back(1);
vector<int>::const_iterator i;
for(i = v.begin(); i != v.end(); ++i) {
cout<<*i<<",";
}
cout<<endl;
return 0;
}
双向迭代器
若p和p1都是双向迭代器,则可对p、p1进行如下操作
++p, p++ | 使p指向容器中的下一个元素 |
–p, p– | 使p指向容器中的上一个元素 |
*p | 取p指向的元素 |
p = p1 | 赋值 |
p == p1, p != p1 | 判断是否相等、不等 |
随机访问迭代器
若p和p1都是随机访问迭代器,则可对p、p1进行以下操作
p += i | 将p向后移动i个元素 |
p -= i | 将p向前移动i个元素 |
p + i | 指向p后面的第i个元素的迭代器 |
p - i | 指向p前面的第i个元素的迭代器 |
p[i] | p后面第i个元素的引用 |
p < p1, p <= p1… | 比较大小 |
p - p1 | 返回p与p1之间的元素个数 |
小总结
容器 | 容器上的迭代器类别 |
---|---|
vector | 随机访问 |
deque | 随机访问 |
list | 双向访问 |
set/multiset | 双向访问 |
map/multimap | 双向访问 |
stack | 不支持迭代器 |
queue | 不支持迭代器 |
priority_queue | 不支持迭代器 |
注意
有的算法例如sort、binary_search需要通过随机访问迭代器来访问容器中的元素
双向迭代器不支持 < ,list里面也没有 [ ] 成员函数
随机访问迭代器可以通过 v[i] 来访问成员
两个重要算法
sort 排序
sort(startaddress, endaddress)
startaddress: the address of the first element of the array
endaddress: the address of the next contiguous location of the last element of the array.
So actually sort() sorts in the range of [startaddress,endaddress)
binary_search
主要思想是将数组一分为二(分而治之),直到找到元素,或者所有元素都用完为止。
binary_search(startaddress,
endaddress, valuetofind)
Parameters :
startaddress: the address of the first
element of the array.
endaddress: the address of the next contiguous
location of the last element of the array.
valuetofind: the target value which we have
to search for.
Returns :
true if an element equal to valuetofind is found, else false.
顺序容器和关联容器中皆有的函数成员
函数名 | 含义 |
---|---|
begin | 返回指向容器中第一个元素的迭代器 |
end | 返回指向容器中最后一个元素后面位置的迭代器 |
rbegin | 返回指向容器中最后一个元素的迭代器 |
rend | 返回指向容器中第一个元素前面的位置的迭代器 |
insert | 向容器中插入某个(些)元素 |
find | 查找等于某个值的元素 |
erase | 从容器中删除一个或者几个元素 |
clear | 从容器中删除所有元素 |
顺序容器的常用成员函数
函数名 | 含义 |
---|---|
front | 返回容器中第一个元素的引用 |
back | 返回容器中最后一个元素的引用 |
push_back | 在容器末尾增加新的元素 |
pop_back | 删除容器末尾的元素 |
erase | 删除迭代器指向的元素(可能会使其失效),或者删除一个区间,返回被删除元素后面的元素的迭代器 |
注意:List除了顺序容器的所有成员函数以外还支持以下8种函数:
函数名 | 含义 |
---|---|
push_front | 在前面插入 |
pop_front | 删除前面的元素 |
sort | 排序,list 不支持 STL 的排序 |
remove | 删除和指定值相等的所有元素 |
unique | 删除所有和前一个元素相同的元素 |
merge | 合并两个列表,并且清空被合并的辣个 |
reverse | 颠倒列表 |
splice | 在指定位置插入另一个链表的一个或多个元素,并在另一个链表中删除被插入的元素 |
关联容器的常用成员函数
函数名称 | 含义 |
---|---|
upper_bound | 查找上界 |
lower_bound | 查找下界 |
equal_range | 同时查找上下界 |
count | 计算等于某个值的元素个数 |