【4】C++类之基础知识


C++三大特性:封装、继承和多态

类的基本思想是数据抽象(data abstraction)和封装(encapsulation)

类(数据抽象):

1)数据抽象

  • 接口:用户能执行的操作
  • 实现:① 类的数据成员;② 负责接口实现的函数体;③ 定义类所需的各种私有函数。

2)封装

  • 实现了类的接口和实现的分离。封装之后的类隐藏了它的实现细节,也就是说,类的用户只能使用接口而无法访问实现部分。

1、成员

C++成员通常包括三个部分:

  1. 成员变量:一般为数据,对外界封装起来的类的每个对象都具有
  2. 成员函数:申明必须写在类的内部,它的定义既可以在类的内部,也可以在类的外部。就像类的其他变量一样,成员函数是类的一个成员,它可以操作类的任意对象,可以访问对象中的所有成员

    成员函数定义在类的外部话,格式为:

    int 类名::meber_function()
    {
        //function body
    }

     

  3. 静态(static)成员:可以是数据or函数,也可以是类对外的接口。独立于对象存在,该类的所有对象共享一个静态存储区
  4. 非成员函数:类的实现常常需要一些辅助函数,尽管这些函数从概念上属于类接口的组成部分,但他们实际上并不属于类本身。如果函数在概念上属于类但是不定义在类中,则它一般应与类声明(而非定义)在头一个头文件内。在这种方式下,用户使用接口的任何部分都只需引入一个头文件。

引入this:

c++中this是一个额外的隐式函数,用来访问当前的对象,而它的本质是一个常量指针,不能把它绑定到一个常量对象上。

当我们调用一个成员函数时,用请求该函数的对象地址初始化this。

例如,调用total.isbn()  <=> Sales data::isbn(&total)

常量成员函数:

【1】常量对象与常量成员函数

 

2、类的访问限制

类的默认权限为private。

C++类的访问修饰符:private、protected、public

  • 类的一个特征就是封装,public和private作用就是实现这一目的。所以:用户代码(类外)可以访问public成员而不能访问private成员;private成员只能由类成员(类内)和友元访问
  • 类的另一个特征就是继承,protected的作用就是实现这一目的。所以:protected成员可以被派生类对象访问,不能被用户代码(类外)访问。

一般来说,作为接口的一部分,构造函数和一部分成员函数应定义在public说明符之后,而数据成员和作为实现部分的函数这应该是在private说明符之后

 private外界不可见,不能直接访问
protected外界不可见,不能直接访问;子类可以访问
public外界可以直接访问
  1. private可以通过类的成员函数、类的实例变量进行访问
  2. public无法通过类的实例变量进行访问。但是可以通过类的友元函数、友元类进行访问。
  3. protected无法通过类的实例变量进行访问。但是可以通过类的友元函数、友元类进行访问。

保护成员的可访问范围比私有成员大,比公有成员小。能访问私有成员的地方都能访问保护成员。保护成员扩大的访问范围表现在:基类的保护成员可以在派生类的成员函数中被访问。

引入保护成员的理由是:基类的成员本来就是派生类的成员,因此对于那些出于隐藏的目的不宜设为公有,但又确实需要在派生类的成员函数中经常访问的基类成员,将它们设置为保护成员,既能起到隐藏的目的,又避免了派生类成员函数要访问它们时只能间接访问所带来的麻烦。

C++就近原则:对于成员函数setName函数中使用的参数age,并不会与成员变量age产生误会,因为会就近使用形参的age。

#include <iostream>

using namespace std;

class student {
public:
    int age;
    
    void setAge(int age) 
    {
        this->age = age;
        return;
    }
};

int main(int argc, char** argv)
{
    student a;
    a.setAge(10);
    cout << "a.age = "  << a.age << endl;
    return 0;
}

输出:
a.age = 10

3、构造函数、拷贝、赋值和析构函数

1)类的构造函数(constructor):每个类都分别定义了它的对象被初始化的方式,类通过一个或几个特殊的成员函数来控制其对象的初始化过程,这些函数称为构造函数(constructor),比如setAge(int age)。

  1. 构造函数的名字和类名相同,和其他函数不一样的是:构造函数没有返回类型
  2. 构造函数也有一个(可能为空)参数列表和一个(可能为空)的函数体
  3. 构造函数不能被声明为const的。在创建一个const对象时,直到构造函数完成初始化过程,对象才能真正取得其“常量”属性。因此,构造函数在const对象的构造过程中可以向其写值。
  4. 是一种特殊的成员函数,它会在每次创建类的新对象时执行。

