[OOP]虚拟继承

子类可以重写从父类继承而来的函数 (overwriting)

class Parent
{
public:
    void Test();
};
class Child : public Parent
{
public:
    void Test();
};


Child ch;
ch.Test(); // 调用的是子类的Test()函数

如果重写的时候,还是要嵌入调用一下父类的函数,怎么办?

void Child::Test()
{
Parent::Test(); // 显式地调用父类的函数
}

可以将父类指针指向一个子类的对象,这是完全允许的。

例如,
// 左侧为Tree*,右侧为AppleTree*
Tree* p = new AppleTree();

从普通的逻辑来讲,苹果树是一种树,因而可以把AppleTree*视为一种Tree*

从语法本质上讲,子类对象的前半部分就是父类,因而可以将子类对象的指针直接转化为父类。

有父类和子类:
class Parent
{
public:
    int a;
};

class Child : public Parent
{
public:
    int b;
};

int main()
{
    Child ch;
    ch.a = 0x11111111;
    ch.b = 0x22222222;

    Parent* p = &ch; // p指向的对象是Child*
    printf("Parent::a = %d \n", p->a);

    return 0;
}
所以,从直观感觉到内在机理都允许这么做

问题:考虑以下情况,
Parent* p = new Child();
p->Test();

那么,此时调用的Test()是父类的、还是子类的?
指针p的类型是Parent*
指针p指向的对象是Child*

调用者的初衷:因为p指向的是对象是子类对象,所以应该调用子类的Test()。

 

当一个成员函数需要子类重写,那么在父类应该将其声明为virtual。
(有时将声明为virtual的函数为“虚函数”)

例如
class Parent
{
public:
virtual void Test();
};

virtual本身表明该函数即将被子类重写。

 

加virtual关键字是必要的。

考虑以下情况,
Parent* obj = new Child(); // 语法允许,合乎情理
obj->Test();

此时,如果Test()在父类中被声明为virtual,是调用的是子类的Test()。

这解释了virtual的作用:根据对象的实际类型,调用相应类型的函数。

 

注意:
(1)只需要在父类中将函数声明为virtual,子类自动地就是virtual了。

(2)即将被重写的函数添加virtual,是一条应该遵守的编码习惯。

 

当一个类被继承时,应该将父类的析构函数声明为virtual,
否则会有潜在的问题。

class Parent
{
virtual ~Parent(){} // 声明为virtual
};

考虑以下场景:

Parent* p = new Child();
delete p; // 此时,调用的是谁的析构函数?

如果析构函数没有标识为virtual,则有潜在的隐患,并有可能直接导致程序崩溃。(资源没有被释放,并引申一系列问题)

[OOP]类的继承

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

class Tutorial
{
public:
    char name[32];
    char author[16];
public:
    void ShowInfo()
    {
        printf("Tutorial: %s, %s \n", name, author);
    }
protected:
    int abc;
};

class VideoTutorial : public Tutorial
{
public:
    void Play()
    {		
        printf("Playing...abc=%d\n", abc);
    }

public:
    char url[128]; // 在线观看的URL地址 
    int visits; // 播放量
};


