C++重要概念




happysneaker.com






1、三种参数传递规则:值传递、&引用传递、*地址传递

① 按值传递

    按值传递参数时,生成实参的值的副本,然后传递给函数形参,形参的改变不会影响到实参。

② 引用传递

    引用传递是将形参作为实参的别名,所以通过形参可以直接访问实参,也就是说形参可以影响实参的值。引用传递中需要在定义形式参数时,在形参前面加引用符“&”。

③ 地址传递

    地址传递是将实参的地址传递给形参,所以对形参所指向的地址中的内容进行修改,也会使得实参的值发生改变。按地址传递中需要将形参的类型定义为指针类型。   


注意:

happysneaker.com




2、内联函数

    当程序执行到调用普通函数时,程序就转去执行该函数,执行完该函数之后再返回程序中。而内联函数在编译阶段就会把每个出现调用该内联函数的地方都用该函数体中的代码替代。因此内联函数的使用会减少函数调用的开销,但是会增加程序的长度。

    ①在函数定义前加关键字inline

    ②内联函数中不能有:循环、switch、goto

    ③内联函数中不能声明数组

    ④递归函数不能定义为内联函数




3、函数重载

    同一个函数名可以对应多个函数实现,完成含义相同的工作,但是参数个数、参数类型不同,函数调用时根据参数的类型、个数决定具体调用哪个参数。





4、数组作为参数

    ①数组作为函数的参数时,传递的是数组中第0个元素的地址,即指针。因此修改形参数组将会影响到实参数组。

    ②函数不知道传递给他的数组的实际长度,当编译器并不检查数组的长度,因此在定义形参的时候可以只写数组名[],方括号中是否写长度无所谓。

    ③但有时候在被调用函数中需要知道数组长度,那么可以采用下面2种方法来传递数组长度信息:

        ···提供一个含有数组长度的额外参数,即定义一个用于存放数组长度的形参;

        ···将形参声明为数组的引用,当形参时一个数组类型的引用时,数组长度成为形参的一部分,编译器会检查数组实参的长度与在函数形参类型中指定的长度是否匹配。





5、静态变量——静态局部变量、静态全局变量

    ①静态局部变量:

     static 类型 变量名:

     ——所在函数调用结束也不释放内存,在程序执行期间始终存在

     ——但是其他函数是不能引用别的函数的静态变量的

     ——定义但不初始化,则自动赋以“0”(整型、实型)或 '\0'(字符型)

     ——且每次调用他们所在的函数时,不再重新赋初值,保留上次调用结束时的值

     ——静态数据成员数据整个类,调用方法:类名::静态成员变量

    ②静态全局变量:

      static 类型 变量名:

     ——该变量只在这个源文件可用,全局静态变量就是静态全局变量

     ——静态全局变量对组成该程序的其他源文件是无效的

     ——静态成员函数的调用:一是对象名.函数名,二是类名::函数名





6、生命期:静态生命期、局部生命期、动态生命期

happysneaker.com






7、构造函数、析构函数

    构造函数:

        ①构造函数是特殊的public成员函数

        ②构造函数名和类名相同

        ③构造函数无函数返回类型说明,但是有返回值,即其所创建的对象

        ④当新对象生成,该对象所属的类的构造函数自动被调用,对象生存期内只调用这一次

        ⑤构造函数可以重载,可以有多个构造函数存在,由不同的参数表区分

        ⑥构造函数可以在类中定义,也可以在类外定义

        ⑦如果类说明中没有自定义构造函数,则编译器给出一个缺省的

        ⑧如果对象的数据成员全为公有public,那么可以在对象名后加={},直接填入全体数据成员的初始值。

    析构函数:

        ①当一个对象定义时,C++自动调用构造函数初始化对象

        ②相应的,当一个对象的生命周期结束,C++也会自动调用一个函数注销该对象,并进行善后工作。

        ③析构函数同样也没有返回类型

        ④构造函数可以有形参,但析构函数没有

        ⑤一个类只有一个析构函数,可以缺省

        ⑥对象注销时,自动调用析构函数。





8、引用

    ①C++函数参数的传递方式是传值,无法改变实参的值,怎么办呢?

    ②这时候出现了指针,但是C++又推出一种新的数据类型——引用(reference)

    ③引用reference又称别名alias

    ④引用是给一个已经定义了的变量重新起一个别名,C++并不会为引用类型的变量分配内存空间

    ⑤定义格式:  类型 &引用变量名 = 已定义过的变量名

    

