C++ 面向对象篇

简介: C++程序在执行时,将内存大致分为四个区域;- 代码区:存放函数体的二进制代码,操作由系统管理- 全局区:存放全局变量和静态变量以及常量- 栈区:由编译器自动分配释放,存放函数的参数值(形参),局部变量等- 堆区:由程序员分配和释放,若程序员不手动释放,系统在程序结束时自动回收

1 内存分区模型

C++程序在执行时,将内存大致分为四个区域;

  • 代码区:存放函数体的二进制代码,操作由系统管理
  • 全局区:存放全局变量和静态变量以及常量
  • 栈区:由编译器自动分配释放,存放函数的参数值(形参),局部变量等
  • 堆区:由程序员分配和释放,若程序员不手动释放,系统在程序结束时自动回收

1.1 程序运行前

代码区:

  • 存放CPU执行的机器指令
  • 代码区是共享的,共享的目的是对于频繁被执行的程序,只需要在内存中存在一份
  • 代码区是只读的,使其只读是为了防止程序意外修改其指令

全局区:

  • 全局变量、静态变量、常量{字符串常量,其他全局常量(const局部常量不存放在全局区)}存在在此区域
  • 该区域数据在程序结束后由操作系统释放

1.2 程序运行后

栈区:

  • 不要返回局部变量的地址,因为系统会自动回收此地址

堆区:

  • 在C++中主要利用new,malloc在堆区开辟内存
  • 释放内存空间一般使用delete,free对应上方2个申请内存空间的方法

2 引用

作用:

  • 给变量取别名,别名指向原名的地址
  • 别名修改其值,原名同样发生改变,因为其地址指向同一个

注意事项:

  • 引用必须初始化
  • 引用初始化之后就不能进行修改其指向的地址;内部实现是一个指针常量,指向的地址上的数据内容可以修改,但不能变更指向地址
用法:数据类型 &别名 = 原名

2.1 引用做函数参数

作用:

  • 引用作为形参进行传递,形参发生改变,实参同样进行改变
  • 可以简化指针的使用,因为引用指向同一个地址,修改同步

使用:

void swap(int &a,int &b){
...
//交换引用变量,实参随着形参的变化而变化
}

2.2 引用做函数返回值

作用:

  • 引用可以作为函数返回值进行返回
  • 返回的引用值可以作为左值

注意事项:

  • 不要将局部变量作为引用返回,因为局部变量定义在栈区,会随着函数的结束而被回收
  • 一旦局部变量被回收,则引用指向的是一个野地址,无意义

2.3 常量引用

作用:

  • 用来修饰形参,防止形参误操作,改变实参的数据
  • 修饰局部变量时,内部实现为const修饰的指针常量

用法:

int a = 10;

const int &b = a;

//const int *const b = &a;

//上述等价

3 函数

3.1 函数默认参数

默认参数必须连续的定义在形参列表末尾,且不能被非默认参数隔断

void func(int a,int b=10,int c=20){
...
}

如果函数声明的形参拥有默认参数,则在函数实现部分不能在出现默认参数定义

结论:函数声明和函数实现只能有一个存在默认参数

void func(int a,int b=10);
void func(int a,int b){
...
}

3.2 占位参数

在形参列表中允许占位参数,但在实参传递时,必须填充;

占位参数也可以拥有默认参数

void func(int = 20,double){
...
}
int main(){
func(10,0.5f);
return 0;
}

3.3 函数重载

3.3.1 函数重载概述

重载:

  • 函数名可以重复,但形参列表个数、类型、顺序需不一致
  • 返回类型不相同与否,不能作为函数重载的条件
  • 在同一作用域
void func004(int a){
    
}
void func004(double a){
    
}
void func004(int a,double b){
    
}
void func004(double a,int b){
    
}

3.3.2 函数重载的注意事项

引用作为函数重载的条件:

  • 以下调用会引用func005(int &a)
  • 因为a是一个局部变量,它的地址声明在栈区
int a = 10;
func005(a);

void func005(int &a){

  cout << "int &a" << endl;

}
  • 以下调用会引用func005(const int &a)
  • 因为b和10是一个局部常量,只允许只读操作,与上述重载函数不存在二义性
 func005(10);
 const int b = 10;
 func005(b);

void func005(const int &a){

  cout << "const int &a" << endl;

}

当函数重载使用默认参数时:

  • 当函数重载出现二义性时,会出现错误,如下所示
  • 例如调用func006(10);时,编译器不知道该调用哪一个函数,故报错
void func006(int a){
...
}

void func006(int a,int b = 10){
...
}

4 类和对象

面向对象三大特性:

  • 封装
  • 继承
  • 多态

4.1 封装

4.1.1 意义

  • 将属性和行为作为一个整体
  • 将属性和行为加以权限控制
class Circle{
//私有访问权限
private:
    //私有成员变量半径r
    int r;//半径
    
//公共访问权限
public:
    //构造函数,给私有成员半径r赋初始值
    Circle(int r):r(r){}
    //公共权限函数,计算周长
    double calculate(){
        return 2 * PI * r;
    }
};

int main(int argc, const char * argv[]) {
    Circle l_circle(2);
    double zc = l_circle.calculate();
    cout << "周长=" << zc << endl;
    return 0;
}

4.1.2 访问权限

  • Private(私有权限):成员 类内可以访问 类外不可以访问
  • Protected(保护权限):成员 类内可以访问 类外不可以访问
  • Public(公共权限):成员 类内可以访问 类外可以访问

注意事项:Private和Protected区别在于,当继承时

  • 如果继承权限为Protected,子类可以访问父类的Protected和Publich成员,但不能访问Private成员
  • 如果继承权限为Private,子类不能访问父类定义的Private、Protected和Publich成员

4.1.3 struct和class区别

struct和class区别在于默认访问权限不同

  • struct 默认访问权限为公共
  • class 默认访问权限为私有

4.2 对象的初始化和清理

4.2.1 构造函数和析构函数

构造函数意义:

  • 主要作用于创建对象时,给类成员赋初始化值
  • 构造函数由编译器自动调用
  • 每一个类都会存在一个public访问权限的空参默认构造函数

析构函数意义:

  • 主要用于在对象销毁前,系统会自动调用,将此对象内存地址回收

构造函数语法:类名(){}

  • 构造函数没有返回值,且不能写void
  • 构造函数名称与类名一致
  • 构造函数可以含有形参,默认无参,可以发生重载
  • 构造函数由编译器自动调用,且调用一次

析构函数语法:~类名(){}

  • 析构函数没有返回值,且不能写void
  • 析构函数与类名一致,且在前方将~符号
  • 析构函数不能含有参数,不能发生重载
  • 析构函数由编译器自动在对象销毁前调用,且调用一次