int main()
{
    VideoTutorial cpp_guide;
    strcpy(cpp_guide.name, "C/C++学习指南");
    strcpy(cpp_guide.author, "邵发");
    cpp_guide.ShowInfo();

    strcpy(cpp_guide.url, "http://111111");
    cpp_guide.visits = 10000000;

//	cpp_guide.abc = 123;
    return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

class Parent
{
public:
    Parent()
    {
        ddd = 0xAAAAAAAAA;
    }
    int a;
private:
    int ddd;
};

class Child : public Parent
{
public:
    int b;
};

int main()
{
    Child c;
    c.a = 0x11111111;
    c.b = 0x22222222;

//	cpp_guide.abc = 123;
    return 0;
}

 

[OOP]析构函数

DataStore.h

struct Student
{
    int id;
    char name[16];
    Student* next;
};

class DataStore
{
public:
    DataStore();
    ~DataStore();

public:
    void Add(const Student* data);
    Student* Find(int id);
    void Print();

private:
    Student m_head;
};

DataStore.cpp

#include <stdio.h>
#include <stdlib.h>
#include "DataStore.h"

DataStore::DataStore()
{
    m_head.next = NULL;
}

DataStore::~DataStore()
{
    Student* p = m_head.next;
    while(p)
    {
        Student* next = p->next;
        free(p);
        p = next;
    }
}


void DataStore::Add(const Student* data)
{
    // 创建对象、复制数据
    Student* copy = (Student*)malloc(sizeof(Student));
    *copy = *data;

    // 插入一个对象到链表中
    Student* cur = m_head.next; // 当前节点current
    Student* pre = &m_head;  // 上一个节点previous
    while(cur)
    {		
        if(copy->id < cur->id) // 找到这个位置
            break;

        pre = cur;
        cur = cur->next;  // 找到最后一个对象
    }

    // 插入到pre节点的后面
    copy->next = pre->next;
    pre->next = copy;
}

Student* DataStore::Find(int id)
{
    Student* p = m_head.next; 
    while(p)
    {
        if(p->id == id)
            return p;

        p = p->next; // 下一个对象
    }
    return NULL;
}

void DataStore::Print()
{
    Student* p = this->m_head.next; 
    while(p)
    {
        printf("ID: %d, name: %s\n", p->id, p->name);
        p = p->next; // 下一个对象
    }
}

 

[OOP]构造函数

#include <stdio.h>

class Circle
{
public:
    Circle ()
    {
        printf("111111\n");
        x = y = 0;
        radius = 1;
    }
    Circle(int x, int y, int r)
    {
        printf("2222222\n");
        this->x = x;
        this->y = y;
        this->radius = r;
    }
    Circle(int x, int y)
    {
        printf("2222222\n");
        this->x = x;
        this->y = y;
        this->radius = 1;
    }

public:
    int x, y;
    int radius;

};

int main()
{
    Circle  a; 
    Circle  b(1,1, 4);  

    
    int n (10); // int n = 10;
    
    return 0;
}

 

[OOP]类的封装

#include <stdio.h>
#include <string.h>

class Circle
{
public:
    int GetX()
    {
        return m_x;
    }
    int GetY()
    {
        return m_y;
    }

    void SetRadius(int r)
    {
        m_radius = r;
    }

    void MoveTo(int x, int y)
    {
        m_x = x;
        m_y = y;
    }

    double Area()
    {
        return 3.14 * m_radius * m_radius;
    }

    // 1, 表示在里面
    int Contain(double x, double y)
    {
        
        return 0;
    }

private:
    int m_x;
    int m_y;
    int m_radius;
};



int main()
{
    Circle obj;
    obj.SetRadius(2);
    obj.MoveTo(0,0);

    int area = obj.Area();
    
    if(obj.Contain(1.2, 1.3))
    {
        printf("yes, in it\n");
    }
    return 0;
}

 

[OOP]面向对象概论

main.c

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "DataStore.h"

int main()
{
    DataStore* store = ds_create();

    Student s;
    s.id = 1;
    strcpy(s.name, "111");
    ds_add(store, &s);

    s.id = 2;
    strcpy(s.name, "222");
    ds_add(store, &s);

    Student* f = ds_find(store, 2);

    ds_print(store);

    ds_destroy(store);

    return 0;
}

DataStore.h

struct Student
{
    int id;
    char name[16];
    Student* next;
};

struct DataStore
{
    Student head;
};


DataStore*  ds_create();
void ds_destroy(DataStore*  store);

void ds_add( DataStore* store, const Student* data);

// (2) 可以按ID来查找一个记录
Student* ds_find(DataStore* store, int id);

//(3) 可以按ID删除一个记录
void ds_remove(DataStore* store, int id);

// (4) 可以打印显示所有的记录
void ds_print(DataStore* store);

DataStore.cpp

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "DataStore.h"

DataStore*  ds_create()
{
    // 动态创建对象
    DataStore* store = (DataStore*)malloc(sizeof(DataStore));
    // 初始化
    store->head.next = NULL;

    return store;
}

void ds_destroy(DataStore*  store)
{
    // 释放所有相关资源
    Student* p = store->head.next;
    while(p)
    {
        Student* next = p->next;
        free(p);
        p = next;
    }
    // 销毁对象
    free(store);
}

void ds_add( DataStore* store, const Student* data)
{
    // 创建对象、复制数据
    Student* copy = (Student*)malloc(sizeof(Student));
    *copy = *data;

    // 插入一个对象到链表中
    Student* cur = store->head.next; // 当前节点current
    Student* pre = &store->head;  // 上一个节点previous
    while(cur)
    {		
        if(copy->id < cur->id) // 找到这个位置
            break;

        pre = cur;
        cur = cur->next;  // 找到最后一个对象
    }

    // 插入到pre节点的后面
    copy->next = pre->next;
    pre->next = copy;

}


// (2) 可以按ID来查找一个记录
Student* ds_find(DataStore* store, int id)
{
    Student* p = store->head.next; 
    while(p)
    {
        if(p->id == id)
            return p;

        p = p->next; // 下一个对象
    }
    return NULL;
}

//(3) 可以按ID删除一个记录
void ds_remove(DataStore* store, int id)
{

}

// (4) 可以打印显示所有的记录
void ds_print(DataStore* store)
{
    Student* p = store->head.next; 
    while(p)
    {
        printf("ID: %d, name: %s\n", p->id, p->name);
        p = p->next; // 下一个对象
    }
}

 

[语法]以文本形式存储

  • 按行存储把每个单元的数据格式化为一行(末尾加上\n),写入文件。例如,保存ip和port
    char ip[]=”192.168.1.100″;
    int port = 8080;char line[256];
    sprintf(line, “ip=%s\n”, ip);
    fwrite(line, 1, strlen(line), fp);sprintf(line, “port=%d\n”, port);
    fwrite(line, 1, strlen(line), fp);

 

  • 也可以直接使用fprintf函数char ip[]=”192.168.1.100″;
    int port = 8080;fprintf (fp, “ip=%s\n”, ip);
    fprintf (fp, “port=%d\n”, port);fprintf:第一个参数是文件指针,后面的参数和printf,直接将数据格式化成字符串并写入文件。
  • 按行解析解析时复杂度相对较高

(1) 按行读取,每次读取一行。
由于不知道每行是多长,所以用fread读取时,需要检测是否已经读到了 \n这个分隔符。(有点复杂)推荐使用fgets函数,这个函数已经把上面的逻辑给封装好了。fgets内部会检查,当读到字符\n时,停止读取。返回实际读取的字节长度。

(2) 对读取到的每一行进行解析(参考15.3讲的解析方法)

 char buf[512];
while(! feof(fp))
{
char* line = fgets(buf, 512, fp);
if(line)
{
printf("got: %s", line);
}
}

注: line末尾有一个\n字符

保存结构体时,可以把结构体的数据格式化为一行。例如,
struct Student
{
    int id; // id
    char gender; // 性别
    char name[16]; // 年龄
};
Student someone = { 20150101, 'M' , "Noname" };

fprintf("id=%d,gender=%c,name=%s\n",
      someone.id,  someone.gender,  someone.name);
保存结构体时,可以把结构体的数据格式化为一行。例如,
struct Student
{
    int id; // id
    char gender; // 性别
    char name[16]; // 年龄
};
Student someone = { 20150101, 'M' , "Noname" };

fprintf("id=%d,gender=%c,name=%s\n",
      someone.id,  someone.gender,  someone.name);
按行解析一行,一般用sscanf是无法胜任的。
比如,
char line[] = "id=123,name=shaofa,hometown=anhui";
int id;
char name[32];
char hometown[32];
sscanf(line, "id=%d,name=%s, hometown=%s", 
     &amp;id, name, hometown);
sscanf只适合提取数字,不能提取字符串!

 

[语法]文件的随机访问-fseek

  • fseekANSI C函数:使用fseek可以实现文件FILE*的随机访问
    int fseek(FILE *stream, long offset, int mode);注:本篇只讨论在“读”文件时候的随机访问技术 

    参数
    stream: 文件指针
    offset: 一个整数,表示偏移值
    mode : 相对位置
    返回值
    0,操作成功;-1,操作失败

     

    跳到第100个字节的位置
    fseek(fp, 100, SEEK_SET);

    跳到倒数100字节的位置
    fseek(fp, 100, SEEK_END);

    跳到当前位置往前100个字节
    fseek(fp, -100, SEEK_CUR);
    跳到当前位置往后100个字节
    fseek(fp, 100, SEEK_CUR);
    什么叫“当前位置”??

文件位置指示器 file-offset indicator
每个被打开的文件对象FILE*,其数据结构里都有一个位置指示器,表示当前的读/写位置。(当前位置到文件头的距离)

当fopen打开文件时,位置指示器的值为0
当fread读取字节时,位置指示器的值会增加相应的字节数
例如,读取128个字节,则位置指示器的值就增加 128,继续fread,则继续增加
当fseek时,会调整位置指示器的值
例如,fseek(fp, 100, SEEK_SET)则位置 指示器的值被设定为100。fseek(fp, 100, SEEK_END)则指示器的值为filesize-100。

 

举例:
文件中按字节保存了100个struct对象的数据,每个对象使用了字节数是一样的。

读第80个对象的数据

Student stu;
fseek(fp, 79* sizeof(Student), SEEK_SET);
fread(&stu, 1, sizeof(Student), fp);

[语法]数据的存储方式

原则:

原则:能够写入,也能够读出并还原。(读出指的是数据的解析和还原)
如果写到文件里,却没有办法读出,那就是一个失败的设计

例如,你2个int变量,值为:12345和6790
失败的存储方法:
char buf[128];
sprintf(“%d%d”, a, b);
fwrite(buf, 1, strlen(buf), fp);

可行的存储方法:
char buf[128];
sprintf(“%d,%d”, a, b); // 以逗号分隔
fwrite(buf, 1, strlen(buf), fp);
如果不以逗号分隔,则无法对数据进行解析。数据白存了!

按字节存储:所有数据,在内存里的表现都是一串字节,因此,只要将这些字节存入即可。

char / short / int : 占1,2,4个字节
float / double: 4,8个字节
数组:
字符数组:
结构体:
指针:另行讨论

  • 基本类型的变量
    char/short/int/float/double型变量的存储
    只需要知道变量地址和大小

// 写入
int a = 0x12345678;
int b = 0x0A0A0A0A;
fwrite(&a, 1, 4, fp); // 4个字节
fwrite(&b, 1, 4, fp); // 4个字节

// 读取
int a ,b;
fread(&a, 1, 4, fp);
fread(&b, 1, 4, fp);

  • 数组的存储
    地址:即数组名
    字节数:手工计算

float arr[4];
fwrite(arr, 1, 4*4 , fp);

// 读取:由于不知道一共存了多个少float,需要循环读取
while(! feof(fp))
{
float a;
if(fread(&a, 1, 4, fp) <= 0)
{
break;
}
}

  • 字符数组的存储
    (注:这里只是描述一种比较简单的存取方式)

定长方式存取:不论有效长度是多少,统一存储32个字节

char text[32];
fwrite(text, 1, 32, fp);

fread(text, 1, 32, fp);

  • 结构体的存储
    有两种办法,都比较简单。
    第一种办法:直接存取整个结构体。
    第二种办法:把每个成员变量依次存储。
    struct Student
    {
    int id;
    char name[16];
    int scores[3];
    };

整体存取
Student s = {201501, “shaofa”, {90,90, 90} };

// 写入
fwrite(&s, 1, sizeof(s), fp);

// 读出
fread(&s, 1, sizeof(s), fp);

把每个成员变量依次分别存取
Student s = {201501, “shaofa”, {90,90, 90} };

// 写入
fwrite(&s.id, 1, sizeof(s.id), fp); // int
fwrite(s.name, 1, sizeof(s.name), fp);// char[]
fwrite(s.score, 1, sizeof(s.score), fp); // int[]

// 读出
fread(&s.id, 1, sizeof(s.id), fp);
fread(s.name, 1, sizeof(s.name), fp);
fread(s.score, 1, sizeof(s.score), fp);

  • 指针的存储
    指针要么不存储,要么存储它指向的对象的内容
    (指针本身没必要存储,它只是一个地址)

struct Car
{
char maker[32]; // 制造商
int price; // 价格
};
struct Citizen
{
char name[32]; // 名字
int deposite; // 存款
Car* car; // NULL时表示没车
};

Car* car = (Car*) malloc(sizeof(Car));
strcpy(car->maker, “Chevrolet”);
car->price = 10;

Citizen who = { “shaofa”, 100};
who.car = car;

如何存储car的信息??显示不能存储指针,因为指针只是一个地址。重启程序之后,要能够从文件中还原出Car的信息才行。

写入文件: 存储car信息
if(who.car != NULL)
{
fwrite(“Y”, 1, 1, fp); // 存入一个字节’Y’
fwrite(who.car->maker, 1, 32, fp);
fwrite(&who.car->price,1, 4, fp);
}
else
{
fwrite(“N”, 1, 1, fp); // 存入一个字节’N’
}

从读文件中读出
char has = ‘N’;
fread(&has, 1, 1, fp);
if(has == ‘Y’) // 先看有没有car的信息
{
Car* car = (Car*) malloc(sizeof(Car));
fread(car->maker, 1, 32, fp);
fread(&car->price, 1, 4, fp);
}

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

struct Student
{
    char gender; // 性别
    int id; // id
    char name[16]; // 年龄
};

struct Car
{
    char maker[32]; // 制造商
    int  price;  // 价格
};
struct Citizen
{
    char name[32]; // 名字
    int  deposite; // 存款
    Car* car;  // NULL时表示没车
};


// 存储数据
int save()
{
    const char* filename = "c:/test/aaa.xyz";
    FILE* fp = fopen(filename, "wb" );
    if(fp == NULL)
    {
        printf("failed to open file!\n");
        return -1;
    }

    Car* car = (Car*) malloc(sizeof(Car));
    strcpy(car->maker, "Chevrolet");
    car->price = 10;

    Citizen  who = { "shaofa", 100};
    who.car = car;

    fwrite(who.name, 1, 32, fp);
    fwrite(&who.deposite, 1, 4, fp);
    if(who.car != NULL)
    {
        fwrite("Y", 1, 1, fp); // 存入一个字节'Y'
        fwrite(who.car->maker, 1, 32, fp);
        fwrite(&who.car->price,1, 4, fp);
    }
    else
    {
        fwrite("N", 1, 1, fp); // 存入一个字节'N'
    }


    fclose(fp);
    return 0;
}

// 读取数据
int load()
{
    const char* filename = "c:/test/aaa.xyz";
    FILE* fp = fopen(filename, "rb" );
    if(fp == NULL)
    {
        printf("failed to open file!\n");
        return -1;
    }

    Citizen who;
    fread(who.name, 1, 32, fp);
    fread(&who.deposite, 1, 4, fp);

    char has = 'N';
    fread(&has, 1, 1, fp);
    if(has == 'Y') // 先看有没有car的信息
    {
        Car* car = (Car*) malloc(sizeof(Car));
        fread(car->maker, 1, 32, fp);
        fread(&car->price, 1, 4, fp);
        who.car = car;
    }
    else
    {
        who.car = NULL;
    }
    

    fclose(fp);
    return 0;
}

int main()
{
    //save();

    load();
    return 0;
}

 

[语法]文件的读取-fread

读取数据
size_t fread(void *buf, // 存储到目标内存地址
size_t size, // 设为1
size_t nelem, // 最多读取多个字节
FILE *stream);

返回值: 实际读取到的字节的个数

#include <stdio.h>
#include <string.h>

struct Student
{
    char gender; // 性别
    int id; // id
    char name[16]; // 年龄
};

// 存储数据
int save()
{
    const char* filename = "c:/test/aaa.xyz";
    FILE* fp = fopen(filename, "wb" );
    if(fp == NULL)
    {
        printf("failed to open file!\n");
        return -1;
    }

    int id = 201510;
    fprintf(fp, "%d\n", id);

    float score = 98.5;
    fprintf(fp, "%.1f\n", id);

    fclose(fp);
    return 0;
}

// 读取数据
int load()
{
    const char* filename = "c:/test/aaa.xyz";
    FILE* fp = fopen(filename, "rb" );
    if(fp == NULL)
    {
        printf("failed to open file!\n");
        return -1;
    }

// 	char buf[128];
// 	int n = fread(buf, 1, 128, fp);

// 	char buf[4];
// 	while(! feof (fp)) 
// 	{
// 		int n = fread (buf, 1, 4, fp);
// 		if( n > 0)
// 		{
// 			printf("read %d bytes \n", n);
// 		}		
// 	}

    char buf[256];
    while(1)
    {
        char* line = fgets(buf, sizeof(buf), fp);
        if(!line)
            break;

        printf("%s", line);
    }

    fclose(fp);
    return 0;
}

int main()
{
    //save();

    load();
    return 0;
}