C++编程面向对象入门全面详解

目录
    • 1. struct和class的区别
    • 2. explicit构造
    • 3. const和mutable
    • 4. 自引用
    • 5. static
    • 复数的实现
    • 6.成员函数重载
  • 7.运算符重载
    • 8.new
    • 9.析构函数
    • 10.friend
    • 11.类的继承
    • 12.多态
    • 13.virtual

1. struct和class的区别

如果从C语言的视角来看,所谓类就是能够调用自身成员的结构体。而在C++中,关键字struct虽然仍旧保留,但已非C语言中的结构体,而是表示默认成员共有的class

即在C++中,struct C{/*code*/}class C{public:/**/}并无区别,例如下面两组代码所实现的功能是完全一致的。

//默认成员公有
struct Number{
private;
    float val;
public:
    float pubVal;
    Number(float inVal);
};
//默认成员为私有
class Number{
    float val;//外部无法直接访问
public:
    float pubVal;
    Number(float inVal);
};

所谓私有成员,就是外部函数不可访问的成员

void printPublic(Number num){
    cout<<num.pubVal<<endl;
}
void printPrivate(Number num){
    cout<<num.val<<endl;        //报错,无法访问私有类型
}

不过从C语言的视角来看,类也的确保留了一些struct的风格,其初始化方法与指针调用便是明证。

int main(){
    Number num{3.14};       //相当于引用构造函数
    printNumber(num);
    Number* pNum = &num;    //指向num的指针
    //->表示类指针所指向的成员
    cout<<pNum->pubVal<<endl;
    system("pause");
    return 0;
}

输出为

PS E:\Code\cpp> g++ .\oop.cpp
PS E:\Code\cpp> .\a.exe
3.14
3.14

2. explicit构造

由于C++对泛型具备十分良好的支持,语言本身的强大可能会导致用户在使用过程中不严谨,继而增大维护成本。例如对于如下构造函数

Number::Number(float inVal){
    val = inVal;
}

那么下面的几个语句都能够输出正确的值

int main(){
    Number num{3.14};
    printNumber(num);
    num = 1.414;
    printNumber(num);
    printNumber(0.618);
    system("pause");
    return 0;
}

结果为

PS E:\Code\cpp> g++ .\oop.cpp
PS E:\Code\cpp> .\a.exe
3.14
1.414
0.618
请按任意键继续. . .

可见这三条语句都没有报错

    Number num{3.14};
    num = 1.414;
    printNumber(0.618);

第一条是没有问题的,是简单赋值语句;第二条和第三条则是暗中调用构造函数,将浮点类型的变量转换成了Number类型,这种意义不明的代码自然会引起维护上的困难。explicit就为解决这种问题而生的。

将构造函数用explicit进行标记,可以有效禁止这种隐式转换

class Number{
    float val;
public:
    explicit Number(float inVal);
    float pubVal;
};
int main(){
    Number num{3.14};
    num = 1.414;        //编译不予通过
    printNumber(0.618);//编译不予通过
    //...
}

3. const和mutable

顾名思义,二者分别是常量与变量,前者要求成员函数不得修改类的成员变量

class Number{
    float val;
public:
    mutable float pubVal;       //注意该变量用了mutable
    explicit Number(float inVal);
    void printVal() const;      //该方法用了const
};
void Number::printVal() const{
    cout<<val<<endl;
    /*
    val = val+1;   //这是不被允许的
    */
   pubVal = val+1;  //这是被允许的
}

即,const成员只能修改mutable成员。

4. 自引用

自引用是一种编程技巧,对于更改类状态的函数,如果将类本身作为返回值,那么就可以实现炫酷而优雅的链式操作。

class Number{
    float val;
public:
    explicit Number(float inVal);
    Number& addOne();       //其返回值是当前对象的地址
};
Number& Number::addOne(){
    cout<<val++<<endl;
    return *this;
}

其中,*this指向调用该成员函数的对象,测试一下

int main(){
    Number num{3.14};       //相当于引用构造函数
    num.addOne().addOne().addOne();
    system("pause");
    return 0;
}

结果为

PS E:\Code\cpp> g++ .\oop.cpp
PS E:\Code\cpp> .\a.exe
3.14
4.14
5.14
请按任意键继续. . .

5. static

