C++ 期末复习(超全)

C++ 期末复习 (第48次修订)

  • 期末复习顺便整理了哈哈哈,没写好的地方还请多多指正,感谢zrt同学帮忙整理4、5章,还有lqp同学整理的6、7章

目录


1 字符串与指针

1.1 处理字符与字符串

  • 求字符串的长度

    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进制
    

1.2 标准C++的String类

  • 包含的头文件

    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() //返回配置器
    

1.3 指针的概念、指针变量的定义和使用

  • 指针变量就是存放地址的变量

  • 定义指针变量
    例如:

    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指向变量的值的交换

1.4 指针与数组

  • 访问数组的方式

    • 下标形式

    • 指针形式

  • 两种类型数组

    • 全局数组和静态数组:在静态储存区中被创建

    • 局部变量:在栈上被创建

  • 数据名对应一块内存,其地址与容量在其生命周期内保持不变,只有数组的内容可以改变

  • 指针可以指向任意类型的内存块,其特征是“可变”

  • 指向一维数组元素的指针

    数组名代表该数组的开始地址,数组名即时一个指针常量,例如

    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;
    }
    

2 指针与函数

2.1 参数的传递方式

  • 基本类型的变量作函数形参

    交换变量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;
    }
    

    通过定义被调用函数中的参数为指针类型,通过间接存取将主调函数的值改变

    注意:指针类型做参数形参的另一种形式是数组名做函数参数

2.2 返回指针的函数及指向函数的指针

  • 指针型函数

    一个函数的返回值是某种数据类型的地址值就是指针型函数

    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);
    }
    

2.3 指针数组与指向指针的指针

  • 指针数组

    一个数组里面所有的元素都是指针变量称为指针数组

  • 指针数组与数组指针的本质区别就是*与[]的优先级顺序不同

    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;
    

2.4 内存的动态分配与释放

  • 变量存储空间的分配是系统完成的,不需用户干预

    • 静态变量:编译时分配空间

    • 动态变量:系统运动时分配空间

  • 程序动态申请空间是由用户在编程时安排的

  • 申请多少空间由运行时情况而定,一般通过指针访问空间

  • new用于动态申请储存空间,delete用于释放new申请的存储空间

    //分配单个元素空间
    int* iptr;
    iptr = new int;
    *iptr = 25;
    //也可以这样
    iptr = new int(25);
    delete iptr;
    //分配一片连续的空间
    int* a;
    a = new int[100];
    delete []a;
    

    注意:如果函数的参数是一个指针,不要用指针去申请动态内存,否则会因为申请的空间无法释放而造成内存泄漏

2.5 void 和 const 修饰指针变量

2.5.1 void修饰指针变量
  • void修饰指针

    void修饰指针代表是一种不确定类型的指针

  • 任何类型的指针都可以直接赋给它,无需类型转换

    void* p1;
    int* p2;
    p1 = p2;
    
  • 不能对void指针进行算数操作

2.5.2 const修饰指针变量
  • 指向常量的指针

    可以改变指针所指的空间,但不可以通过指针改变现在所指的内容

    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
    
  • 指向常量的指针常量

    既不可以修改指针所指的内容又不可改变指针的指向


3 结构体与列表

3.1 结构体的建立

  • 结构体就是通过定义一种数据类型,把不同的数据作为一个整体来处理

  • 结构体是一种由程序员创建的抽象数据类型

  • 先定义结构体

    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;
    

    注意

    • 不能将结构体作为一个整体进行输入或者输出

    • 结构体变量可以用作函数的参数,属于按值传递

    • 函数可以返回一个结构体变量

3.2 结构体的应用

  • 使用结构体变量作为函数参数的效率较低,因为结构体作为形参需要赋值

  • 结构体数组与指针

    定义结构体数组和初始化

    student studentList[4];
    student studentList[4] = {{...}, {...}, {...}, {...}};
    

    使用结构体数组

    //引用元素
    for(int i = 0; i < 4; i++) {
      cout << studentList[i].name;
    }
    //指针
    student *ps;
    ps = stud;
    ps++;
    

3.3 typedef 定义类型名

  • 语法格式

    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;
      