指针和引用的区别:

    ①引用不可以为空,指针可以为空。所以定义一个引用的时候,必须初始化

    ②引用不可以改变指向,而指针可以,指针可以指向其他对象

    ③对引用的操作直接作用于对象,如下结果为:11、11、21、21、20,尽管 ref 又赋值为j,但是它指向的仍然是i,并没有改变j的值

    happysneaker.com

    ④引用的大小是它所指向的变量的大小,指针的大小是其本身的大小==固定的4个字节

    ⑤引用比指针更加安全,由于不存在空引用,而且引用一旦被初始化指向一个对象就不能再次指向其他对象,因此引用很安全。

        ——对于指针来说,可以随时指向别的对象,并且可以不被初始化,或者为NULL,这非常不安全。

        ——const指针虽然不能改变指向,但仍然存在空指针,并且有可能产生野指针

综上而言:

    所有差别都可以归纳为“指针指向一块内存,他的内容是该内存的地址;引用则是某块内存的别名,引用不会改变指向”。





9、拷贝构造函数

    ①拷贝构造函数,又称复制构造函数

    ②是一种特殊的构造函数

    ③它由编译器调用来完成一些基于同一类的其他对象的构建及初始化

    ④其形参必须是引用,但并不限制为const,一般普遍的会加上const限制

    happysneaker.com

    ⑤如果在类中没有定义拷贝构造函数,编译器会自行定义一个。如果类带有指针变量,并有动态内存分配,则它必须有一个拷贝构造函数。

    happysneaker.com

    详:https://www.runoob.com/cplusplus/cpp-copy-constructor.html





10、运算符的重载

    ①实际上是一种特殊的函数重载

    ②必须定义一个函数,告诉编译器当遇到该重载的运算符时调用此函数

    ③这个函数就叫做运算符重载函数,通常为类的成员函数

    ④格式: 返回值类型 类名::operator 重载的运算符(参数表){}

        ——比如:Complex Complex::operator+(Complex c){

                                    Complex Temp(Real+c.Real,Image+c.Image);

                            }

    ⑤operator是关键字,与重载的运算符一起构成函数名

    ⑥总结其功能就是:改变一个运算符原来的作用




11、友元

    ①私有成员对于类外部的所有程序部分而言都是隐藏的,访问它们需要调用一个公共成员函数,但有时也可能会需要创建该规则的一项例外。

    ②友元函数是一个不属于类成员的函数,但它可以访问该类的私有成员。换句话说,友元函数被视为好像是该类的一个成员。友元函数可以是常规的独立函数,也可以是其他类的成员。实际上,整个类都可以声明为另一个类的友元。

    ③为了使一个函数或类成为另一个类的友元,必须由授予它访问权限的类来声明。

    ④通过将关键字 friend 放置在函数的原型之前,即可将函数声明为友元。

    ⑤如下,将Aux类的addBudget(double)函数声明为Budget类的友元函数:

    happysneaker.com

    ⑥将整个类声明为一个类的友元并不是一个好主意,因为这将导致别的类的每个成员函数(包括稍后可能添加的函数)都可以访问本类的私有成员。所以,最好的做法是只声明那些必须有权访问类的私有成员的函数作为友元。




12、模板——TODO

    ①模板是建立通用的与数据类型无关的算法的重要手段

    ②在学习与数据结构相关的表、排序、查找等的算法时,要逐步熟悉函数模板、类模板的编程方法

    ③为了代码重用,代码就必须是通用的,通用的代码就必须不受数据类型的限制。

    ④那么就可以把数据类型改为一个设计参数。这种类型的程序设计成为参数化程序设计。

    ⑤分为两类:函数模板function template、类模板class template

    ⑥





13、this指针

    ①简答题

    happysneaker.com

    ②静态成员函数没有this指针。因为普通成员函数虽然在物理上只有一份拷贝,但在逻辑上都认为一个对象有一份拷贝,所以有this指针;而静态成员函数在逻辑上也只有一份拷贝,不属于具体的对象当然没有this指针。

    ③this是C++中的一个关键字,也是一个const指针,它指向当前对象,通过它可以访问当前对象的所有成员

    ④所谓当前对象,是指正在使用的对象。例如:stu.show();   stu就是当前对象,this就指向stu

    ⑤如下举例