顾名思义,静态成员之所以被称为静态,在于其存储位置只有一个。对于一个类而言,无论创建了多少实例,类中的静态变量就只被存储在那一个位置。这意味着静态成员要比对象实例具有更长的生命周期,当一个对象被销毁之后,静态成员并没有被销毁,从而再次被调用的时候,也不必另行分配内存。

class Number{
    float val;
    static Number defaultNum;
public:
    explicit Number(float inVal=0);
    static void setDefault(float inVal);
    void printVal() const;
};
void Number::printVal() const{
    cout<<val<<endl;
}
//定义默认Num
Number Number::defaultNum{3.14};
void Number::setDefault(float val){
    defaultNum = Number{val};
};
Number::Number(float inVal){
    val = inVal ? inVal : defaultNum.val;
}
int main(){
    Number num{};       //相当于引用构造函数
    num.printVal();
    system("pause");
    return 0;
}

输出为

PS E:\Code\cpp> .\a.exe
3.14
请按任意键继续. . .

复数的实现

复数有实部和虚部,默认值为0,其加法和减法分别就是实部和虚部相减,其乘法为

#include<iostream>
using namespace std;
class Complex{
    float real;     //实部
    float im;       //虚部
    static Complex defaultNum;
public:
    explicit Complex(float inReal=0, float inIm=0);
    static void setDefault(float inReal, float inIm);
    void printVal() const;
    Complex& add(float inReal, float inIm);
    Complex& minus(float inReal, float inIm);
    Complex& multi(float inReal, float inIm);
    Complex& div(float inReal, float inIm);
};
//默认值为{0,0}
Complex Complex::defaultNum{0,0};
void Complex::setDefault(float inReal,float inIm){
    defaultNum = Complex{inReal, inIm};
};
//打印当前值
void Complex::printVal() const{
    cout<<"real part: "<<real<<endl;
    cout<<"image part:"<<im<<endl;
}
//加法
Complex::Complex(float inReal, float inIm){
    real = inReal ? inReal : defaultNum.real;
    im = inIm ? inIm : defaultNum.im;
}
Complex& Complex::add(float inReal, float inIm){
    real += inReal ? inReal : 0;
    im += inIm ? inIm : 0;
    return *this;
}
Complex& Complex::minus(float inReal, float inIm){
    real -= inReal ? inReal : 0;
    im -= inIm ? inIm : 0;
    return *this;
}
Complex& Complex::multi(float inReal, float inIm){
    float temp = real*inReal - im*inIm;
    im = real*inIm + im*inReal;
    real = temp;
    return *this;
}
Complex& Complex::div(float inReal, float inIm){
    float temp = inReal*inReal + inIm*inIm;
    float tempReal = (real*inReal + im*inIm)/temp;
    im = (im*inReal-real*inIm)/temp;
    real = tempReal;
    return *this;
}
int main(){
    Complex num{};       //相当于引用构造函数
    num.add(1,2).multi(3,4).div(1,2);
    num.printVal();
    system("pause");
    return 0;
}

下面的操作便基于这个复数类进行。

6.成员函数重载

上述的加减乘除运算,默认输入值为实部和虚部的组合,但并不能实现两个Complex的运算。C++支持成员函数的重载。

class Complex{
/*
上文中所定义的类的结尾
*/
    Complex operator+(Complex);
    Complex operator-(Complex);
    Complex operator*(Complex);
    Complex operator/(Complex);
    //实现类似数乘功能
    Complex operator*(float);
    Complex operator/(float);
}

这些函数可以通过最简单的方式定义

Complex& Complex::add(Complex num){
    real += num.real;
    im += num.im;
    return *this;
}

也可以通过调用已经定义过的成员函数

Complex& Complex::multi(Complex num){
    multi(num.real, num.im);
    return *this;
}

7.运算符重载

在C++中,可以很方便地对一些运算符进行重载,其格式为

Complex operator+(Complex);

对于两个复数a和b来说,调用重载之后的运算符a+b等价于a.operator(b)

其具体实现为