4.2.2 构造函数的分类及调用

分类方式:

  • 按参数分:有参构造和无参构造
  • 按类型分:普通构造和拷贝构造
拷贝构造函数写法:const 类名 &对象名
    Student(const Student &stu){
        name = stu.name;
        age = stu.age;
        score = stu.score;
    }

调用方式:

  • 括号法
    //默认构造函数,使用默认构造函数时,不要添加();
    //因为编译器会认定为一个函数声明
    Student stu; 
    Student lisi("李四",23,90.0); //有参构造函数
    Student wangwu(lisi); //拷贝构造函数
  • 显示法
    Student niuer = Student("牛二");//有参构造函数
    Student zhaoer = Student(niuer);//拷贝构造函数
    
    //匿名对象
    //执行完此行之后,内存空间被系统回收
    Student("匿名");
    
    //不要利用拷贝构造函数初始化匿名对象
    //编译器会认定Student(zhaoer) === Student zhaoer;
    //则认定为重新定义一个无参构造类对象,但此名称已被使用,所以产生重定向
    Student(zhaoer);//语法错误
  • 隐式转换法
    Student zhaosan = 10;//等价于 Student zhaosan = Student(10);
    Student wangqi = zhaosan;//拷贝构造

4.2.3 拷贝构造函数

使用构造函数的调用时机:

  • 使用一个已经创建完毕的对象来初始化一个新对象
  • 值传递的方式给函数参数传值

调用Student lisi;语句时,通过无参默认构造函数建立一个对象

当调用execute(lisi);语句时,则在形参列表通过拷贝构造函数建立一个对象形参

void execute(Student stu){
...
}
...
Student lisi;
execute(lisi);
  • 以返回值的方式返回局部对象

在当下测试时,返回的是局部对象的地址,也就是说,两者对象指向同一个地址

并不会再去调用拷贝构造函数,然后将建立的地址进行返回

结论:编译器默认优化,故没有调用拷贝函数,关闭默认优化则会显现

Student execute(){
...
return Student("name");
}
...
Student stu = execute();

4.2.4 构造函数调用规则

默认情况下,C++编译器至少给一个类添加三个函数:

  • 默认无参构造函数
  • 默认析构函数
  • 默认拷贝构造函数,对属性进行值拷贝

规则:

  • 如果程序员定义了有参构造函数,则C++不再提供默认构造函数,但会提供默认拷贝构造函数
  • 如果程序员定义了拷贝构造函数,则C++不再提供其他构造函数(默认构造函数和默认拷贝构造函数)

4.2.5 深拷贝和浅拷贝

  • 浅拷贝:值拷贝

浅拷贝带来的问题:堆区的内存被重复释放

当建立一个对象,在构造函数中给成员id在堆区开辟一个内存空间,并对该对象进行初始化

然后通过编译器默认拷贝函数进行浅拷贝

由于id为指针,两个对象指向的都是同一堆区地址,于是当一个对象被回收之后,调用析构函数

清除id的内存引用;随机另外一个对象也被回收,同样调用析构函数;会出现堆区内存被重复释放;

此处的空判断,是对当前对象的成员id的地址进行判断,堆区内存引用虽被释放,但内存地址仍旧存在;

    string name;
    int *id;
...
Student(string name,int id){
    this->name = name;
    this->id = new int(id);
    }
    
~Student(){
    if(id != NULL){
        delete id;
        id = NULL;
        }
    cout << "析构函数-释放资源" << endl;
    }
...
 //浅拷贝
    Student zhangsan("张三",110);
    Student lisi(zhangsan);
  • 深拷贝:在堆区重新申请空间,进行拷贝操作

解决办法:不使用系统提供的默认拷贝构造函数,通过自定义一个拷贝构造函数

在堆区内重新开辟一段内存地址存放id,这样两个对象的成员id所指向的内存地址不一样,就不会产生上述问题。

Student(const Student &stu){
        name = stu.name;
        id = new int(*stu.id);
    }

总结

  • 浅拷贝:会创建一个新的对象,将旧对象的数据内容拷贝到新对象之中;如果是基本类型,则通过值拷贝;如果是引用类型,则将旧对象的成员地址拷贝给新对象;也就是说新旧对象不一样,但引用类型成员的地址指向同一个;
  • 深拷贝:对于基本数据类型,深拷贝同样采用值传递;对于引用类型,则重新在堆区申请内存空间,并使用旧对象的数据对新对象进行初始化。

4.2.6 初始化列表

语法:构造函数():属性1(值1),属性2(值2){}

语法:构造函数(形参1 标识符1,形参2 标识符2):属性1(标识符),属性2(标识符2){}

4.2.7 类对象作为类成员

C++类中的成员变量可以是另一个类的对象,称为对象成员;

当其他类对象作为本类成员,先构造其他类对象,然后在构造本身;

本类先调用析构函数,然后在由其他类成员调用其析构函数

4.2.8 静态成员

静态成员即在成员变量和成员函数前加static

静态成员访问方式:

  • 通过类名::静态成员(静态变量与静态函数一致)
  • 通过初始化类对象进行访问

静态成员变量:

  • 所有对象共享一份数据
  • 在编译阶段分配内存
  • 类内声明,类外初始化

静态成员函数:

  • 所有对象共享同一个函数
  • 静态成员函数只能访问静态成员变量
  • 静态成员函数也拥有访问权限

静态函数只能调用静态变量:因为静态函数在内存中只有一份,而非静态成员变量,可以被多个类对象创建引用,编译器无法识别静态函数中的非静态变量是哪一个类对象的成员,故无法引用

4.3 C++对象模型和this指针

4.3.1 成员变量和成员函数分开存储

在C++中,类的成员变量和成员函数分开存储,只有非静态成员变量才属于类的对象上;

在下列代码中,除了非静态变量age属于类对象上,其余都不属于;则Student类大小为4字节(32位),静态成员变量、静态成员函数、非静态成员函数都不熟类对象上的内存空间

class Student{
 int age;
 static int id;
 void func1(){}
 static void func2(){}
};
Student stu;

空对象占用内存空间为1字节;是为了区分空对象在内存上的位置;

4.3.2 this指针

this指针指向被调用的成员函数所属的对象

用途:

  • 当形参和成员变量同名时,可以用this指针区分
  • 在类的非静态成员函数中返回对象本身,可使用return *this

4.3.3 空指针访问成员函数

空指针允许调用成员函数,但成员函数内不允许调用成员变量;

因为成员变量没有实际的对象,没有实际地址;

void test(){
    Student *stu = NULL;
    stu->toString();
}

4.3.4 const修饰成员函数

常函数:

  • 成员函数后加const后,称为常函数
  • 常函数内不可以修改成员属性
  • 成员属性声明时加关键字mutable后,允许在常函数中修护成员属性