#include <iostream>
using namespace std;

class Student{
public:
    void setname(char *name);
    void setage(int age);
    void setscore(float score);
    void show();
private:
    char *name;
    int age;
    float score;
};

void Student::setname(char *name){
    this->name = name;
}
void Student::setage(int age){
    this->age = age;
}
void Student::setscore(float score){
    this->score = score;
}
void Student::show(){
    cout<<this->name<<"的年龄是"<<this->age<<",成绩是"<<this->score<<endl;
}

int main(){
    Student *pstu = new Student;
    pstu -> setname("李华");
    pstu -> setage(16);
    pstu -> setscore(96.5);
    pstu -> show();

    return 0;
}

运行结果:

李华的年龄是16,成绩是96.5

    ⑥this只能用在类的内部,通过 this 可以访问类的所有成员,包括private、protected、public属性的

    ⑦分析,注意,this 是一个指针,要用->来访问成员变量或成员函数。

    happysneaker.com

    ⑧

    happysneaker.com

    ⑨this到底是什么呢:

    ——this实际上是成员函数的一个形参,在调用成员函数时将对象的地址作为实参传递给this。

    ——不过this这个形参是隐式的,它并不出现在代码中,而是在编译阶段由编译器默默地将它添加到参数列表中。

    ——this作为隐式形参,本质上是成员函数的局部变量,所以只能用在成员函数的内部,并且只有在通过对象调用成员函数时,才给this赋值。

    ⑩在《C++函数编译原理和成员函数的实现》一节中讲到,成员函数最终被编译成与对象无关的普通函数,除了成员变量,会丢失所有信息,所以编译时要在成员函数中添加一个额外的参数,把当前对象的首地址传入,以此来关联成员函数和成员变量。这个额外的参数,实际上就是 this,它是成员函数和成员变量关联的桥梁。






14、堆内存的分配、释放——https://blog.csdn.net/chy19911123/article/details/48135239

    ①通常定义变量或者对象,编译器在编译时都可以根据该变量或对象的类型知道所需内存空间的大小,从而系统在适当的时候为他们分配确定的存储空间,这种内存分配被称为静态存储分配。

    ②有些操作对象只有在程序运行时才能确定,这样编译器在编译时就无法为他们预定存储空间,只能在程序运行时,系统根据运行时的要求进行内存分配,这种内存分配被称为动态内存分配。

    ③所有的动态存储分配都在堆区中进行。

    ④内存的分配与释放:

        ——当程序运行到需要一个动态分配的变量或对象,必须向系统申请取得堆中的一块所需大小的内存,用来存储该变量或对象。

        ——当不再使用该变量或对象时,即生命周期结束时,要显式释放它所占用的存储空间,这样系统就能对该堆空间进行再分配,做到重复使用有限资源。

        ——在C++中,申请、释放堆中的空间,分别使用new、delete这两个运算符

        ——使用格式: 指针变量名 = new 类型名(初始化式)

                                 delete 指针名

        ——new 返回的是一个指向所分配类型变量或对象的指针。对所创建的变量或对象,都是通过该指针来间接操作的

        ——动态创建的对象本身没有名字

#include<iostream>
using namespace std;
class ST{
private:
    int a;
public:
    ST(int _a=0):a(_a){
        this->a = _a;
        cout<<"Object was built. "<<endl;
    }
    ~ST()
    {
        cout<<"Object was free. "<<endl;
    }
};
void malloc_free(){
    ST *tmp = (ST*)malloc(sizeof(ST));
    free(tmp);
}
void new_delete(){
    ST *tmp = new ST[1];//但是new为对象数组分配空间不可初始化
    delete []tmp;
}
void main(){
    ST *pt = new ST(1);//new为对象分配空间就可初始化
    delete [] pt;//delete p 是删除pt所指空间,而并非删除pt,为了预防野指针可将pt=NUll
    malloc_free();
    new_delete();
}

happysneaker.com



如下:

happysneaker.com

happysneaker.com

    关于malloc、free和new、delete的区别:https://blog.csdn.net/chy19911123/article/details/48135239