默认构造函数:

如果类没有显示的定义构造函数,那么编译器就会 隐式的定义一个默认构造函数(default constructor)。其具有以下特性:

  • 不需要任何实参
  • 如果类内存在初始值,用它来初始化成员;否则,执行默认初始化。
#include <iostream>
using namespace std;
class Date {
public:
    Date(int year,int month,int day):year(year), month(month), day(day){
    }
    void printData(void){
        cout <<  "year =  "  << year << endl;
        cout <<  "month = " << month << endl;
        cout <<  "day =   "  << day << endl;
    }
private:
    int year;
    int month;
    int day;
};

int main()
{
    Date date1(1,2,3);
    date1.printData();
    return 0;
}

最好令构造函数初始值的顺序与成员声明的顺序保持一致。而且如果可能的话,尽量避免使用某些成员初始化其他成员。

成员的初始化顺序与他们在类定义中出现顺序一致

构造顺序:先父后儿

  1. 先调用基类的构造函数;对于基类,先虚拟基类,后一般的基类(虚拟基类构造函数总是最先调用的
  2. 自身,先是对象成员,后自己

对于虚拟基类,即使多次被继承,构造函数也只执行一次

2)拷贝构造函数:

是一种特殊的构造函数。它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。拷贝构造函数通常用于:

  • 通过使用另一个同类型的对象来初始化新创建的对象
  • 复制对象把它作为参数传递的函数
  • 复制对象,并从函数返回这个对象

3)析构函数:

类的析构函数是类的一种特殊成员函数,它会在每次删除所创建爱你的对象时执行。析构函数有助于在跳出程序(比如关闭文件、释放内存等)钱释放资源。

#include <iostream>
using namespace std;
class Date {
public:
    Date(int year,int month,int day):year(year), month(month), day(day){
        cout << "constructor occur" << endl;
    }
    ~Date(){
        cout << "~ occur" << endl;
    }/* 析构函数 */
......
};

int main()
{
    Date *date1 = new Date(1,2,3);
    date1->printData();

    //手动删除
    delete date1;
    return 0;
}

4、类的友元

友元(friend):允许其他类或者函数访问它的非公有成员(非public成员)。只要增加一条以friend声明的即可。

#include <iostream>
using namespace std;
class Date {
public:
    Date(){}
    Date(int year,int month,int day):year(year), month(month), day(day){
        cout << "constructor occur" << endl;
    }
    ~Date(){
        cout << "~ occur" << endl;
    }
    void printData(void){
        cout << "message is :" << endl;
        cout <<  "year =  "  << year << endl;
        cout <<  "month = " << month << endl;
        cout <<  "day =   "  << day << endl;
    }
    /* 友元函数的声明 */
   friend void printAddYearOne(Date &dat0,Date &dat1,Date &dat2);
private:
    int year;
    int month;
    int day;
};

/* 友元函数的定义 */
void printAddYearOne(Date &dat0,Date &dat1,Date &dat2){
    dat0.year = dat1.year + dat2.year;
}

int main()
{
    Date date1(1,2,3);
    date1.printData();
    Date date2(1,2,3);
    date2.printData();
    Date DateTemp(1,2,3);
    printAddYearOne(DateTemp,date1,date2);
    DateTemp.printData();
    //手动删除
    return 0;
}

运行结果:

 

5、封装,继承、多重继承


1)封装有两个重要的优点:

  • 确保用户代码不会无意间破坏封装对象的状态
  • 被封装的类的具体实现细节可以随时改变,而无需调整用户级别的代码

2)继承

  • 派生类不能访问基类的私有成员
  • 派生类可以访问protected成员,其他代码不可以
  • 派生类可以通过protected/public的成员函数

调整访问控制:派生类继承父类的非private成员函数可以变成private或者protected

/* 将printfInfo私有化
 * 将proteced公有化
 */
class Father{
proteced:
    int proteced = 0;
public:
    void printfInfo();
}

class Son : public Father{
private:
    using Father::printfInfo;
public:
    using Father::proteced;
}

继承方式:

  1. 无论哪种继承方式,在派生类内部使用父类时并无差别
  2. 不同继承方式,会影响这两方面:外部代码对派生类的使用、派生类的子类

3)多重继承:一个派生类可以有多个父类(基类)

#include <iostream>

using namespace std;

/* 多重继承
 */
class Sofa
{
public:
    Sofa() {}
    void watchTv(){
        cout << "Watch TV" << endl;
    }
};