class Dog{
private:
    string name;
    mutable int age;
public:
    ///常函数:不允许修改成员属性
    ///this指针的本质是指针常量,不允许修改指向地址,但可以修改其值
    ///Dog * const this;
    ///当成员函数加上const之后,this指针变为了 const Dog* const this;即地址也不能修改,值也不能修改
    void eat() const{
        //this->name = "aaa"; //error
    }
    
    ///常函数
    ///当想要在常函数中修改成员属性,即需要在成员属性前加mutable
    void bark() const{
    ///age 被mutable修饰,故可以在常函数中修改
        this->age = 7;
    }
};

常对象:

  • 声明对象前加const,称该对象为常对象
  • 常对象依旧不能修改成员属性,除非修改加有mutable修饰的成员变量即可以修改
  • 常对象只能调用常函数
常对象之所以不能调用普通函数,是因为普通函数可以修改成员属性;

如果常对象可以调用普通函数,则与常对象不能修改非mutable成员属性相违背

4.4 友元

友元:让有些私有属性,可以被类外一些特殊的函数和类进行访问

友元三种实现:

  • 全局函数做友元
  • 类做友元
  • 成员函数做友元

4.4.1 全局函数做友元

访问类中私有成员变量和私有成员函数
只需将访问函数放到类中进行声明并前方加friend关键字进行修饰即可

class Attribute{
    //声明全局友元函数
    friend void visitor(Attribute *attribute);
private:
    string privateAttr;
public:
    string publicAttr;
    
public:
    Attribute(){
        privateAttr = "私有属性";
        publicAttr = "公共属性";
    }
    
private:
    void toString(){
        cout << "正在访问私有函数" << endl;
    }
};

///访问类中私有成员变量和私有成员函数
///只需将访问函数放到类中进行声明并前方加friend关键字进行修饰即可
void visitor(Attribute *attribute){
    cout << "访问成员:" << attribute->privateAttr << endl;
    attribute->toString();
}

4.4.2 类做友元

类A中包含一个类B成员对象,若A对象想要访问B的私有属性,则需要在类B中添加友元类声明

class B{

friend class A;

...

};

class Visitor{
public:
    Building *attr;
    Visitor();
    void behavior();
};

class Building{
    //声明Visitor类为本类的友元
    friend class Visitor;
private:
    string style;
public:
    string name;
public:
    Building();
};

///类外实现类内构造函数
Building::Building(){
    name = "圆明园";
    style = "中式";
}

///类外实现类内构造函数
Visitor::Visitor(){
    attr = new Building;
}

///类外实现类内公共成员函数
///在类外实现成员函数,跟类内一样,对成员函数、成员变量拥有同样的访问权限
void Visitor::behavior(){
    //访问成员对象公共属性
    cout << "访问建筑:" << attr->name << endl;
    //访问成员对象私有属性
    cout << "建筑样式:" << attr->style << endl;
}

4.4.3 成员函数做友元

不声明类做为友元类,声明类中成员函数作为友元函数,让成员函数可以访问成员对象的私有属性

class Consumer{
public:
    Phone *phone;
    Consumer();
    void svip();
    void phoneType();
};

class Phone{
    //声明另一个类的函数为友元函数,则此函数可以访问本类中的私有属性
    friend void Consumer::svip();
private:
    string model;
public:
    string name;
    Phone();
};

Phone::Phone(){
    model = "麒麟9000";
    name = "华为mate";
}

Consumer::Consumer(){
    phone = new Phone;
}

//友元函数,已经在对象类中声明此函数为友元函数,运行访问成员对象的私有属性
void Consumer::svip(){
    cout << "名称:" << phone->name;
    cout << " ";
    cout << "型号:" << phone->model;
}

//非友元函数,无法访问成员对象的私有属性
void Consumer::phoneType(){
    cout << "名称:" << phone->name << endl;
    //cout << " ";
    //cout << "型号:" << phone->model;
}

4.5 运算符重载

对运算符重新进行定义,赋予一种新的功能

4.5.1 加号运算符重载

通过成员函数重载运算符+

   Person operator +(const Person &a){
        Person temp;
        temp.age = this->age + a.age;
        temp.name = this->name + a.name;
        return temp;
    }
    //运算符重载函数可以发生重载
     Person operator +(int age){
        return Person("重载运算符",this->age + age);
    }

通过全局函数重载运算符+

Person operator + (const Person &a,const Person &b){
    Person temp;
    temp.name = a.name + b.name;
    temp.age = a.age + b.age;
    return temp;
}

调用

void test01(){
    Person zhangsan("张三",22);
    Person lisi("李四",24);
    ///下面两种方式都可以调用函数重载运算符+
    //成员重载函数本质:Person wangwu = zhangsan.operator+(lisi);
    Person wangwu = zhangsan + lisi;
    
     //全局重载函数本质:Person zhaoer = zhaoer.operator+(zhangsan,lisi);
    Person zhaoer = zhangsan + lisi;
    
    //运算符重载函数可以发生重载
    Person liqi = zhangsan + 20;
    wangwu.toString();
}

4.5.2 左移运算符重载

左移运算符重载一般不放到成员函数内,一般放到全局函数;

因为成员函数内需要传入2个对象才能完成需求,但又不符合需要;

例如:lisi.operator <<(wangwu),但不符合 cout << wangwu的需求

cout的类型为ostream,通过返回ostream类型,形成链式

ostream& operator <<(ostream &cout,const Person &current){
    return cout << "姓名:" << current.name << " " << "年龄:" << current.age;
}
...
Person lisi("李四",24);
cout << lisi << endl;

4.5.3 递增运算符重载

class MyInteger{
    friend MyInteger& operator ++(MyInteger &myint);
    friend ostream& operator <<(ostream &cout,const MyInteger &myint);
    friend MyInteger operator ++(MyInteger &myint,int);
private:
    int count;
public:
    MyInteger(){
        count = 0;
    }
};

//重载前置++运算符
MyInteger& operator ++(MyInteger &myint){
    myint.count++;
    return myint;
}

///重载后置++运算符
///operator++ (int) int代表占位参数,用来区分前置和后置++
MyInteger operator ++(MyInteger &myint,int){
    MyInteger temp = myint;
    myint.count++;
    return temp;
}
//重载<<运算符
ostream& operator <<(ostream &cout,const MyInteger &myint){
    return cout << myint.count;
}


void test02(){
    MyInteger myint;
    //前置++
    cout << ++(++myint) << endl;
    cout << myint << endl;
    
    //后置++
    cout << myint++ << endl;
    cout << myint << endl;
}

4.5.4 赋值运算符重载

如果类中有属性指向堆区,赋值操作会引起深浅拷贝问题;

