简单实例复习C++面向对象

/*
简单实例复习C++面向对象
资料来源:http://www.runoob.com/cplusplus/cpp-tutorial.html
本篇通过对资料学习整理得来
*/

/*知识点目录
1,类和对象
1.1 类
1.2 对象
1.3 访问数据成员
1.4 类成员函数
1.5 类访问修饰符
1.6 构造函数、析构函数
1.7 拷贝构造函数
1.8 友元函数
1.9 内联函数
1.10 this指针
1.11 对象指针
1.12 静态成员
2,继承
2.1 基类 派生类
3,重载运算符和重载函数
3.1函数重载
3.2运算符重载
4,多态
5,数据抽象
6,数据封装
7,接口
*/

#include "stdafx.h"

#include <iostream>
using namespace std;


class Object{};
class Object1{};

//1.1类定义
/*2.1.1基类 派生类
1,一个类可以派生自多个类,可以从多个基类继承数据和函数。
2,使用类派生列表来指定基类。
3,访问修饰符是 public、protected 或 private 其中的一个,默认为 private
*/
/*2.1.2 访问控制和继承
派生类继承了所有的基类方法,下列情况除外:
    a 基类的构造函数、析构函数和拷贝构造函数。
    b 基类的重载运算符。
    c 基类的友元函数。
*/
/*2.1.3继承类型
1,我们几乎不使用 protected 或 private 继承,通常使用 public 继承。
2,公有继承(public):当一个类派生自公有基类时,基类的公有成员也是派生类的公有成员,
    基类的保护成员也是派生类的保护成员,
    基类的私有成员不能直接被派生类访问,但是可以通过调用基类的公有和保护成员来访问。
3,保护继承(protected): 当一个类派生自保护基类时,基类的公有和保护成员将成为派生类的保护成员。
4,私有继承(private):当一个类派生自私有基类时,基类的公有和保护成员将成为派生类的私有成员。
*/
/*2.1.4多继承 
各个基类之间用逗号分隔
*/
class Line: public Object, protected Object1
{
//1.5.1私有成员变量或函数在类的外部是不可访问的,甚至是不可查看的。
//只有类和友元函数可以访问私有成员。默认情况下,类的所有成员都是私有的
private:
    double width;//粗细度

//1.5.2保护成员变量或函数与私有成员十分相似,
//但保护成员在派生类(即子类)中是可访问的。
protected:

//1.5.3公有成员在程序中类的外部是可访问的
public:
    double length;

    //1.4.1类成员函数,类内部定义
    double getLength(){ 
        return length;
    }

    //1.4.2类成员函数,类外部定义
    void setLength(double len);

    /*3.1函数重载 下面多个构造函数就是重载得例子
    1,在同一作用域中的某个函数和运算符指定多个定义,分别称为函数重载和运算符重载。
    2,具有相同名称,但是参数列表和定义(实现)不同。
    3,重载决策:选择最合适的重载函数或重载运算符的过程。当调用一个重载函数或重载运算符时,
    编译器通过把所使用的参数类型与定义中的参数类型进行比较,决定选用最合适的定义。
    */  

    //1.6.1构造函数
    Line(){ 
        //1.12.2 使用静态成员函数来判断类的对象是否已被创建
        count++;
    }

    //1.6.2带参数的构造函数
    Line(double len){ length = len; }

    //1.6.3使用初始化列表来初始化字段,效果同1.6.2,所以会编译冲突
    //Line(double len):length(len){};

    //1.6.4析构函数
    ~Line(){}

    //1.7.1拷贝构造函数 如果在类中没有定义拷贝构造函数,编译器会自行定义一个。
    //如果类带有指针变量,并有动态内存分配,则它必须有一个拷贝构造函数(编者注:否则会重复释放内存)
    Line(const Line& obj){}

    //1.8.1友元函数
    /*
    类的友元函数定义在类外部。
    有权访问类的所有私有(private)成员和保护(protected)成员。
    友元函数并不是成员函数。
    友元可以是一个函数,称为友元函数;友元也可以是一个类,称为友元类,这时整个类及其所有成员都是友元。
    */
    friend void printWidth(Line line){
        printf("width=%f", line.width);
    }

    /*1.12.1静态成员
    1,无论创建多少个类的对象,静态成员都只有一个副本。
    2,静态成员在类的所有对象中是共享的。
    3,如果不存在其他的初始化语句,在创建第一个对象时(编者注:不是指所在类的对象),所有的静态数据都会被初始化为零。
    4,不能把静态成员放置在类的定义中,可以在类的外部通过使用范围解析运算符 :: 来重新声明静态变量从而对它进行初始化。
    */
    static int count;