class Bed
{
public:
    Bed() {}
    void sleep(){
        cout << "sleep" << endl;
    }
};

class Sofabed : public Sofa,public Bed//如果不写public,默认是私有继承
{
public:
    Sofabed(){}
};
int main(int argc,char **argv)
{
    Sofabed testDemo;
    testDemo.sleep();
    testDemo.watchTv();
    cout << "Hello World!" << endl;
    return 0;
}

多重继承存在的问题:二义性:多个基类有相同的成员函数

解决方法:

① 调用时加上相对应的父类加以限定,例如:testDemo.Sofa::setweight(10);

② 虚拟继承:虚基类使得多个派生出的对象只继承一个基类对象

#include <iostream>

using namespace std;

/* 多重继承存在二义性
 * 虚拟继承消除二义性
 */
class Furniture
{
private:
    int weight;
public:
    Furniture() {}
    void setweight(int weight){
        this->weight = weight;
    }
    int getWeight(void) const{
        return weight;
    }//const成员函数
};

class Sofa : virtual public Furniture{
private:
    int a;
public:
    Sofa() {}
    void watchTv(){
        cout << "Watch TV" << endl;
    }

};

class Bed : virtual public Furniture
{
private:
    int b;
public:
    Bed() {}
    void sleep(){
        cout << "sleep" << endl;
    }
};

class Sofabed : public Sofa,public Bed//如果不写public,默认是私有继承
{
private:
    int c;
public:
    Sofabed(){}
};
int main(int argc,char **argv)
{
    Sofabed testDemo;
    testDemo.sleep();
    testDemo.watchTv();
    testDemo.setweight(10);
//    testDemo.Sofa::setweight(10);
    cout << testDemo.getWeight() << endl;
    cout << "Hello World!" << endl;
    return 0;
}

Sofa、Bed内存情况:

 SofaBed
共用                              weight
各自成员变量ab

 

testDemo:

weigtFurniture
aSofa
bBed
c自己

应该尽量避免使用多重继承,它使得程序变得更加复杂、更容易出错。

6、多态、类型转换

1)多态:使用相同的调用的调用方法,对于不同的对象,它会调用不同的类里面实现的函数

#include <iostream>

using namespace std;
/* 多态 */
class Furniture{
private:
    int a;
public:
   Furniture(){
       cout << "Furniture()" << endl;
   }
   ~Furniture(){
       cout << "~Furniture()" << endl;
   }
   virtual void eatting(void){
       cout << "eatting something" << endl;
   }
};

class Sofa:public Furniture{
private:
    int b;
public:
    Sofa(){
        cout << "Sofa()" << endl;
    }
    ~Sofa(){
        cout << "~Sofa()" << endl;
    }
    void eatting(void){
        cout << "eatting something on the sofa" << endl;
    }
};

class Bed:public Furniture{
private:
    int c;
public:
    Bed(){
        cout << "Bed()" << endl;
    }
    ~Bed(){
        cout << "~Bed()" << endl;
    }
    void eatting(void){
        cout << "eatting something on the bed" << endl;
    }
};

void test_eatting(Furniture& F){
    F.eatting();
}
int main()
{
    Furniture F;
    Sofa S;
    Bed B;
    test_eatting(F);
    test_eatting(S);
    test_eatting(B);

    cout << "Hello World!" << endl;
    return 0;
}

机制:

静态联编:非虚函数,在编译时确定好调用哪一个函数

动态联编:在运行时候才确定调用哪个函数。①对象里有一个指针,该指针指向一个表(虚函数表);② 调用函数时,通过该指针找到表,调用虚函数。

  1. 多态只有使用指针或者引用来使用对象时,才有多态test_func(Human *h)、test_func(Human &h)传值时,无多态test_func(Human h),只能使用静态联编
  2. 只有类的成员函数才能声明为虚函数
  3. 静态成员函数不能是虚函数
  4. 内联函数不能是虚函数
  5. 构造函数不能是虚函数
  6. 析构函数一般都声明为虚函数
  7. 重载:函数参数不同,不可设为虚函数(不存在相同的调用方法);覆盖:函数参数、返回值相同,可设置为虚函数
  8. 返回值例外:函数参数相同,但是返回值是当前对象的指针或引用时,也可以设为虚函数

2)类型转换

隐式类型转换:由编译器来实现,编译器不一定能够猜测出我们的真实意图,所以使用隐式类型转换的时候经常有各种警告,为此,引入显示类型转换。