15、深拷贝、浅拷贝——对象拷贝就是将一个对象的属性拷贝到另一个有着相同类型的对象中去。

    ①对于基本类型的数据以及简单的对象,它们之间的拷贝非常简单,就是按位复制内存,如下图。b 和 obj2 都是以拷贝的方式初始化的,具体来说,就是将 a 和 obj1 所在内存中的数据按照二进制位(Bit)复制到 b 和 obj2 所在的内存,这种默认的拷贝行为就是浅拷贝,这和调用 memcpy() 函数的效果非常类似

    happysneaker.com

    ②对于简单的类,默认的拷贝构造函数一般就够用了,我们也没有必要再显式地定义一个功能类似的拷贝构造函数。但是当类持有其它资源时,例如动态分配的内存、指向其他数据的指针等,默认的拷贝构造函数就不能拷贝这些资源了,我们必须显式地定义拷贝构造函数,以完整地拷贝对象的所有数据。

    ③下面我们通过一个具体的例子来说明显式定义拷贝构造函数的必要性。我们知道,有些较老的编译器不支持变长数组,例如 VC6.0、VS2010 等,这有时候会给编程带来不便,下面我们通过自定义的 Array 类来实现变长数组。

#include <iostream>
#include <cstdlib>
using namespace std;

//变长数组类
class Array{
public:
    Array(int len);
    Array(const Array &arr);  //拷贝构造函数
    ~Array();
public:
    int operator[](int i) const { return m_p[i]; }  //获取元素(读取)
    int &operator[](int i){ return m_p[i]; }  //获取元素(写入)
    int length() const { return m_len; }
private:
    int m_len;
    int *m_p;
};

Array::Array(int len): m_len(len){
    m_p = (int*)calloc( len, sizeof(int) );
}

Array::Array(const Array &arr){  //拷贝构造函数
    this->m_len = arr.m_len;
    this->m_p = (int*)calloc( this->m_len, sizeof(int) );
    memcpy( this->m_p, arr.m_p, m_len * sizeof(int) );
}

Array::~Array(){ free(m_p); }

//打印数组元素
void printArray(const Array &arr){
    int len = arr.length();
    for(int i=0; i<len; i++){
        if(i == len-1){
            cout<<arr[i]<<endl;
        }else{
            cout<<arr[i]<<", ";
        }
    }
}

int main(){
    Array arr1(10);
    for(int i=0; i<10; i++){
        arr1[i] = i;
    }
   
    Array arr2 = arr1;
    arr2[5] = 100;
    arr2[3] = 29;
   
    printArray(arr1);
    printArray(arr2);
   
    return 0;
}

运行结果:
0, 1, 2, 3, 4, 5, 6, 7, 8, 9
0, 1, 2, 29, 4, 100, 6, 7, 8, 9

        本例中我们显式地定义了拷贝构造函数,它除了会将原有对象的所有成员变量拷贝给新对象,还会为新对象再分配一块内存,并将原有对象所持有的内存也拷贝过来。这样做的结果是,原有对象和新对象所持有的动态内存是相互独立的,更改一个对象的数据不会影响另外一个对象,本例中我们更改了 arr2 的数据,就没有影响 arr1。

    ④这种将对象所持有的其它资源一并拷贝的行为叫做深拷贝,我们必须显式地定义拷贝构造函数才能达到深拷贝的目的。

    ⑤深拷贝的例子比比皆是,除了上面的变长数组类,我们在《C++ throw关键字》一节中使用的动态数组类也需要深拷贝;此外,标准模板库(STL)中的 string、vector、stack、set、map 等也都必须使用深拷贝。

    ⑥如果希望亲眼目睹不使用深拷贝的后果,可以将上例中的拷贝构造函数删除,那么运行结果将变为:

        0, 1, 2, 29, 4, 100, 6, 7, 8, 9
        0, 1, 2, 29, 4, 100, 6, 7, 8, 9

        可以发现,更改 arr2 的数据也影响到了 arr1。这是因为,在创建 arr2 对象时,默认拷贝构造函数将 arr1.m_p 直接赋值给了 arr2.m_p,导致 arr2.m_p 和 arr1.m_p 指向了同一块内存,所以会相互影响。

    ⑦如果一个类拥有指针类型的成员变量,那么绝大部分情况下就需要深拷贝,因为只有这样,才能将指针指向的内容再复制出一份来,让原有对象和新生对象相互独立,彼此之间不受影响。如果类的成员变量没有指针,一般浅拷贝足以。

    ⑧二者区别:

        ——1.浅拷贝: 将原对象或原数组的引用直接赋给新对象,新数组,新对象/数组只是原对象的一个引用

        ——2.深拷贝: 创建一个新的对象和数组,将原对象的各项属性的“值”(数组的所有元素)拷贝过来,是“值”而不是“引用”

    ⑨为什么要使用深拷贝?——我们希望在改变新的数组(对象)的时候,不改变原数组(对象)

    ⑩怎么检测深拷贝成功?——改变任意一个新对象/数组中的属性/元素,     都不改变原对象/数组