    /*1.12.2静态函数成员
    1,静态成员函数与类的任何特定对象无关。
    2,静态成员函数即使在类对象不存在的情况下也能被调用,使用类名加范围解析运算符 :: 就可以访问。
    3,静态成员函数只能访问静态数据成员,不能访问其他静态成员函数和类外部的其他函数。
    4,静态成员函数有一个类范围,不能访问类的 this 指针。可以使用静态成员函数来判断类的对象是否已被创建。
    */
    static int getCount()
    {
        return count;
    }

};

int Line::count = 0;//1.12.1重新声明静态变量并对它进行初始化

//1.4.2 在类的外部使用范围解析运算符 :: 定义
void Line::setLength(double len){
    /*1.10.1
    this 指针是所有成员函数的隐含参数。
    友元函数没有 this 指针,因为友元不是类的成员。
    */
    this->length = len;
}

/*1.9.1内联函数
1,内联函数是通常与类一起使用。
2,如果一个函数是内联的,那么在编译时,编译器会把该函数的代码副本放置在每个调用该函数的地方。
3,对内联函数进行任何修改,都需要重新编译函数的所有客户端,因为编译器需要重新更换一次所有的代码。
4,如果想把一个函数定义为内联函数,则需要在函数名前面放置关键字 inline,在调用函数之前需要对函数进行定义。
5,如果已定义的函数多于一行,编译器会忽略 inline 限定符。
6*,在类定义中定义的函数都是内联函数,即使没有使用 inline 说明符。
*/
inline int Max(int x, int y)
{
    return (x > y) ? x : y;
}

void lineTest(){

    //1.2对象
    Line line;

    //1.3访问数据成员
    line.length = 5.0;

    //1.11 对象指针
    /*
    1,对象指针与结构指针类似,访问对象的成员,需要使用成员访问运算符 ->,就像访问结构成员一样。
    2,与所有的指针一样,您必须在使用指针之前,对指针进行初始化。
    */
    Line* pline = &line;
    pline->length = 5.0;

    printf("count=%d", Line::count);
}
/*4,多态
1,多态按字面的意思就是多种形态。
2,当类之间存在层次结构,并且类之间是通过继承关联时,就会用到多态。
3,C++ 多态意味着调用成员函数时,会根据调用函数的对象的类型来执行不同的函数。
*/
class A{
public:
    void fun(){
        cout << "A::fun()\n";
    }

    /*4.1虚函数
    1,虚函数 是在基类中使用关键字 virtual 声明的函数。
    2,在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数。
    3,在程序中任意点可以根据所调用的对象类型来选择调用的函数,这种操作称为动态链接,或后期绑定。
    */
    virtual void vfun(){
        cout << "A::vfun()\n";
    }

    /*4.2纯虚函数
    1,想要在基类中定义虚函数,以便在派生类中重新定义该函数以更好地适用于对象,
    2,但是在基类中又不能对虚函数给出有意义的实现,这个时候就会用到纯虚函数。
    3,= 0 告诉编译器,函数没有主体,虚函数是纯虚函数。
    */
    virtual void pureVfun() = 0;

};

class B :public A{
public:
    void vfun(){
        cout << "B::vfun()\n";
    }
    void fun(){
        cout << "B::fun()\n";
    }
    void pureVfun(){

    }
};

class C :public A{
public:
    void vfun(){
        cout << "C::vfun()\n";
    }
    void fun(){
        cout << "C::fun()\n";
    }
    void pureVfun(){

    }

};

void testVirtual(){

    A* a;
    B b;
    C c;

    cout << endl << "fun()被编译器设置为基类中的版本,这就是静态多态,或静态链接" << endl;
    cout << "函数调用在程序执行前就准备好了。也被称为早绑定。" << endl;
    a = &b;
    a->fun();
    a = &c;
    a->fun();

    cout << endl << "vfun声明前放置关键字 virtual,此时,编译器看的是指针的内容,而不是它的类型。因此,会调用各自的 vfun() 函数。" << endl;
    cout << "在派生类中重新定义基类中定义的虚函数时,会告诉编译器不要静态链接到该函数。" << endl;
    cout << "在程序中可以根据所调用的对象类型来选择调用的函数,这种操作称为动态链接,或后期绑定。" << endl;
    a = &b;
    a->vfun();
    a = &c;
    a->vfun();

    cout << endl << "这就是多态的一般使用方式。有了多态,可以有多个不同的类" << endl;
    cout << "都带有同一个名称但具有不同实现的函数,函数的参数甚至可以是相同的。" << endl;

}