在进行重载=运算符时,应判断类中是否存在堆区属性,如果存在,则应该清空,然后进行深拷贝赋值

class Calculate{
    friend void test03();
private:
    int *count;
    
public:
    Calculate(int count){
        //内存分配到堆区
        this->count = new int(count);
    }
    
    ~Calculate(){
        if(count != NULL){
            delete count;
            count = NULL;
        }
    }
    ///重载=运算符
    Calculate& operator=(const Calculate &param){
        if(count != NULL){
            delete count;
            count = NULL;
        }
        this->count = new int(*param.count);
        return *this;
    }
};

void test03(){
    Calculate c1(10);
    Calculate c2(20);
    Calculate c3(30);
    
    c3 = c2 = c1;
    cout << "value = " << *c3.count << endl;
}

4.5.5 关系运算符重载

class Compare{
    int num;
public:
    Compare(int num){
        this->num = num;
    }
    
    //重载>运算符
    bool operator >(const Compare &a){
        if(this->num > a.num){
            return true;
        }
        return false;
    }
    //重载<运算符
    bool operator <(const Compare &a){
        if(this->num < a.num){
            return true;
        }
        return false;
    }
    //重载==运算符
    bool operator ==(const Compare &a){
        if(this->num == a.num){
            return true;
        }
        return false;
    }
};

void test04(){
    Compare a(10);
    Compare b(20);
    cout << "a>b " << (a>b) << endl;
    cout << "a<b " << (a<b) << endl;
    cout << "a==b " << (a==b) << endl;
}

4.5.6 函数调用运算符重载

  • 函数调用运算符()也可以重载
  • 由于重载后使用的方式非常像函数的调用,因此称为仿函数
  • 仿函数没有固定写法,非常灵活
class Operate{
public:
    void operator() (string content){
        cout << content << endl;
    }
    
    void operator() (int content){
        cout << content << endl;
    }
    
    int operator() (int a,int b){
        return a+b;
    }

};

void test05(){
    Operate operat;
    operat("hello world!");
    
    int result = operat(10,20);
    operat(result);
}

4.6 继承

基类又称父类

派生类又称子类

子类通过继承父类,可以获取父类表现为共性的公共属性,也保留自身特性的成员属性

4.6.1 继承方式

继承方式:

  • 公共继承

继承方式为公共属性,则父类的公共成员属性与保护成员属性在子类保持不变,父类私有成员不允许访问

  • 保护继承

继承方式为保护属性,则父类的公共成员属性在子类变为保护成员属性,父类保护成员属性在子类保持不变,父类私有成员不允许访问

  • 私有继承

继承方式为私有属性,则父类的公共成员属性与保护成员属性在子类变为私有成员属性,父类私有成员不允许访问

class Animal{
private:
    string sounds;
protected:
    string moving;
public:
    string name;
};
//继承权限为公共权限
class Dog: public Animal{
public:
    void func(){
        name = "狗";
        moving = "爬";
        //sounds 不允许访问
    }
};
//继承权限为保护权限
class Bird: protected Animal{
public:
    void func(){
        name = "鸟";
        moving = "飞";
        //sounds 不允许访问
    }
};
//继承权限为私有权限
class Fash: private Animal{
public:
    void func(){
        name = "鱼";
        moving = "游";
        //sounds 不允许访问
    }
};


void test01(){
    Dog dog;
    dog.name = "狗";
    //保护权限,不允许访问 dog.moving = "爬";
    
    Bird bird;
    //保护权限,不允许访问 bird.name = "鸟";
    //保护权限,不允许访问 bird.moving = "飞";
    
    Fash fash;
    //私有权限,不允许访问 fash.name = "鱼";
    //私有权限,不允许访问 fash.moving = "游";
}

4.6.2 继承中的对象模型

子类继承的父类,父类的所有成员属性都会被继承(私有成员也会被继承,只是被隐藏);

子类的成员属性包括父类所有成员属性和自身成员属性;

例如:父类有公共成员属性、保护成员属性、私有成员属性各一个整型成员(设int 占四个字节)

子类有一个整型成员变量,则子类占内存空间为16字节

4.6.3 继承中的构造和析构

子类继承父类,先调用父类的构造函数,然后调用子类构造函数

程序结束时,先析构子类对象,然后析构父类对象

4.6.4 继承同名成员

当子类和父类出现同名成员属性时

  • 访问子类同名成员,可直接调用
  • 访问父类同名成员,通过作用域符号::调用
  • 如果子类出现了与父类同名成员函数,会屏蔽父类中所有同名成员函数
class Base{
public:
    int age;
    Base(){
        age = 10;
        cout << "Base construcat" << endl;
    }
    
    ~Base(){
        cout << "Base destroy" << endl;
    }
    
    void func(){
        cout << "Base func" << endl;
    }
};

class Sub:public Base{
public:
    int age;
    Sub(){
        age = 20;
        cout << "Sub construcat" << endl;
    }
    
    ~Sub(){
        cout << "Sub destroy" << endl;
    }
    
    void func(){
        cout << "Sub func" << endl;
    }
};

void test02(){
    Sub sub;
    cout << "sub age=" << sub.age << endl;
    cout << "parent age=" << sub.Base::age << endl;
    
    sub.func();
    sub.Base::func();
}

4.6.5 继承中同名的静态成员

静态成员与非静态成员出现同名,处理方式一致

  • 访问子类同名成员,直接访问计科
  • 访问父类同名成员,需要加作用域

4.6.6 多继承

C++内允许一个子类继承多个父类

语法:class 子类名称:继承方式 parent1,继承方式 parent2,...{

...

};

当父类中出现了同名的成员,则需要加作用域进行访问

4.6.7 菱形继承

菱形继承概念

两个派生类同时继承一个基类,又有某个类同时继承两个派生类

例如:有一个基类动物,羊和乌龟同时继承动物类,又存在一个沸羊羊双面龟类继承羊和乌龟。

菱形继承存在的问题:

  • 羊和乌龟同时继承动物的属性,当沸羊羊双面龟类使用其共有属性时,存在二义性

可通过作用域进行访问

  • 沸羊羊双面龟继承了两份同样的公共属性

可通过虚继承进行解决,使用虚继承之后,就相当于这一个同名公共属性就只存在一份,子类共享这一个属性

//Animal称为虚基类
class Animal{
public:
    int age;
};

///添加virtual 称为虚继承
class Sheep:virtual public Animal{

};

class Turtle:virtual public Animal{

};

class Dog:public Sheep,public Turtle{
    
};