16、多继承——http://c.biancheng.net/view/2277.html


        ①派生类都只有一个基类,称为单继承(Single Inheritance)

        ②C++也支持多继承(Multiple Inheritance),即一个派生类可以有两个或多个基类。

        ③多继承容易让代码逻辑复杂、思路混乱,一直备受争议,中小型项目中较少使用,后来的 Java、C#、PHP 等干脆取消了多继承。

        ④多继承的语法也很简单,将多个基类用逗号隔开即可。例如已声明了类A、类B和类C,那么可以这样来声明派生类D:

class D: public A, private B, protected C{
    //类D新增加的成员
}

        D 是多继承形式的派生类,它以公有的方式继承 A 类,以私有的方式继承 B 类,以保护的方式继承 C 类。D 根据不同的继承方式获取 A、B、C 中的成员,确定它们在派生类中的访问权限。






17、虚继承、虚基类——http://c.biancheng.net/view/2280.html

    ①多继承是指从多个直接基类中产生派生类的能力,多继承的派生类继承了所有父类的成员。

    ②尽管概念上非常简单,但是多个基类的相互交织可能会带来错综复杂的设计问题,命名冲突就是不可回避的一个。

    ③多继承时很容易产生命名冲突。

    ④为了解决多继承时的命名冲突和冗余数据问题,C++ 提出了虚继承,使得在派生类中只保留一份间接基类的成员。

    ⑤在继承方式前面加上 virtual 关键字就是虚继承

//间接基类A
class A{
protected:
    int m_a;
};

//直接基类B
class B: virtual public A{  //虚继承
protected:
    int m_b;
};

//直接基类C
class C: virtual public A{  //虚继承
protected:
    int m_c;
};

//派生类D
class D: public B, public C{
public:
    void seta(int a){ m_a = a; }  //正确
    void setb(int b){ m_b = b; }  //正确
    void setc(int c){ m_c = c; }  //正确
    void setd(int d){ m_d = d; }  //正确
private:
    int m_d;
};

int main(){
    D d;
    return 0;
}

        这段代码使用虚继承重新实现了上图所示的菱形继承,这样在派生类 D 中就只保留了一份成员变量 m_a,直接访问就不会再有歧义了。

    ⑥虚继承的目的是让某个类做出声明,承诺愿意共享它的基类。其中,这个被共享的基类就称为虚基类(Virtual Base Class),本例中的 A 就是一个虚基类。在这种机制下,不论虚基类在继承体系中出现了多少次,在派生类中都只包含一份虚基类的成员。

    

使用虚继承解决菱形继承中的命名冲突问题

        观察这个新的继承体系,我们会发现虚继承的一个不太直观的特征:必须在虚派生的真实需求出现前就已经完成虚派生的操作。在上图中,当定义 D 类时才出现了对虚派生的需求,但是如果 B 类和 C 类不是从 A 类虚派生得到的,那么 D 类还是会保留 A 类的两份成员。

        换个角度讲,虚派生只影响从指定了虚基类的派生类中进一步派生出来的类,它不会影响派生类本身。