testVirtual() 运行结果
这里写图片描述

/*
5.1 数据抽象
C++ 类为数据抽象提供了可能。它们向外界提供了大量用于操作对象数据的公共方法,
也就是说,外界实际上并不清楚类的内部实现。

5.2 访问标签强制抽象
一个类可以包含零个或多个访问标签:
使用公共标签定义的成员都可以访问该程序的所有部分。一个类型的数据抽象视图是由它的公共成员来定义的。//前半句真是没看懂
使用私有标签定义的成员无法访问到使用类的代码。私有部分对使用类型的代码隐藏了实现细节。//前半句真是没看懂
访问标签出现的频率没有限制。每个访问标签指定了紧随其后的成员定义的访问级别。直到遇到下一个访问标签或者遇到类主体的关闭右括号为止。

5.3 数据抽象的好处
数据抽象有两个重要的优势:
1)类的内部受到保护,不会因无意的用户级错误导致对象状态受损。
2)类实现可能随着时间的推移而发生变化,以便应对不断变化的需求,或者应对那些要求不改变用户级代码的错误报告。
如果只在类的私有部分定义数据成员,编写该类的作者就可以随意更改数据。如果实现发生改变,则只需要检查类的代码,看看这个改变会导致哪些影响。
如果数据是公有的,则任何直接访问旧表示形式的数据成员的函数都可能受到影响。

5.4 设计策略
抽象把代码分离为接口和实现。所以在设计组件时,必须保持接口独立于实现,这样,如果改变底层实现,接口也将保持不变。
在这种情况下,不管任何程序使用接口,接口都不会受到影响,只需要将最新的实现重新编译即可。
*/

/*
6.1 数据封装
封装是面向对象编程中的把数据和操作数据的函数绑定在一起的一个概念,这样能避免受到外界的干扰和误用,从而确保了安全。
数据封装引申出了另一个重要的 OOP 概念,即数据隐藏。
数据封装是一种把数据和操作数据的函数捆绑在一起的机制,
数据抽象是一种仅向用户暴露接口而把具体的实现细节隐藏起来的机制。
C++ 通过创建类来支持封装和数据隐藏(public、protected、private)。
类包含私有成员(private)、保护成员(protected)和公有成员(public)成员。
默认情况下,在类中定义的所有项目都是私有的

把一个类定义为另一个类的友元类,会暴露实现细节,从而降低了封装性。理想的做法是尽可能地对外隐藏每个类的实现细节。

6.2 设计策略
通常情况下,我们都会设置类成员状态为私有(private),除非我们真的需要将其暴露,这样才能保证良好的封装性。
这通常应用于数据成员,但它同样适用于所有成员,包括虚函数。
*/

/*
7.1  接口(抽象类)
C++ 接口是使用抽象类来实现的。
抽象类与数据抽象互不混淆,数据抽象是一个把实现细节与相关的数据分离开的概念。
如果类中至少有一个函数被声明为纯虚函数,则这个类就是抽象类。
设计抽象类(通常称为 ABC)的目的,是为了给其他类提供一个可以继承的适当的基类。
抽象类不能被用于实例化对象,它只能作为接口使用。
如果试图实例化一个抽象类的对象,会导致编译错误。
可用于实例化对象的类被称为具体类

7.2设计策略
面向对象的系统可能会使用一个抽象基类为所有的外部应用程序提供一个适当的、通用的、标准化的接口。然后,派生类通过继承抽象基类,就把所有类似的操作都继承下来。
外部应用程序提供的功能(即公有函数)在抽象基类中是以纯虚函数的形式存在的。这些纯虚函数在相应的派生类中被实现。
这个架构也使得新的应用程序可以很容易地被添加到系统中,即使是在系统被定义之后依然可以如此。
*/

/*
各种运算符重载
*/
#include "stdafx.h"

#include <iostream>
using namespace std;

class Line
{

public:
    double length;
    Line(){}
    Line(double len){ length = len; }

    /*3.2运算符重载
    1,可以重定义或重载大部分 C++ 内置的运算符。
    2,重载的运算符是带有特殊名称的函数,函数名是由关键字 operator 和其后要重载的运算符符号构成的。
    3,与其他函数一样,重载运算符有一个返回类型和一个参数列表。
    */
    /*3.2.1一元运算符重载(递增++,递减--,负号-,逻辑非!)
    */
    Line operator-(){
        Line line;
        line.length = -length;
        return line;
    }