class Complex{
/*
上文中所定义的类的结尾
*/
    Complex operator+(Complex);
    Complex operator-(Complex);
    Complex operator*(Complex);
    Complex operator/(Complex);
}
Complex Complex::operator+(Complex num){
    float outReal = real+num.real;
    float outIm = im+num.im;
    return Complex{outReal, outIm};
}
Complex Complex::operator-(Complex num){
    return Complex{real-num.real, im-num.im};
}
Complex Complex::operator*(Complex num){
    return Complex{real*num.real - im*num.im,
                   real*num.im + im*num.real};
}
Complex Complex::operator/(Complex num){
    float temp = num.real*num.real + num.im*num.im;
    return Complex{(real*num.real + im*num.im)/temp,
                   (im*num.real-real*num.im)/temp};
}
Complex Complex::operator*(float val){
    return Complex{real*val,im*val};
}
Complex Complex::operator/(float val){
    return Complex{real/val,im/val};
}
//主函数
int main(){
    Complex temp{1,1};
    Complex temp1 = temp-temp*temp*2;
    temp1.printVal();
    temp.printVal();
    system("pause");
    return 0;
}

测试一下结果为

PS E:\Code\cpp> g++ .\oop.cpp
PS E:\Code\cpp> .\a.exe
real part: 1
image part:-3
real part: 1
image part:1

可见操作符虽然被重载了,但运算次序得以保留。

8.new

C语言中通过STRUCT* struct = (STRUCT*)malloc(sizeof(STRUCT))的方式来动态地开辟内存,留待日后使用。

在C++中,new可以胜任这一工作。

例如

int* p = new int;
int* Q = new int(5);

对于Complex类,可以通过指针形式进行实现

int main(){
    Complex* temp = new Complex(1,1);
    temp->add(*temp);
    temp->printVal();
    delete(temp);       //销毁temp内存
    system("pause");
    return 0;
}

其中,->亦承自C语言,用于类指针调用类成员,其结果为

PS E:\Code\cpp> g++ .\oop.cpp
PS E:\Code\cpp> .\a.exe
real part: 2
image part:2
请按任意键继续. . .

9.析构函数

一般通过new来分配内存空间,需要在调用结束之后使用delete对内存进行释放,delete的执行过程,便会调用析构函数。

在解释析构函数之前,需要回顾一下构造函数,所谓构造函数,即与类名相同的函数,通过构造函数可以创建一个类的对象,并开辟足够的内存。析构函数即销毁函数,将构造函数开辟的内存销毁掉。

析构函数亦与类名相同,而且无参数无return不可重载,是一个不易理解但易于使用的方法。

public:
    explicit Complex(float inReal=0, float inIm=0);
    //此即析构函数,
    ~Complex(){}

10.friend

在复数类中,实部和虚部被封装为私有变量,外部函数是无法访问的。此时,如果希望在其他类中创建一个提取复数实部或虚部的变量,则可以考虑友元机制。

所谓友元机制,即允许一个类将其非共有成员授权给指定的函数或者类,通过关键字friend修饰。例如,

/*
Complex类
*/
    friend float getReal(Complex num);
};
float getReal(Complex num){
    cout<<num.real<<endl;
    return num.real;
}

这样,getReal就可以直接访问Complex类的私有成员。

11.类的继承

一般来说,复数 a + b i a+b\text{i} a+bi并不支持类似直乘的操作,即 ( a + b i ) ∗ ( a + b i ) ≠ a b + c d i (a+b\text{i})*(a+b\text{i})\not ={ab+cd\text{i}} (a+bi)∗(a+bi)​=ab+cdi,那么如果希望构造一种新的代数关系,使之既支持复数乘法,又可以直乘,那么就需要新建一个类,为了避免代码过于重复,这个类可以作为复数类的派生类而存在。

需要注意的一点是,此前所创建的Complex类默认成员为私有,所以其imreal对于子类而言是不可访问的。出于简单考虑,我们将class改为struct,这样其子类便可无痛调用。

//Complex类的派生类
class antiComplex : Complex{
public:
    antiComplex(float inReal,float inIm){
        real = inReal;
        im = inIm;
    };
    void printVal();
    antiComplex operator*(antiComplex);
};
antiComplex antiComplex::operator*(antiComplex num){
    return antiComplex{real*num.real,im*num.im};
}
//重写printVal函数
void antiComplex::printVal(){
    cout<<"I'm antiComplex"<<endl;
    cout<<"real part: "<<real<<endl
        <<"image part:"<<im<<endl;
}
int main(){
    antiComplex temp{1,2};
    temp.printVal();
    temp = temp*temp;
    temp.printVal();
    system("pause");
    return 0;
}