void test03(){
    Dog dog;
    dog.Sheep::age = 10;
    dog.Turtle::age = 20;
    //使用虚继承之后,就相当于这一个公共属性age就只存在一份,羊和乌龟共享着=这一个属性
    cout << "羊的年龄:" << dog.Sheep::age << endl;//输出20
    cout << "乌龟的年龄:" << dog.Turtle::age << endl;//输出20
    cout << "乌龟的年龄:" << dog.age << endl;//输出20
}

4.7 多态

4.7.1 多态的概念

多态类型:

  • 静态多态:函数重载和运算符重载
  • 动态多态:派生类和虚函数实现运行时多态

多态的区别:

  • 静态多态函数地址早绑定;编译阶段确定函数地址
  • 动态多态函数地址晚绑定;运行阶段确定函数地址

动态多态的满足条件:

  • 有继承关系
  • 子类重写父类成员函数

动态多态的使用:父类指针或引用执行子类对象

class Animal{
  
public:
    ///将此函数定义为虚函数
    void virtual speak(){
        cout << "动物在叫..." << endl;
    }
};

class Cat: public Animal{
  
public:
    void speak(){
        cout << "喵喵喵..." << endl;
    }
};

///即使传入的是子类对象,但依旧调用的是父类重名函数
///属性静态多态,地址在编译阶段确定函数地址
///如果需要执行子类同名函数,需要在运行阶段确定函数地址-动态多态,在执行函数加virtual,称为虚函数
void speaking(Animal &animal){
    animal.speak();
}
void test01(){
    ///根据传入的对象作为最终的函数形参对象-虚函数
    Cat cat;
    speaking(cat);
    
    Animal animal;
    speaking(animal);
}

4.7.2 纯虚函数和抽象类

在多态中,通常父类中的虚函数的实现是毫无意义的,主要使用的是子类重写内容,因此可以改为纯虚函数

纯虚函数语法:

virtual 返回值类型 函数名 (参数列表) = 0;

当类中包含了纯虚函数,此类也称为抽象类

抽象类特点:

  • 无法实例化对象
  • 子类必须重写抽象类中的纯虚函数,否则也属于抽象类
class Person{
public:
    ///纯虚函数,拥有纯虚函数的类称为抽象类
    ///抽象类不允许被实例化
    virtual void speaking() = 0;
};

class Student: public Person{
public:
    ///子类继承抽象类,必须重写纯虚函数;否则依旧是一个抽象类
    ///子类重写的纯虚函数,virtual可写可不写
    void speaking(){
        cout << "为中华之崛起而读书" << endl;
    }
};
void test02(){
    Person *zhangsan = new Student;
    zhangsan->speaking();
    
    Student lisi;
    lisi.speaking();
}

4.7.4 虚析构和纯虚析构

在多态中,如果子类中有成员属性内存地址在堆区,那么父类指针在释放时无法调用子类的析构函数

解决方案:将父类中的析构函数改为虚析构或者纯虚析构

虚析构和纯虚析构的共性:

  • 可以解决父类指针释放子类对象问题
  • 都需要有具体的函数实现

虚析构和纯虚析构的异性:

  • 如果为纯虚析构,则此类为抽象类,无法实例化对象

虚析构语法:

virtual ~类名(){}

纯虚析构语法:

声明: virtual ~类名() = 0;

实现: 类名::~类名(){}

虚析构或纯虚析构用来解决父类指针释放子类对象

如果子类中没有堆区数据,可以不写虚析构或纯虚析构

拥有纯虚析构函数的累属于抽象类

class Person{
public:
    ///纯虚函数,拥有纯虚函数的类称为抽象类
    ///抽象类不允许被实例化
    virtual void speaking() = 0;
    
    //虚析构,可以解决父类指针释放子类对象堆区成员属性不干净问题
//    virtual ~Person(){
//
//    }
    
    ///纯虚析构函数定义
    virtual ~Person() = 0;
};
   ///纯虚析构函数实现
    Person::~Person(){
    cout << "父类纯析构" << endl;
}

class Student: public Person{
public:
    string *name;
    Student(string name){
        this->name = new string(name);
    }
    ///子类继承抽象类,必须重写纯虚函数;否则依旧是一个抽象类
    ///子类重写的纯虚函数,virtual可写可不写
    void speaking(){
        cout << *(this->name) << "为中华之崛起而读书" << endl;
    }
    
    ~Student(){
        if(name != NULL){
            cout << *(this->name) << "对象被销毁" << endl;
            delete name;
            name = NULL;
        }
    }
};
void test02(){
    ///通过父类指针指向子类对象,当析构父类对象时,子类中的堆区对象并不会被销毁
    ///需要将父类析构函数改为虚析构或者纯虚析构
    Person *zhangsan = new Student("张三");
    zhangsan->speaking();
    delete zhangsan;
}

父类中有纯虚函数,说明这个父类是一个抽象类,不能进行实例化。但是,可以通过指针的方式指向子类的实例化对象地址,因为子类继承了父类的纯虚函数并提供了具体实现,使得子类对象满足了父类的要求,可以进行实例化。通过指针方式使用子类对象,也就是通过父类的接口访问子类的实现,实现了多态性。

5 文件操作

C++中对文件操作需要包含头文件

文件类型分为:

  • 文本文件:文件以文本的ASCII码形式存储在计算机中
  • 二进制文件:文件以文本的二进制形式存储在计算机中

操作文件的三大类:

  • ofstream:写操作
  • ifstream:读操作
  • frstream:读写操作

5.1 文本文件

5.1.1 写文件

写文件操作步骤:

  1. 包含头文件:#include
  2. 创建流对象:ofstream ofs;
  3. 打开文件:ofs.open("文件路径",打开方式)
  4. 写数据:ofs << "写入的内容";
  5. 关闭文件:ofs.close();
打开方式 备注
ios::in 为读文件而打开
ios::out 为写文件而打开
ios::ate 初始位置:文件尾
ios::app 追加方式写文件
ios::trunc 如果文件存在则删除,在创建
ios::binary 二进制方式

注意:文件打开方式可以配合使用,利用|操作符

例如:用二进制方式写文件ios::binary | ios::out

#include <fstream> 
...
void writeFile(){
    ///1.创建文件流
    ofstream ofs;
    ///2.打开文件test.txt,打开方式写文件
    ///如果不加绝对路径,则默认文件创建在与项目同级路径
    ofs.open("/Users/FranzLiszt/Downloads/test.txt",ios::out);
    ///3.写内容
    ofs << "姓名:张三" << endl;
    ofs << "年龄:32" << endl;
    ofs << "性别:不详" << endl;
    ///4.关闭文件流
    ofs.close();
}

5.1.2 读文件

读文件操作步骤:

  1. 包含头文件:#include
  2. 创建流对象:ifstream ifs;
  3. 打开文件:打开文件并判断是否打开成功,ifs.open("文件路径",打开方式)
  4. 读数据:包含四种方式
  5. 关闭文件:ifs.close();