    /*3.2.2二元运算符重载
    二元运算符需要两个参数。加运算符( + )、减运算符( - )、乘运算符( * )和除运算符( / )都属于二元运算符。
    */
    Line operator-(const Line& line){
        Line linetmp;
        linetmp.length = this->length - line.length;
        return linetmp;
    }

    /*3.2.3关系运算符重载
    各种关系运算符( < 、 > 、 <= 、 >= 、 == 等等),可以重载任何一个关系运算符,重载后的关系运算符可用于比较类的对象。
    */
    bool operator<(const Line& line){
        return length < line.length;
    }

    /*3.2.4输入/输出运算符重载
    1,您可以重载流提取运算符 >> 和流插入运算符 << 来操作对象等用户自定义的数据类型。
    2,需要把运算符重载函数声明为类的友元函数,这样就能不用创建对象而直接调用函数。
    */
    friend ostream& operator << (ostream& output, const Line& line){
        output << "length:" << line.length;
        return output;
    }

    /*3.2.5 ++ 和 -- 运算符重载
    递增运算符( ++ )和递减运算符( -- ),包括前缀和后缀两种用法。
    */
    // 重载前缀递增运算符( ++ )
    Line operator++(){
        return Line(++length);
    }
    // 重载后缀递增运算符( ++ )
    Line operator++(int){
        return Line(length++);
    }

    /*3.2.6 赋值运算符重载
    */
    void operator=(const Line& line){
        length = line.length;
    }

    /*3.2.7 函数调用运算符 () 重载
    可以被重载用于类的对象。重载 ()是创建一个可以传递任意数目参数的运算符函数。
    */
    Line operator()(int a, int b, int c){
        Line line;
        line.length = a + b + c;
        return line;
    }
    /*3.2.8 下标运算符 [] 重载
    下标操作符 [] 通常用于访问数组元素。重载该运算符用于增强操作 C++ 数组的功能。
    */
    int arr[10];
    int& operator[](int i){
        return arr[i];
    }

};


/*3.2.9类成员访问运算符 -> 重载
1,类成员访问运算符(->)可以被重载,但它较为麻烦。它被定义用于为一个类赋予"指针"行为。
2,运算符->必须是一个成员函数。
3,如果使用了->运算符,返回类型必须是指针或者是类的对象。
4,运算符->通常与指针引用运算符 * 结合使用,用于实现"智能指针"的功能。
5,这些指针是行为与正常指针相似的对象,唯一不同的是,当您通过指针访问对象时,它们会执行其他的任务。
比如,当指针销毁时,或者当指针指向另一个对象时,会自动删除对象。
6,间接引用运算符->可被定义为一个一元后缀运算符。也就是说,给出一个类:
class Ptr{
//...
X * operator->();
};
类 Ptr 的对象可用于访问类 X 的成员,使用方式与指针的用法十分相似。例如:
void f(Ptr p)
{
p->m = 10; // (p.operator->())->m = 10
}
语句 p->m 被解释为(p.operator->())->m。同样地,下面的实例演示了如何重载类成员访问运算符->。
*/

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

// 假设一个实际的类
class Obj {
    static int i, j;
public:
    void f() const { cout << i++ << endl; }
    void g() const { cout << j++ << endl; }
};

// 静态成员定义
int Obj::i = 10;
int Obj::j = 12;

// 为上面的类实现一个容器
class ObjContainer {
    vector<Obj*> a;
public:
    void add(Obj* obj)
    {
        a.push_back(obj);  // 调用向量的标准方法
    }
    friend class SmartPointer;
};

// 实现智能指针,用于访问类 Obj 的成员
class SmartPointer {
    ObjContainer oc;
    int index;
public:
    SmartPointer(ObjContainer& objc)
    {
        oc = objc;
        index = 0;
    }
    // 返回值表示列表结束
    bool operator++() // 前缀版本
    {
        if (index >= oc.a.size()) return false;
        if (oc.a[++index] == 0) return false;
        return true;
    }
    bool operator++(int) // 后缀版本
    {
        return operator++();
    }
    // 重载运算符 ->
    Obj* operator->() const
    {
        if (!oc.a[index])
        {
            cout << "Zero value";
            return (Obj*)0;
        }
        return oc.a[index];
    }
};

void testPointerAccess(){
    const int sz = 10;
    Obj o[sz];
    ObjContainer oc;
    for (int i = 0; i < sz; i++)
    {
        oc.add(&o[i]);
    }
    SmartPointer sp(oc); // 创建一个迭代器
    do {
        sp->f(); // 智能指针调用
        sp->g();
    } while (sp++);
}
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页