3.4 链表

  • 链表的引入

    • 数据空间是连续的

    • 实际应用无法确定数组的大小

    • 定义足够大————空间浪费

  • 链表的结构

    struct student {
      int ID;
      char name[20];
      student* next; //链表与结构体的区别
    };
    
  • 单向链表

    带有头节点的单向链表

    不带头节点的单向链表

  • 链表的应用示例

    #include "stdafx.h"
    #include <iostream>
    #include <iomanip>
    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;
    }
    

4 文件操作

4.1 文件的打开与关闭

  • 文件流类型

      #include<fstream> //文件流头文件
      ifstream a//输入文件流,只读取文件流
      ofstream b//输出文件流,只写入文件流
      fstream c//文件流
    
  • 文件使用过程

    打开文件
    • 开启方式
    ifstream inputFile;
    inputFile.open("d:\\costumer.dat");
    //or
    char fileName[20];
    cin>>fileName;
    inputFile.open(fileName);
    
    • 使用fstream对象时,第二个参数用于表明文件的打开方式
    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();
    
    • 为何要关闭文件

      文件缓冲区是一块小的内存空间

      操作系统限制同时打开的文件数量

4.2 文本文件输入输出

  • 使用<<向一个文件写入信息

    #include<iostream>
    #include<fstream>
    #include<cstdlib>
    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<iostream>
    #include<fstream>
    #include<cstdlib>
    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();
    }
    
  • 采用函数成员读写文件

    采用>>读文件的缺陷:空白字符(空格、跳格、换行、回车)是数据之间的分界符,采用>>操作符进行读取时,会忽略空白字符。

getline成员函数:
  dataFile.getline(str,81,’\n’)

str: 从文件中读取的数据将存储在该空间中

81:从文件中最多能读取80个字符

’\n’界符:如果在读满最大字符之前,遇到了界符,那么将停止读取(注意:该参数可选)

#include<iostream>
#include<fstream>
#include<cstdlib>
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;
  }
get成员函数
  • 例如:inFile.get(ch);
put成员函数
  • 例如:inFile.put(ch);
#include<iostream>
#include<fstream>
#include<cstdlib>
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;
}

4.3 文件出错检测方法

  • 出错检测

    流对象的标志位
    • 例如
    流对象内容
    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<iostream>
#include<fstream>
#include<cstdlib>
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();//清除出错标记位
}

4.4 多文件操作

  • 实例
#include<iostream>
 using namespace std;
 #include<fstream>
 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(); 
 }

4.5 操作简单二进制文件

  • 二进制文件

    二进制文件是按照在内存中存储的形式存储,不是按照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();
 }

4.6 读写结构体文件

  • 读写结构体记录

    结构体数据可以采用定长块存储到文件中

    因为结构体中可以包含不同类型的数据,所以当打开这种类型文件时,必须以二进制方式
    打开

  • 实例

      #include <iostream>
      using namespace std;
      #include <fstream>
      #include <cstdlib>
      #include <cctype>
      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();
      }
    

4.7 随机访问文件

  • seekp 和 seekg函数

    seekp 函数用于输出文件(写,put)

    seekg 函数用于输入文件(读,get)

    file.seekp(20L,ios::beg);//把file对象相对于文件头向后偏移20个字节
    
    //文件随机访问模式
    ios::beg;//从文件头开始计算偏移量
    ios::end;//从文件尾开始计算偏移量
    ios::cur;//从当前位置开始计算偏移量
    
  • seek实例:

    #include<iostream>
    #include<fstream>
    #include<cstdlib>
    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 <iostream>
    using namespace std;
    #include <fctream>
    #include <cstdlib>
    #include <cctype>
    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();
    }
    

5 类的基础部分

5.1 类的基本概念

  • 过程化程序设计的缺陷

    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;
    }
    

5.2 类的多文件组织方式

  • 组织方式:

    类的定义存储在头文件里(类定义文件)

    成员函数定义存储在.cpp文件(类的实现文件)

    应用程序通过#include包含头文件,将类的实现文件和主程序进行联编,从而
    生成一个完整的程序

 

5.3 构造函数与析构函数

  • 定义

    构造函数:是一个函数成员,在对象创建时,采用给定的值,自动调用该
    函数将对象中的数据成员初始化

    析构函数:也是一个函数成员,当对象终止时,将自动调用该函数进行
    “善后”处理

 

  • 构造函数的特点

    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;
     }
    }
    

5.4 重载构造函数