void readFile(){
    ///1.创建文件流
    ifstream ifs;
    ///2.打开文件test.txt,打开方式读文件
    ifs.open("/Users/FranzLiszt/Downloads/test.txt",ios::in);
    ///3.判断是否打开成功
    if(!ifs.is_open()){
        cout << "文件打开失败!" << endl;
        ifs.close();
        return;
    }
    ///4.读数据
    ///4.1 第一种读取方式
    char buff1[1024] = {0};
    while (ifs >> buff1) {
        cout << buff1 << endl;
    }
    ///4.2 第二种读取方式
    char buff2[1024] = {0};
    while (ifs.getline(buff2, sizeof(buff2))) {
        cout << buff2 << endl;
    }
    ///4.3 第三种读取方式
    string buff3;
    while (getline(ifs, buff3)) {
        cout << buff3 << endl;
    }
    ///4.4 第四种方式
    char ch;
    while ((ch = ifs.get()) != EOF) {
        cout << ch;
    }
    ifs.close();
}

5.2 二进制文件

对二进制方式进行操作要将打开方式指定为ios::binary

5.2.1 写二进制文件

二进制方式写文件主要利用流对象调用成员函数write

函数原型:ostream& write(const char* buffer,int len);

字符指针buffer指向内存中一段存储空间,len是读写的字节数

void writeBinary(){
    //1.创建流对象
    ofstream ofs;
    //2.打开文件,打开方式为写|二进制文件
    ofs.open("/Users/FranzLiszt/Downloads/testBinary.txt",ios::out|ios::binary);
    char name[] = "张三";
    int age = 32;
    Person zhangsan(name,age);
    ///3.写入内容
    ofs.write((const char*)&zhangsan, sizeof(zhangsan));
    ///4.关闭流文件
    ofs.close();
}

5.2.2读二进制文件

void readBinary(){
    //1.创建流对象
    ifstream ifs;
    //2.打开文件,打开方式为写|二进制文件
    ifs.open("/Users/FranzLiszt/Downloads/testBinary.txt",ios::in|ios::binary);
    //3.判断文件是否打开成功
    if(!ifs.is_open()){
        cout << "文件打开失败!" << endl;
        ifs.close();
        return;
    }
    ///4.读取内容
    Person zhangsan;
    char buff[1024] = {0};
    ifs.read((char *)&zhangsan, sizeof(Person));
    cout << "姓名:" << zhangsan.name << endl;
    cout << "年龄:" << zhangsan.age << endl;
    ///5.关闭流文件
    ifs.close();
}

6 模版

6.1 模版的概念

模版就是建立通用的模具,提高复用性

C++另一种编程思想称为泛型编程,主要利用的技术就是模版

C++提供两种模版机制:函数模版和类模版

特点:

  • 模版不可以直接使用,它只是一个框架
  • 模版并不是万能的

6.2 函数模版

6.2.1 函数模版基本用法

函数模版的作用:

  • 建立一个通用函数,其函数返回值类型和形参类型可以不具体指明,用一个虚拟类型代替
语法:template 函数定义或声明

注:

  • template:声明创建模版
  • typename:表明其后面的符号是一种数据类型,可以用class代替
  • T:通用数据类型,名称可以自定义
//声明一个模版,告诉编译器后面的代码中的T不要报错,T是一个通用数据类型
template<typename T> void swap(T &a,T &b){
    T temp = a;
    a = b;
    b = temp;
}
...
void test01(){
    int a = 10;
    int b = 20;
    //1.自动类型推导
    swapValue(a, b);
    //2.显示指定类型
    swapValue<int>(a, b);
    cout << "a=" << a << endl;
    cout << "b=" << b << endl;
}

6.2.2 函数模版注意事项

注意事项:

  • 自动类型推导,必须推导的一致的数据类型T,才可以使用
  • 模版必须要确定出T的数据类型,才可以使用
template

template

上述两种写法,没有区别,效果一致

6.2.3 函数模版示例

示例描述:

  • 利用函数模版封装一个排序的函数,可以对不同的数据类型数组进行排序
  • 排序按降序,排序算法为选择排序
//声明一个模版,告诉编译器后面的代码中的T不要报错,T是一个通用数据类型
template<typename T> void swapValue(T &a,T &b){
    T temp = a;
    a = b;
    b = temp;
}

/// Description 利用模版技术写一个选择排序升序排序算法
/// - Parameters:
///   - array: 数组元素首地址
///   - length: 数组内容长度
template<typename T>
void selectSort(T array[],int length){
    int min;
    for(int i=0;i<length;i++){
        min = i;
        for(int j=i+1;j<length;j++){
            if(array[min] > array[j]){
                min = j;
            }
        }
        if(i != min){
            swapValue(array[i], array[min]);
        }
    }
}

/// Description 利用模版技术写一个打印函数
/// - Parameters:
///   - array: 数组元素首地址
///   - length: 数组内容长度
template <typename T>
void printfArray(const T array[],int length){
    for(int i=0;i<length;i++){
        cout << array[i];
    }
    cout << endl;
}

void test02(){
    int a[] = {2,5,7,8,1,0};
    char b[] = "cabfdg";
    int aLength = sizeof(a) / sizeof(int);
    int bLength = sizeof(b) / sizeof(char);
    selectSort(a,aLength);
    selectSort(b,bLength);
    printfArray(a,aLength);
    printfArray(b,bLength);
}

6.2.4 普通函数与函数模版的区别

区别:

  • 普通函数调用可以发生自动类型转换(隐式类型转换)
  • 函数模版调用时,如果利用自带类型推导,不会发生隐式类型转换
  • 如果利用显示指定类型方式,可以发生隐式类型转换
///普通函数允许隐式类型转换
int normalAdd(int a,int b){
    return a+b;
}

void test03(){
    int a = 10;
    char b = 'b';
    //字符'b'对应的ascii码为98,所有自动将char类型转为int类型
    int result = normalAdd(a, b);
    cout << "reuslt=" << result << endl;
}

template <typename T>
T templateAdd(T a, T b){
    return a+b;
}

void test04(){
    int a = 10;
    char b = 'b';
    //函数模版的自动推导类型无法实现隐式类型转换,也就是无法将char转为int,无法完成下列语句
    //int result = templateAdd(a, b);
    //cout << "reuslt=" << result << endl;
    
    //函数模版的显示指定类型可以实现隐式类型转换,下列语句正常执行
    int result = templateAdd<int>(a, b);
    cout << "reuslt=" << result << endl;
}

6.2.5 普通函数与函数模版的调用规则

调用规则:

  • 如果函数模版和普通函数都可以实现,优先调用普通函数
  • 可以通过空模版参数列表来强制调用函数模版
  • 函数模版也可以发生重载
  • 如果函数模版可以产生更好的匹配,则优先调用函数模版