其结果为

PS E:\Code\cpp> .\a.exe
I'm antiComplex
real part: 1
image part:2
I'm antiComplex
real part: 1
image part:4
请按任意键继续. . .

在C++中有三种继承方式,分别是publicprivateprotected,一般默认为public继承,其特点是无法访问父类的私有成员;private继承则连公有成员和保护成员都无法访问;protected则允许其子类访问,但不允许子类的子类访问。

具体表现如下表所示,其读法为public成员在private继承时表现为private成员。

public继承 private继承 protected继承
public成员 public private protected
private成员 private private private
protected成员 protected private protected

12.多态

所谓多态就是多个子类继承一个基类时的差异性,例如,ComplexantiComplex都可以作为一种抽象数据结构的子类,毕竟二者只有在乘除法上表现不同。

#include<iostream>
using namespace std;
struct Abstract{
    float real;
    float im;
    Abstract(float inReal, float inIm){
        real = inReal;
        im = inIm;
    }
    void printVal(){
        cout<<"I'm Abstract"<<endl;
    };
    Abstract& multi(Abstract val){};
};
struct Complex:Abstract{
    Complex(float inReal, float inIm)
        :Abstract(inReal,inIm){}
    void printVal();
    Abstract& multi(Abstract val);
};
void Complex::printVal(){
    cout<<"I'm Complex:"
        <<real<<"+"<<im<<"i"<<endl;
}
Abstract& Complex::multi(Abstract val){
    float temp = real*val.real - im*val.im;
    im = real*val.real + im*val.im;
    real = temp;
    return *this;
}
struct antiComplex:Abstract{
    antiComplex(float inReal, float inIm)
        :Abstract(inReal,inIm){}
    void printVal();
    Abstract& multi(Abstract val);
};
void antiComplex::printVal(){
    cout<<"I'm antiComplex:"
        <<real<<"+"<<im<<"j"<<endl;
}
Abstract& antiComplex::multi(Abstract val){
    real = real*val.real;
    im = im*val.im;
    return *this;
}
int main(){
    Complex temp{1,2};
    antiComplex antemp{1,2};
    temp.multi(temp).multi(temp);
    antemp.multi(antemp).multi(temp);
    temp.printVal();
    antemp.printVal();
    system("pause");
    return 0;
}

其输出结果为

PS E:\Code\cpp> g++ .\oop.cpp
PS E:\Code\cpp> .\a.exe
I'm Complex:-3+5i
I'm antiComplex:1+4j
请按任意键继续. . .

可见这个结果是错的,原因在于multi函数的返回指针为Abstract类型,而基类中的multi为一个空函数,所以只执行一次multi。所以,如果子类再调用函数之后继续保持子类的方法就好了,这就需要使用关键字virtual

13.virtual

直接通过指针来说明virtual的功能比较合适。

struct Abstract{
    float real;
    float im;
    Abstract(float inReal, float inIm){
        real = inReal;
        im = inIm;
    }
    void printVal(){
        cout<<"I'm Abstract"<<endl;
    };
};
struct Complex:Abstract{
    Complex(float inReal, float inIm)
        :Abstract(inReal,inIm){}
    void printVal(){
        cout<<"I'm Complex:"
        <<real<<"+"<<im<<"i"<<endl;
    }
};
int main(){
    Abstract* a;
    Complex temp{1,2};
    a = &temp;
    a->printVal();
    system("pause");
    return 0;
}

其运行结果为

PS E:\Code\cpp> g++ .\oop.cpp
PS E:\Code\cpp> .\a.exe
I'm Abstract

也就是说,虽然父类的指针指向了子类的对象,但最终指针指向的仍然是父类的函数。而如果在父类函数前加上virtual关键字,即将其改为

struct Abstract{
    float real;
    float im;
    Abstract(float inReal, float inIm){
        real = inReal;
        im = inIm;
    }
    virtual void printVal(){
        cout<<"I'm Abstract"<<endl;
    };
};

则其输出为

PS E:\Code\cpp> .\a.exe
I'm Complex:1+2i