double d = 100.1;
int i = d;            //double转换成int,i = 100
char *str = "hello";
int *p = str;        //char*转为int*

显示类型转换:

(1)对于C语言:格式:(type-id)

double i = 100.1;
int i = (int)d;
char *str = "hello";
int *p = (int*)str;

(2)对于C++

① 动态转换(dynamic_cast)

格式:dynamic_cast<type-id>(expression)

该运算符把expression转换成type-id类型的对象。Type-id必须是类的指针、类的引用或者void *;如果type-id是类指针类型,那么expression也必须是一个指针如果type-id是一个引用,那么expression也必须是一个引用

  1. 用于多态场合,即:必须有虚函数
  2. 主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换
  3. 在类层次见进行上行转换时,dynamic_cast和static_cast的效果是一样的;
  4. 在进行类型转换时,dynamic具有类型检查的功能,比static_cast更安全
#include <iostream>

using namespace std;
/* 多态 */
class Furniture{
private:
    int a;
public:
   Furniture(){
       cout << "Furniture()" << endl;
   }
   virtual ~Furniture(){
       cout << "~Furniture()" << endl;
   }
   virtual void eatting(void){
       cout << "eatting something" << endl;
   }
};

class Sofa:public Furniture{
private:
    int b;
public:
    Sofa(){
        cout << "Sofa()" << endl;
    }
    virtual ~Sofa(){
        cout << "~Sofa()" << endl;
    }
    void eatting(void){
        cout << "eatting something on the sofa" << endl;
    }
};

class Bed:public Furniture{
private:
    int c;
public:
    Bed(){
        cout << "Bed()" << endl;
    }
    virtual ~Bed(){
        cout << "~Bed()" << endl;
    }
    void eatting(void){
        cout << "eatting something on the bed" << endl;
    }
};

void test_eatting(Furniture *F){
    Sofa *S = new Sofa;
    Bed *B = new Bed;
    F->eatting();
    /* 想分辨是Sofa还是Bed */
    /* 使用动态类型转换 */
    if(S == dynamic_cast<Sofa *>(F)){
        cout << "This is Sofa" << endl;
    }
    if(B == dynamic_cast<Bed *>(F)){
        cout << "This is Bed" << endl;
    }
    delete S;
    delete B;
}
int main()
{
    Furniture F;
    Sofa S;
    Bed B;
    test_eatting(&F);
    test_eatting(&S);
    test_eatting(&B);

    cout << "Hello World!" << endl;
    return 0;
}

② 静态转换(static_cast):该运算符把expression转换为type-id类型,但运行时没有类型检查来保证转换的安全性。

格式:static_cast<type-id>(expression)

  1. 用于类层次结构中基类和子类之间指针或引用的转换
  2. 进行上行转换(把子类的指针或引用转换成基类表示)是安全的
  3. 进行下行转换(把基类指针或引用转换成基类表示)是安全的
  4. 用于基本数据类型之间的转换,如把int转换成char,把int转换成enum:这种转换的安全性也要开发人员来保证
  5. 把woid指针类型转换成目标类型的指针(不安全!!!)
  6. 把任何类型的表达式转换成void类型

注意:static_cat不能转换掉expression的const、volatile、或者__unaliged属性

③ 重新解析转换(reinterpret_cast):先当于C风格的用小括号( )实现的强制类型转换(1)

格式:reinterpret_cast<type_id>(expression)、圆括号不能少

  • type-id必须是一个指针、引用、算术类型、函数指针或者成员指针
  • 他可以吧一个指针转换成一个整数,也可以把一个整数转换成一个指针
  • 跟C风格的强制转换类似,没有安全性检查
#include <iostream>

using namespace std;
//类型转换
int main()
{
    double d = 100.1;
    int i = reinterpret_cast<int>(d);            //错误
    char *str = "hello";
    int *p = reinterpret_cast<int*>(str);        //正确:char*转为int*
    cout << "Hello World!" << endl;
    return 0;
}

④ const_cast转换该运算符用来去除原来类型的const或volatile属性。

格式:const_cast<type_id> (expression)

#include <iostream>

using namespace std;
//类型转换
int main()
{
    double d = 100.1;
    int i = (int)(d);            //double转换成int,i = 100
    const char *str = "hello";
    char *str2 = const_cast<char *>(str);

    int *p = reinterpret_cast<int*>(str2);        //char*转为int*
    cout << i << "\n" << *p << endl;
    cout << "Hello World!" << endl;
    return 0;
}

No Warnning,No Error。


 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值