void myPrintf(int a,int b){
    cout << "normal function" << endl;
}

template <typename T> void myPrintf(T a,T b){
    cout << "template function" << endl;
}

///函数模版也可以发生重载
template <typename T> void myPrintf(T a,T b,T c){
    cout << "overload template function" << endl;
}
void test05(){
    int a = 10;
    int b = 20;
    int c = 30;
    ///如果函数模版和普通函数都可以实现,优先调用普通函数
    ///如果普通函数只有声明没有实现,模版函数存在,会出现编译错误,因为优先调用普通函数,但是普通函数没有实现,故错误
    myPrintf(a, b);
    
    ///可以通过空模版参数列表来强制调用模版函数
    myPrintf<>(a, b);
    
    ///函数模版也可以发生重载
    myPrintf(a, b,c);
    
    ///如果函数模版可以产生更好的匹配,则优先调用函数模版
    char ca = 'a';
    char cb = 'b';
    myPrintf(ca, cb);
}

6.2.6 模版的局限性

模版的通用性并不是万能的,某些特定数据类型,需要具体方式做特殊实现

///具体化模版
template<> bool compare(Student &a,Student &b){
    if(a.name == b.name && a.age == b.age){
        return true;
    }
    return false;
}
void test06(){
    Student zhangsan("张三",32);
    Student lisi("张三",32);
    bool result = compare(zhangsan, lisi);
    if(result){
        cout << "same" << endl;
    }else{
        cout << "different" << endl;
    }
}

6.3 类模版

6.2.1 类模版基本用法

类模版的作用:

  • 建立一个通用函数,类中成员的数据类型可以不具体指明,用一个虚拟类型代替
语法:template 类

注:

  • template:声明创建模版
  • typename:表明其后面的符号是一种数据类型,可以用class代替
  • T:通用数据类型,名称可以自定义
///类模版,对于通用的数据类型可以使用虚拟类型来代替
template <class T,class W>
class Animal{
public:
    T name;
    W age;
    Animal(T name,W age){
        this->name = name;
        this->age = age;
    }
    
    void toString(){
        cout << this->name << this->age << "岁了" << endl;
    }
};

void test07(){
    //模版参数列表
    Animal<string, int> cat("喵喵",2);
    cat.toString();
}

6.3.2 类模版和函数模版的区别

主要区别:

  • 类模版没有自动类型推导方式,只有显示指定类型方式
  • 类模版在模版参数列表可以有默认参数
///类模版参数列表允许有默认参数
template <class T,class W = int>
class Animal{
...
};
...
///类模版参数列表有了默认参数之后,在声明对象时,如果使用默认参数列表的类型,
///则在显示指定类型时可以省略
 Animal<string> cat("喵喵",2);

6.3.3 类模版成员函数创建时机

类模版中成员函数和普通类中成员函数创建时机的区别:

  • 普通类中的成员函数一开始就可以创建
  • 类模版中的成员函数在调用时才可以创建

6.3.4 类模版对象做函数参数

传入方式:

  • 指定传入类型:直接显示对象的数据类型
  • 参数模版化:将对象中的参数变为模版进行传递
  • 整个类模版化:将这个对象类型模版化进行传递
///指定模版参数传入类型
void printf1(Person<string, int> &p){
    p.toString();
}

///参数模版化
template <class T,class W>
void printf2(Person<T, W> &p){
    p.toString();
}

///整个类模版化
template <class T>
void printf3(T &t){
    t.toString();
}
void test09(){
    Person<string, int> zhangsan("张三", 32);
    ///指定传入类型
    printf1(zhangsan);
    
    ///参数模版化
    Person<string, int> lisi("李四", 24);
    printf2(lisi);
    
    ///整个类模版化
    Person<string, int> wangwu("王五",45);
    printf3(wangwu);
}

6.3.5 类模版与继承

注意事项:

  • 当子类继承的父类是一个类模版,子类在声明的时候,要指定出父类中T的类型
  • 如果不指定,编译器无法给子类分配内存
  • 如果像灵活指出父类中T的类型,子类也需要变成类模版
template <class T>
class Company{
public:
    T companyName;
};
///子类继承的父类是一个类模版,需要显示指定类的数据类型
class Department:public Company<string>{
    string departmentName;
};

///如果想要灵活的继承一个类模版,子类也可以声明成一个类模版
template <class T,class W>
class Staff: Company<T>{
public:
    string staffName;
    W departmentName;
};

6.3.6 类模版成员函数类外实现

template <class T,class W>
class Dolphin{
public:
    T name;
    W age;
    
    Dolphin(T name,W age);
    
    void toString();
};

///类模版的构造函数的类外实现
template <class T,class W>
Dolphin<T,W>::Dolphin(T name,W age){
    this->name = name;
    this->age = age;
}

///类模版的成员函数的类外实现
template <class T,class W>
void Dolphin<T, W>::toString(){
    cout << this->name << this->age << "岁了" << endl;
}

6.3.7 类模版分文件编写

类模版成员函数创建时机是在调用阶段,导致分文件编写时链接不到

解决方案:

  • 直接包含.cpp源文件
  • 将声明和实现写到同一个文件中,并更改后缀名为.hpp.hpp是约定的名称,可以自定义;.hpp文件内包含类模版声明和实现

6.3.8 类模版与友元

全局函数 类内实现 -直接在类内声明友元

全局函数 类外实现 -需要提前让编译器知道全局函数的存在

///全局函数 类外实现
///加空模版参数列表
///如果是全局函数是类外实现 需要让编译器提前知道这个函数的存在
template <class T1,class T2>class Teadcher;
template <class T1,class T2>
void printfTearch2(Teadcher<T1,T2> t){
    cout << "姓名=" << t.name << " " << "年龄=" << t.id << endl;
}


template <class T1,class T2>
class Teadcher{
    
    //全局函数 类内实现
    friend void printfTearch(Teadcher<T1,T2> &t){
        cout << "姓名=" << t.name << " " << "年龄=" << t.id << endl;
    }
    
    //全局函数 类外实现
    friend void printfTearch2<>(Teadcher<T1,T2> t);
    
private:
    T1 name;
    T2 id;
public:
    Teadcher(T1 name,T2 id){
        this->name = name;
        this->id = id;
    }
};


void test11(){
    Teadcher<string, int> zhangsan("张三",32);
    //全局函数 类内实现
    printfTearch(zhangsan);
    //全局函数 类外实现
    printfTearch2(zhangsan);
}

6.3.9 类模版案例