一个类中可以定义多个构造函数

  • 缺省构造函数的表现形式

    1、如果类中没有定义构造函数,系统将提供一个无参构造函数(属缺省构造函数),该函数不实现任何功能。如果用户自定义了一个构造函数,那么系统缺省的构造函数将失效。

    2、如果类中定义有无参的构造函数,那么该构造函数也是缺省的构造函数。

    3、如果类中定义有带参的构造函数,并且所有形参均具有缺省值,那么该构造函数也属于缺省的构造函数。

    4、一个类只能有一个缺省构造函数,否则将产生二义性。

    出错实例:

    class A;
    {
      int a;
    
      public:
      A(){a=0;}
      A(aa=1){a=aa;}//两构造函数产生歧义,因为有缺省值,也属于缺省构造函数
    }
    

 

  • 对象数组
    特点
    • 创建对象数组时,数组中每个元素(对象)都将调用构造函数

    • 如果没有为数组元素指定初始值,元素便使用缺省值来初始化,即调用缺省构造函数

    • 当数组中每一个对象被删除时,都要调用一次析构函数


6 类的高级部分

6.1 静态数据成员

特点
  • 用关键字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;
}

6.2 静态成员函数

特点
  • 静态函数成员是类中的一个函数,有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;

6.3 友元函数

特点
  • 友元函数不是类中的函数成员,但它和类的函数成员一样,可以访问类中定义的私有成员

  • 友元函数可以是一个外部函数,也可以是另外一个类的函数成员

  • 若一个类为另一个类的友元,则此类的所有成员都能访问对方类的私有成员

声明方式
  • 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); 
}

6.4 对象的赋值问题

  • 采用赋值运算符“=”可以将一个对象赋值给另外一个对象,或者采用一个对象初始化另外一个对象

  • 在缺省的情况下,这两个操作执行的是对象成员之间的拷贝,也被称为按位拷贝或浅拷贝

  • 通过赋值运算符进行对象赋值时,是把一个区域中的内容拷贝过来,覆盖另外一个区域,只影响值的变化,对内存区域没有任何影响

  • 赋值和初始化的区别:赋值出现在两个对象都已经存在的情况下,而初始化出现在创建对象时

  • 当采用一个对象初始化另外一个对象时,对象成员之间的赋值也是按位拷贝

  • 编译器会区分赋值与初始化,赋值的时候调用重载的赋值运算符,初始化的时候调用拷贝构造函数。若类中原来没有,则生成的默认构造函数只会简单地赋值类中的每一个成员

6.5 什么是拷贝构造函数

  • 通常采用按位拷贝操作也能正常赋值,但当类成员变量含有指针时往往不能正常运行

    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;
    }
    

6.6 调用拷贝构造函数的情况

  • 用对象初始化同类的另一个对象

    PersonInfo st2(st1), st3 = st1;
    
  • 如果函数的形参时对象,当进行参数传递时将调用拷贝构造函数

  • 理解:拷贝构造函数的参数一定是个引用,防止拷贝构造函数被调用后又实例化了一个对象,再次调用了拷贝构造函数,形成无限递归

  • 如果函数的返回值是对象,函数执行结束时,将调用拷贝构造函数对无名临时对象初始化

6.7 赋值运算符重载与this指针

  • 赋值运算符重载

    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; 
    }
    

6.8 双目运算符重载

  • 任何一个双目算术运算符 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; 
    }
    

6.9 单目运算符重载

  • 单目算术运算符有 ++、–、!、~(按位取反)等

    例如:对++运算符进行重载

    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;
    }
    

6.10 关系运算符重载

  • 关系算术运算符有 >、<、<=、>=、==、!= 等

    重载关系运算符,实现两个对象的比较,其中关系运算符函数要返回一个布尔值(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; 
    }
    

6.11 流操作符重载

  • 流操作符有 >>、<< 等

    由于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;

6.12 类型转换运算符重载

  • 类型转换运算符()

    对于一个对象,通过重载类型转换函数,可实现类型转换功能。

    例如:对<<进行重载如下

    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;
    }
    

    注意:对象到整形、浮点型的类型转换没有返回值

6.13 下标运算符[]重载

  • 下标操作符 [ ] 通常用于访问数组元素。重载该运算符用于增强操作 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;
    }
    

注意:运算符重载不能改变运算符原来要求的参数个数。


7 类的继承、多态、虚函数