18、虚函数——https://blog.csdn.net/jhz033/article/details/73527288

    ①在同一类中是不能定义两个名字相同、参数个数和类型都相同的函数的,否则就是“重复定义”。但是在类的继承层次结构中,在不同的层次中可以出现名字相同、参数个数和类型都相同而功能不同的函数。

    ②在Circle类中定义了 area函数,在Circle类的派生类Cylinder中也定义了一个area函数。这两个函数不仅名字相同,而且参数个数相同(均为0),但功能不同,函数体是不同的。前者的作用是求圆面积,后者的作用是求圆柱体的表面积。

    ③这是合法的,因为它们不在同一个类中。 编译系统按照同名覆盖的原则决定调用的对象。在例12.1程序中用cy1.area( ) 调用的是派生类Cylinder中的成员函数area。如果想调用cy1 中的直接基类Circle的area函数,应当表示为 cy1.Circle::area()。用这种方法来区分两个同名的函数。但是这样做 很不方便。

    ④人们提出这样的设想,能否用同一个调用形式,既能调用派生类又能调用基类的同名函数。在程序中不是通过不同的对象名去调用不同派生层次中的同名函数,而是通过指针调用它们。例如,用同一个语句“pt->display( );”可以调用不同派生层次中的display函数,只需在调用前给指针变量 pt 赋以不同的值(使之指向不同的类对象)即可。

    ⑤C++中的虚函数就是用来解决这个问题的。虚函数的作用是允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数。利用虚函数就很好地解决了这个问题。可以看到:当把基类的某个成员函数声明为虚函数后,允许在其派生类中对该函数重新定义,赋予它新的功能,并且可以通过指向基类的指针指向同一类族中不同类的对象,从而调用其中的同名函数。由虚函数实现的动态多态性就是:同一类族中不同类的对象,对同一函数调用作出不同的响应。




19、纯虚函数——https://blog.csdn.net/yuanchunsi/article/details/78833899

    ①纯虚函数定义方法

    happysneaker.com

    ②纯虚函数也一定是某个类的成员函数,那么,包含纯虚函数的类也叫作什么呢?我们把包含纯虚函数的类称之为抽象类

    ③对于抽象类来说,C++是不允许它去实例化对象的。也就是说,抽象类无法实例化对象

    ④在C++中,可以将虚函数声明为纯虚函数:virtual 返回值类型 函数名 (函数参数) = 0;

    ⑤纯虚函数没有函数体,只有函数声明,在虚函数声明的结尾加上=0,表明此函数为纯虚函数。

    ⑥最后的=0并不表示函数返回值为0,它只起形式上的作用,告诉编译系统“这是纯虚函数”。

    ⑦包含纯虚函数的类称为抽象类(Abstract Class)。之所以说它抽象,是因为它无法实例化,也就是无法创建对象。原因很明显,纯虚函数没有函数体,不是完整的函数,无法调用,也无法为其分配内存空间。

    ⑧抽象类通常是作为基类,让派生类去实现纯虚函数。派生类必须实现纯虚函数才能被实例化。

#include <iostream>
using namespace std;

//线
class Line{
public:
    Line(float len);
    virtual float area() = 0;
    virtual float volume() = 0;
protected:
    float m_len;
};
Line::Line(float len): m_len(len){ }

//矩形
class Rec: public Line{
public:
    Rec(float len, float width);
    float area();
protected:
    float m_width;
};
Rec::Rec(float len, float width): Line(len), m_width(width){ }
float Rec::area(){ return m_len * m_width; }

//长方体
class Cuboid: public Rec{
public:
    Cuboid(float len, float width, float height);
    float area();
    float volume();
protected:
    float m_height;
};
Cuboid::Cuboid(float len, float width, float height): Rec(len, width), m_height(height){ }
float Cuboid::area(){ return 2 * ( m_len*m_width + m_len*m_height + m_width*m_height); }
float Cuboid::volume(){ return m_len * m_width * m_height; }

//正方体
class Cube: public Cuboid{
public:
    Cube(float len);
    float area();
    float volume();
};
Cube::Cube(float len): Cuboid(len, len, len){ }
float Cube::area(){ return 6 * m_len * m_len; }
float Cube::volume(){ return m_len * m_len * m_len; }

int main(){
    Line *p = new Cuboid(10, 20, 30);
    cout<<"The area of Cuboid is "<<p->area()<<endl;
    cout<<"The volume of Cuboid is "<<p->volume()<<endl;
  
    p = new Cube(15);
    cout<<"The area of Cube is "<<p->area()<<endl;
    cout<<"The volume of Cube is "<<p->volume()<<endl;

    return 0;
}

运行结果:
The area of Cuboid is 2200
The volume of Cuboid is 6000
The area of Cube is 1350
The volume of Cube is 3375

本例中四个类的继承关系为:Line --> Rec --> Cuboid --> Cube







20、抽象类——http://c.biancheng.net/view/2299.html







21、C++异常——http://c.biancheng.net/view/422.html



























Web安全技术分享
请先登录后发表评论
  • 最新评论
  • 总共0条评论