可见虚函数的作用是使得父类指针指向实际对象的方法。将父类的multi函数变为

virtual Abstract& multi(Abstract val){};

则最终的输出结果为

PS E:\Code\cpp> .\a.exe
I'm Complex:-16+34i
I'm antiComplex:-16+136j

看的不过瘾?

C++元编程初步

以上就是C++面向对象入门全面详解的详细内容,更多关于C++面向对象入门的资料请关注我们其它相关文章!

(0)

相关推荐

  • C++内存管理看这一篇就够了

    目录 1 内存分布图 2 C语言和C++内存分配实现 2.1 C语言实现 2.2 C++实现 new的原理 delete的原理 3 C语言和C++内存管理区别 4 内存泄漏 总结 1 内存分布图 注意: 1.向下生长:地址由高到低 2.向上生长:地址由低到高 3.栈又叫堆栈,非静态局部变量/函数参数/返回值等等 4.堆用于程序运行时动态内存分配 2 C语言和C++内存分配实现 2.1 C语言实现 malloc函数 void *malloc(size_t size) 分配所需的内存空间,单位是字节

  • 详解C++值多态中的传统多态与类型擦除

    引言 我有一个显示屏模块: 模块上有一个128*64的单色显示屏,一个单片机(B)控制它显示的内容.单片机的I²C总线通过四边上的排针排母连接到其他单片机(A)上,A给B发送指令,B绘图. B可以向屏幕逐字节发送显示数据,但是不能读取,所以程序中必须设置显存.一帧需要1024字节,但是单片机B只有512字节内存,其中只有256字节可以分配为显存.解决这个问题的方法是在B的程序中把显示屏分成4个区域,保存所有要绘制的图形的信息,每次在256字节中绘制1/4屏,分批绘制.发送. 简而言之,我需要维护

  • 剖析C++的面向对象编程思想

    面向对象的程序设计 面向对象编程(Object Oriented Programming,OOP,面向对象程序设计) 的主要思想是把构成问题的各个事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙一个事物在整个解决问题的步骤中的行为. 面向过程就是分析出解决问题所需要的步骤,然后用函数逐步实现,再依次调用就可以了. 面向对象和面向过程是两种不同的编程思想,没有哪一种绝对完美,要根据具体需求拟定开发方案.例如,开发一个小型软件或应用程序,工程量小,短时间内即可完成,完全可以采用面

  • c++ 面向对象设计五大原则

    面向对象设计(OOD)是面向对象编程(OOP)必不可少的一个环节,只有好的设计,才能保障程序的质量.面向对象设计的主要任务就是类的设计,不少面向对象(OO)的先驱和前辈已经提出了很多关于类的设计原则,用于指导OOP,其中就包括类设计的五项基本原则. 1.单一职责原则(Single Resposibility Principle,SRP) 专注是一个人的优良品质,同样,单一职责也是一个类的优良设计.单一职责的核心思想:一个类只做好一件事情. 单一职责原则可以看作是高内聚.低耦合在面向对象原则上的引

  • C++编程面向对象入门全面详解

    目录 类 1. struct和class的区别 2. explicit构造 3. const和mutable 4. 自引用 5. static 复数的实现 6.成员函数重载 7.运算符重载 8.new 9.析构函数 10.friend 11.类的继承 12.多态 13.virtual 类 1. struct和class的区别 如果从C语言的视角来看,所谓类就是能够调用自身成员的结构体.而在C++中,关键字struct虽然仍旧保留,但已非C语言中的结构体,而是表示默认成员共有的class. 即在C

  • Python面向对象编程repr方法示例详解

    目录 为什么要讲 __repr__ 重写 __repr__ 方法 str() 和 repr() 的区别 为什么要讲 __repr__ 在 Python 中,直接 print 一个实例对象,默认是输出这个对象由哪个类创建的对象,以及在内存中的地址(十六进制表示) 假设在开发调试过程中,希望使用 print 实例对象时,输出自定义内容,就可以用 __repr__ 方法了 或者通过 repr() 调用对象也会返回 __repr__ 方法返回的值 是不是似曾相识....没错..和 __str__ 一样的

  • hibernate4快速入门实例详解

    Hibernate是什么 Hibernate是一个轻量级的ORMapping框架 ORMapping原理(Object RelationalMapping) ORMapping基本对应规则: 1:类跟表相对应 2:类的属性跟表的字段相对应 3:类的实例与表中具体的一条记录相对应 4:一个类可以对应多个表,一个表也可以对应对个类 5:DB中的表可以没有主键,但是Object中必须设置主键字段 6:DB中表与表之间的关系(如:外键)映射成为Object之间的关系 7:Object中属性的个数和名称可

  • Java探索之Feign入门使用详解

    一,简介 Feign使得 Java HTTP 客户端编写更方便.Feign 灵感来源于Retrofit.JAXRS-2.0和WebSocket.Feign最初是为了降低统一绑定Denominator到HTTP API的复杂度,不区分是否支持Restful.Feign旨在通过最少的资源和代码来实现和HTTP API的连接.通过可定制的解码器和错误处理,可以编写任意的HTTP API. Maven依赖: <!-- https://mvnrepository.com/artifact/com.netf

  • ABP(现代ASP.NET样板开发框架)系列之二、ABP入门教程详解

    ABP是"ASP.NET Boilerplate Project (ASP.NET样板项目)"的简称. ASP.NET Boilerplate是一个用最佳实践和流行技术开发现代WEB应用程序的新起点,它旨在成为一个通用的WEB应用程序框架和项目模板. ABP的官方网站:http://www.aspnetboilerplate.com ABP在Github上的开源项目:https://github.com/aspnetboilerplate ABP 的由来 "DRY--避免重复

  • Java编程复用类代码详解

    本文研究的主要是Java编程中的复用类,那么到底复用类是什么东西,又有什么用法,下面具体介绍. 看了老罗罗升阳的专访,情不自禁地佩服,很年轻,我之前以为和罗永浩一个级别的年龄,也是见过的不是初高中编程的一位大牛之一,专访之后,发现老罗也是一步一个脚印的人.别说什么难做,做不了,你根本就没去尝试,也没有去坚持. If you can't fly then run,if you can't run then walk, if you can't walk then crawl,but whateve

  • C语言实现面向对象的方法详解

    目录 1.引言 2.封装 3.继承 4.多态 4.1 虚表和虚指针 4.2 在构造函数中设置vptr 4.3 继承 vtbl 和 重载 vptr 4.4 虚函数调用 4.5 main.c 5.总结 1.引言 面向对象编程(OOP)并不是一种特定的语言或者工具,它只是一种设计方法.设计思想.它表现出来的三个最基本的特性就是封装.继承与多态.很多面向对象的编程语言已经包含这三个特性了,例如 Smalltalk.C++.Java.但是你也可以用几乎所有的编程语言来实现面向对象编程,例如 ANSI-C.

  • Reactive Programming入门概念详解

    目录 正文 Reactive Programming Reactive Streams Spring Reactor Reactive Streams.Reactor和WebFlux 区别? 正文 为了应对高并发环境下的服务端编程,xx提出了一个实现异步编程的方案 -Reactive Programming,中文名称反应式编程.反应式编程(reactive programming)并不是一个新的概念,也不是一个新的技术,很早之前就被提出来了. 先从几个概念入门说起: Reactive Progr

  • Java并发编程Semaphore计数信号量详解

    Semaphore 是一个计数信号量,它的本质是一个共享锁.信号量维护了一个信号量许可集.线程可以通过调用acquire()来获取信号量的许可:当信号量中有可用的许可时,线程能获取该许可:否则线程必须等待,直到有可用的许可为止. 线程可以通过release()来释放它所持有的信号量许可(用完信号量之后必须释放,不然其他线程可能会无法获取信号量). 简单示例: package me.socketthread; import java.util.concurrent.ExecutorService;

  • Docker Swarm入门实例详解

    Swarm 在 Docker 1.12 版本之前属于一个独立的项目,在 Docker 1.12 版本发布之后,该项目合并到了 Docker 中,成为 Docker 的一个子命令.目前,Swarm 是 Docker 社区提供的唯一一个原生支持 Docker 集群管理的工具.它可以把多个 Docker 主机组成的系统转换为单一的虚拟 Docker 主机,使得容器可以组成跨主机的子网网络. 1. Swarm 认识 Swarm 是目前 Docker 官方唯一指定(绑定)的集群管理工具.Docker 1.

随机推荐