7.1 继承

  • 继承易于扩充现有类以满足新的应用。将已有的类称之为父类,也称基类;将新产生的类称为子类,也称为导出类或派生类。

    导出类不做任何改变地继承了基类中的所有变量和函数(构造函数和析构函数除外),并且还可以增加新的数据成员和函数,从而使导出类比基类更为特殊化。

    class Grade 
    { 
      char letter; 
      float score; 
    public: 
      void setScore(float s); 
    };
    

    Test 子类公有继承 Grade父类

    class Test : public Grade
    { 
      int numQuestions; 
    public: 
      Test( int, int );
    };
    

    父类中的公有成员在子类中仍是公有的,它们可以和子类中的公有成员一样被访问。但反过来是错误的,基类对象或基类中的某个函数不能调用子类中的函数。

7.2 保护成员和类的访问

  • 基类中的保护成员和私有成员比较类似,唯一的区别是:子类不可访问基类中的私有成员,但可访问基类中的保护成员。

    在公有继承或保护继承的情况下,子类能访问基类的protected成员。

    不同继承方式,基类成员在子类中的表现:

    继承方式基类成员在子类中的表现
    private1.基类的私有成员在子类中不可访问;
    2.基类的保护成员变成了子类中的私有成员;
    3.基类的公有成员变成了子类中的私有成员。
    protected1.基类的私有成员在子类中不可访问;
    2.基类的保护成员变成了子类中的保护成员;
    3.基类的公有成员变成了子类中的保护成员。
    public1.基类的私有成员在子类中不可访问;
    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)不要将继承修饰符与成员的访问修饰符相混淆:

    • 继承修饰符是为了限定父类成员在子类中的表现
    • 成员访问修饰符是规定类外语句能否访问类中的成员

7.3 保护成员和类的访问

  • 继承下的构造函数和析构函数

    当基类和子类都有构造函数时,如果定义一个子类对象,那么首先要调用基类的构造函数,然后再调用子类的构造函数;

    析构函数的调用次序与此相反,即先调用子类的析构函数,然后再调用基类的析构函数。

    例子:

     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 ; 
    }
    

7.4 初始化列表的作用

  • 初始化列表的作用

    如果类之间具有继承关系,子类必须在其初始化列表中调用基类的构造函数。如:

    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; }
    

7.5 覆盖基类的函数成员

  • 覆盖与重载的区别

    重载的特点:

    • 重载表现为有多个函数,它们的 名字相同,但参数不全相同;

    • 重载可以出现在同一个类中,也可出现在具有继承关系的父类与子类中;

    • 重载也可表现为外部函数的形式。

      例如:

      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”;
        } 
      };
      

7.6 虚函数与纯虚函数

  • 虚函数

    函数覆盖体现了一定的多态性。但是,简单的函数覆盖并不能称为真正的多态性。

    不支持多态性的语言不是一个正在的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();
    }
    

7.7 多重继承与多继承

  • 多重继承

    类C继承类B中所有的成员,包括B从A中继承所得的成员

    如:class C->class B->class A

  • 多继承

    如果一个子类具有两个或多个直接父类,那么就称为多继承。

    对父类构造函数的调用,是按照继承的顺序进行。

    实例:

    class DateTime : public Date, public Time{}
    

7.8 类模板

  • 类模板用于创建类属类和抽象数据类型,从而使程序员可以创建一般形式的类,而不必编写处理不同数据类型的类。

    类模板的定义和实现必须在同一个文件中,通常是头文件。编译器看到模 板实现时才展开模板。

    实例:

    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;
    }
    

8 异常处理

  • 基本异常

    异常是在程序执行期间的突发性事件

    异常与错误不同,错误可以通过编译系统处理

  • 抛出异常

    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) {
      ...
    }
    

9 标准模板STL

9.1 STL 概述

  • 标准模板库:常用数据结构和算法的模板的集合

  • 容器:可以容纳各种数据类型的通用数据结构,是类模板

  • 迭代器:可以用于依次存取容器中的元素,类似于指针

  • 算法:用来操作容器中的元素的函数模板

9.2 容器

  • 顺序容器

    • 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

      优先队列,最高优先级元素总是第一个出列

9.3 迭代器

  • 用于指向顺序容器和关联容器中的元素,用法和指针类似

  • 有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] 来访问成员

9.4 算法

  • 两个重要算法

    • 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计算等于某个值的元素个数
更多内容参考:https://www.geeksforgeeks.org/algorithms-library-c-stl/
  • 4
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值