实现一个通用的数组类,具体描述如下:

  • 可以对内置数据类型以及自定义数据类型的数据进行存储
  • 将数组中的数据存储到堆区
  • 构造函数中可以传入数组的容量
  • 提高对应的拷贝构造函数以及operate =(重载操作符=)防止浅拷贝问题
  • 提高尾插法和尾删法对数组中的数据进行增加和删除
  • 可以通过下标的方式访问数组中的元素
  • 可以获取数组中当前元素个数和数组容量
#include <iostream>
#include <string>
using namespace std;

template<class T> class TempArray{
private:
    T *array;//数组
    int length;//数组初始化长度
    int num;//数组当前元素个数(最后一个元素下标-1)
    
public:
    TempArray(){
        
    }
    
    /// Description 构造一个T型数据类型数组,初始化大小为length
    /// - Parameter length: 数组初始化长度
    TempArray(int length){
        this->num = 0;
        this->length = length;
        this->array = new T[this->length];
    }
    
    
    /// Description 自定义拷贝构造函数
    /// - Parameter array: 拷贝的对象
    TempArray(const TempArray& copy){
        this->length = copy.length;
        this->num = copy.num;
        this->array = new T[this->length];
        for(int i=0;i<this->length;i++){
            this->array[i] = copy.array[i];
        }
    }
    
    /// Description 重载运算符=,防止浅拷贝发生的问题
    /// 返回当前TempArray对象引用,可以作为左值使用
    /// - Parameter copy: 需要复制的对象
    TempArray& operator = (const TempArray &copy){
        if(this->array != NULL){
            delete[] this->array;
            this->array = NULL;
        }
        
        this->length = copy.length;
        this->num = copy.num;
        this->array = new T[this->length];
        for(int i=0;i<this->length;i++){
            this->array[i] = copy.array[i];
        }
        return *this;
    }
    
    
    /// Description 重载操作符[]
    /// - Parameter index: 需要获取元素的下标
    T& operator[](int index){
        if(index >= length){
            return NULL;
        }
        return this->array[index];
    }
    
    /// Description 往数组内插入元素,从尾部插入
    /// - Parameter &t: 需要插入的模版数据类型
    void insertNode(const T &t){
        if(this->num > this->length - 1){
            cout << "数组已满!!!" << endl;
            return;
        }
        this->array[this->num++] = t;
    }
    
    
    /// Description 删除数组内最后一位元素
    void deleteNode(){
        if(this->num <= 0){
            cout << "数组已空!!!" << endl;
            return;
        }
        this->num--;
    }
    
    
    /// Description 获取元素当前个数
    int getArraySize(){
        return this->num;
    }
    
    
    /// Description 获取数组长度
    int getArrayLength(){
        return this->length;
    }
    
    /// Description 获取当前下标指向的元素
    T getCurrent(){
        int index = this->num - 1;
        return this->array[index];
    }
    
    /// Description 获取指定下标指向的元素
    T getNode(int index){
        return this->array[index];
    }
    
    ~TempArray(){
        if(this->array != NULL){
            delete[] this->array;
            this->array = NULL;
        }
    }
};

class Person{
private:
    string name;
    int age;
public:
    Person(){
        
    }
    Person(string name,int age){
        this->name = name;
        this->age = age;
    }
    
    void toString(){
        cout << this->name << this->age << "岁了" << endl;
    }
    
    string getName(){
        return this->name;
    }
    
    int getAge(){
        return this->age;
    }
};

void execute(){
    TempArray<int> array(10);
    array.insertNode(10);
    array.insertNode(20);
//    int size1 = array.getArraySize();
//    array.deleteNode();
//    int size2 = array.getArraySize();
//    cout << size1 << " " << size2 << endl;
    int node = array.getCurrent();
    cout << node << endl;
//    int length = array.getArrayLength();
//    cout << length << endl;
    
    
    TempArray<int> zhangsan(10);
    zhangsan = array;
    TempArray<int> lisi(array);
    int zhangsan_node1 = zhangsan.getCurrent();
    int zhangsan_node2 = zhangsan.getNode(0);
    int lisi_node1 = zhangsan.getCurrent();
    int lisi_node2 = zhangsan.getNode(0);
    cout << zhangsan_node1 << endl;
    cout << zhangsan_node2 << endl;
    cout << lisi_node1 << endl;
    cout << lisi_node2 << endl;
}

void testPerson(){
    TempArray<Person> student(10);
    cout << "当前数组元素个数:" << student.getArraySize() << endl;
    student.insertNode(Person("张三",22));
    student.insertNode(Person("李四",24));
    student.insertNode(Person("王五",35));
    cout << "当前数组元素个数:" << student.getArraySize() << endl;

    student.deleteNode();
    cout << "当前数组元素个数:" << student.getArraySize() << endl;

    Person lisi = student.getCurrent();
    Person wangwu = student.getNode(0);
    cout << lisi.getName() << endl;
    cout << wangwu.getName() << endl;
}
int main(int argc, const char * argv[]) {
    //execute();
    testPerson();
    return 0;
}
相关文章
|
2月前
|
安全 程序员 编译器
【C++篇】继承之韵:解构编程奥义,领略面向对象的至高法则
【C++篇】继承之韵:解构编程奥义,领略面向对象的至高法则
90 11
|
3月前
|
存储 安全 编译器
【C++核心】一文理解C++面向对象(超级详细!)
这篇文章详细讲解了C++面向对象的核心概念,包括类和对象、封装、继承、多态等。
30 2
|
2月前
|
存储 编译器 C语言
【C++】初识面向对象:类与对象详解
【C++】初识面向对象:类与对象详解
|
7月前
|
算法 Java 程序员
【C++专栏】C++入门 | 类和对象 | 面向过程与面向对象的初步认识
【C++专栏】C++入门 | 类和对象 | 面向过程与面向对象的初步认识
60 0
|
4月前
|
存储 安全 数据处理
【C++】C++ 超市会员卡管理系统(面向对象)(源码+数据)【独一无二】
【C++】C++ 超市会员卡管理系统(面向对象)(源码+数据)【独一无二】
106 1
|
4月前
|
算法 数据可视化 C++
【C++】C++ 学生信息管理系统(源码+面向对象)【独一无二】
【C++】C++ 学生信息管理系统(源码+面向对象)【独一无二】
|
5月前
|
存储 开发框架 Java
|
6月前
|
算法 编译器 C语言
C++进阶之路:深入理解编程范式,从面向过程到面向对象(类与对象_上篇)
C++进阶之路:深入理解编程范式,从面向过程到面向对象(类与对象_上篇)
79 3
|
5月前
|
Java C++ iOS开发
|
6月前
|
C++
C++ 是一种面向对象的编程语言,它支持对象、类、继承、多态等面向对象的特性
C++ 是一种面向对象的编程语言,它支持对象、类、继承、多态等面向对象的特性