首先本文以C++描述面向对象。面向对象应该可以说是C++对C最为重要的扩充。面向对象使得C++可以用更符合人的思维模式的方式编程,使得有一定基础的程序员可以更容易的写程序。相对于C,C++还有其他许多方面的改进,唯一的缺点就是损失了些许效率。本文只针对笔者心中的C++面向对象进行总结说明。

最开始学习面向对象切忌浮躁,需要有耐心的去理解。如果不能从文字解释上理解这里面“对象”这个词,可以从C++增加的关键字class的用法开始理解,这个后面再进行说明。

1.C与C++对比

  在C++编译环境中完全使用C的语法是可以编译通过的,除了一些原本不太规范的地方,比如:无返回类型的函数、非void返回类型函数内部无实际返回值等等在C++编译环境中无法编译通过。

// 无返回类型
test1()
{ } int test2()
{
// 无返回语句
}

  相对于C,C++最重要的改动就是面向对象,面向对象一词算是对大部分改动的一个概括,下面以“给定直角三角形的两条直角边长,求该三角形的面积”这个简单问题,用C/C++写出几种不同的解法

(对于几十行的小程序来说,从哪里开始读代码都无所谓,但是大程序,我相信多数人都是从main函数开始读代码的,这样来确定代码的业务流程)

首先用C语言,直接输出,这种方式代码量少,但可读性很差,如果没有文字说明这是求直角三角形面积,不好确定代码究竟是干什么的

/*
* 直接输出的方式,这种方式可读性很差,
* 如果没有文字说明这是求直角三角形面积,不好确定代码究竟是干什么的。
*/
#include <stdio.h> int main(int arg, char * args[])
{
// 直接输出
printf("The area is :%.0f\n", 3.0f * 4.0f / );
getchar();
return ;
}

  使用C语言结构体和函数相结合的方式,代码量提升,但是可以很清晰的知道代码的意图。(有个前提,英语要稍微合格,毕竟大多数程序语言都是英美出品,如果英语不合格,你就不知道RightTriangle是直角三角形的意思)

 #include <stdio.h>
// 直角三角形结构体
struct RightTriangle
{
float side1;
float side2;
};
// 获取一个直角三角形面积
float getArea(struct RightTriangle triangle)
{
return triangle.side1 * triangle.side2 / ;
} int main(int arg, char * args[])
{
struct RightTriangle triangle = { , };
printf("The area is : %.0f\n", getArea(triangle));
getchar();
return ;
}

  下面算是很接近C语言写法的C++写法实现(对C++来说不是很规范),可以发现C++中函数可以写在结构体内部。std::cout是C++中标准打印方式,相当于C语言中的printf,不过需要用到“<<”操作符,std::cout的好处是可以自动匹配值类型。

  多个输出项都可以用“<<”操作符连接起来,std::endl是换行的意思。

  从main函数中的语句可以清晰读出获取两边长为3,4的直角三角形的面积。

 #include <iostream>
// 这里先用结构体作为示例,相对于C语言的struct,可以在struct内部使用同名函数对内部变量进行初始化
// 这个函数叫做构造函数,还可以在struct写其他相关计算的函数,如函数getArea
struct CRightTriangle
{
CRightTriangle(float side1, float side2)
{
m_fside1 = side1;
m_fside2 = side2;
}
float getArea()
{
return m_fside1 * m_fside2 / ;
}
float m_fside1;
float m_fside2; }; int main(int arg, char * args[])
{
// 输出直角边分别为3,4的直角三角形的面积
std::cout << "The area is : "<< CRightTriangle(, ).getArea() << std::endl; getchar();
return ;
}

  如果整个程序真的可以不到10行的话,个人建议还是第一种写法,但是我想不会有谁学C/C++就为了自己个人写个几十行代码的程序的。大部分的开发都是许多人通力合作的,所以程序的可读性很重要。

2.封装

  封装指的是隐藏细节,使得代码模块化。上面代码中getArea()可以算是代码模块话,那么隐藏细节又指什么呢?这里需要改造下上面的代码进行说明:

 #include <iostream>

 class CRightTriangle
{
public:
// 构造函数,用来初始化对象,初始化操作包括设置对象内部初始值
CRightTriangle(float side1, float side2) :m_fside1(side1), m_fside2(side2) // 使用初始化列表初始化值
{
}
float getArea()
{
return m_fside1 * m_fside2 / ;
}
private:
float m_fside1;
float m_fside2; }; int main(int arg, char * args[])
{
// 输出直角边分别为3,4的直角三角形的面积
CRightTriangle triangle(, );
std::cout << "The area is : " << triangle.getArea() << std::endl; getchar();
return ;
}

  C++中,更倾向于使用class,也就是类,类定义出的变量叫做对象。

  相比较于上一节的代码,首先我将struct换成了class,C++中struct和class的区别很小,主要是默认访问权限的区别。c++中struct的默认访问权限是public,class的默认访问权限是private。访问权限暂且不细说,暂时要知道,private的访问权限的内容无法通过"."操作符调用,比如上面的代码,定义triangle后,无法使用triangle.m_fside1获取该值,而在上一节的代码中是可以这样获取的。这就叫做隐藏细节

  访问说明符有private,protected,public三种(protected在下节继承中在细说),除了使用struct和class控制默认访问权限外,还可以用在代码中写访问说明符的方式规定访问权限,显示访问说明符的作用范围是直到遇到下一个访问说明符或者到类的末尾。

  隐藏细节,使得代码模块化,这就是所谓封装。

3.继承

  继承是两个类之间的一种关系(硬要说也可以是多个类之间的一种关系,这里以两个类来说明)。

  类B公共继承于类A,那么类A是类B的父类,类B是类A的子类(或派生类)。类A中所有的公共成员函数和变量,那么在类B中无需任何代码可以直接使用。

 #include <iostream>

 class A
{
public:
// 多个同名函数的代码现象被称为函数重载,只要同名函数相互间的形参个数或者形参类型不同,就不会编译出错
// 构造函数重载
A(int a, int b) :m_na(a), m_nb(b){ std::cout << "A Con" << std::endl; }
// 符号~加上类名的函数叫做析构函数,会在对象被释放时自动调用
// 一般用于释放用户申请的空间
~A(){ std::cout << "A De" << std::endl; }
// 可以以下面这种方式代理初始化
A() :A(, ){}
int getSum(){ return m_na + m_nb; }
int m_na;
int m_nb; };
// 类B public继承类A
class B :public A
{
public:
// 类A没有默认无参构造函数,所以这样
B(int a, int b, int c) :A(a, b), m_nc(c)
{ std::cout << "B Con" << std::endl; }
~B(){ std::cout << "B De" << std::endl; } // B可直接使用A的公共变量
int getProduct()
{
return m_na * m_nb * m_nc;
} B() :A(, ){}
// B类自己的成员
int m_nc;
}; int main(int arg, int args[])
{
// 加上大括号可以观察对象构造和析构的顺序
{
B b(, , );
// B可直接使用A的函数和变量
std::cout << b.getSum() << std::endl;
// B自己的成员函数
std::cout << b.getProduct() << std::endl;
// 因为是公共成员所以可以直接访问
std::cout << b.m_na + b.m_nb + b.m_nc;
getchar();
}
getchar();
return ;
}

(继承说明代码)

以上代码能够还算清楚的展示继承的用法,其中,类A和类B的所有成员都是公共的(public),类B也是公共继承于类A,所以可以访问A中的成员。下面以继承说明代码称呼这段程序。

将类A中的成员变量变为私有的(private):

 class A
{
public:
A(int a, int b) :m_na(a), m_nb(b){ std::cout << "A Con" << std::endl; }
~A(){ std::cout << "A De" << std::endl; }
// 可以以下面这种方式代理初始化
A() :A(, ){}
int getSum(){ return m_na + m_nb; }
private:
int m_na;
int m_nb;
};

这个时候继承说明代码中的31行和50行会报错,因为私有成员无法继承。但是B类定义的对象还是可以使用getSum函数。

将类A中的成员变量变为受保护的(protected):

这个时候继承说明代码中的50行会报错,因为受保护成员可以继承,但是无法外部访问。但是B类定义的对象还是可以使用getSum函数。

下面说说继承方式,以上所有的测试代码都是在类B共有继承类A的情况。类的继承同样分为三种,公有继承(public),私有继承(private),保护继承(protected)。三种继承模式的共同点是,原来私有的,继承后就不可见(不允许子类使用)。不同点就是,私有继承会将其他所有父类成员改为私有的,受保护继承会将其他所有父类成员改为受保护的,共有继承会保留其他所有父类成员的访问权限。

比如上面把类B改为私有继承或者保护继承类A,那么继承说明代码中第53行会报错。

继承暂时说这么多。

4.多态

应该说正是有了多态才使得面向对象变得伟大,多态使得许多设计模式得以实现。

多态概念比较抽象,较为普通的解释就是同样的调用语句有多种不同的执行效果。

多态发生在有继承关系的类之间,要体现多态关系至少得有三个类,且其中两个类继承于另一个类。且这两个子类需要实现父类中带virtual关键字的方法。最后需要一个指向父类对象的指针或者引用来操作实现多态。

类比C语言,C++中的多态有点像C中的函数指针。

下面写个简单多态的例子:

 #include <iostream>

 // 父类
class Parent
{
public:
// 只有使用virtual才能实现多态
virtual void func()
{
std::cout << "Parent" << std::endl;
}
};
// 子类1
class Child1 : public Parent
{
public:
virtual void func()
{
std::cout << "Child1" << std::endl;
}
};
// 子类2
class Child2 : public Parent
{
public:
// 如果子类不会再有子类的话,可以不用virtual
void func()
{
std::cout << "Child2" << std::endl;
}
};
// 多态的引用函数展示
void func(Parent & p)
{
p.func();
}
// 测试用例
void test()
{
// 分别定义子类
Parent p;
Child1 c1;
Child2 c2;
// 定义指向父类的指针
Parent *pP = &p;
// 下面演示指针的执行结果
pP->func();
pP = &c1;
pP->func();
pP = &c2;
pP->func();
std::cout << std::endl;
// 引用函数
func(p);
func(c1);
func(c2);
} int main(int arg, char * args[])
{
test();
getchar();
return ;
}

以上代码的执行结果为:

Parent
Child1
Child2 Parent
Child1
Child2

而如果不使用virtual关键字,上面的所有结果都会是Parent。

C++中的多态的实现是当类中声明虚函数时(有virtual关键字),编译器会在类中生成一个虚函数表,该表是一个存储类成员函数指针的数据结果,由编译器检测生成维护,编译器会识别所有的带virtual的成员函数,将其放入虚函数表中。为了连接虚函数表,对于有虚函数表的每个对象中都会有个指向虚函数表的指针,一般称其为vptr指针。

我们可以用sizeof证明该指针的存在:

 #include <iostream>

 // C1是有虚函数的类
class C1
{
public:
C1() :m_na(){}
virtual void func();
private:
int m_na;
};
// C2除了无virtual,其他和C1一样
class C2
{
public:
C2() :m_na(){}
void func();
private:
int m_na;
}; int main(int arg, char * args[])
{
// 输出两个类对象的占用空间大小
std::cout << "sizeof C1:" << sizeof(C1) << "," << "sizeof C2:" << sizeof(C2) << std::endl;
getchar();
return ;
}

在32位编译环境下,执行结果为:

sizeof C1:,sizeof C2:

可见C1多了一个指针大小的空间,证明多了一个指针的存在。

5.简要概括

本文只是大概总结了下C++面向对象的特性以及和C语言做了下对比,许多基础的特性,如命名空间、函数重载、运算符重载、友元等方面的东西并未说明,高级应用如标准库、模板、stl等技术也未提及,如果想要打一个良好的基础,建议通读C++ Primer这本书,现在最新版本是实现了C++ 11 特性的第五版。

C/C++心得-面向对象的更多相关文章

  1. JAVA第二次blog总结

    JAVA第二次blog总结 0.前言 这是我们在博客园上第二次写博客,进行JAVA阶段学习的总结.现在我们接触到JAVA已经有一段时间了,但难点还是在于编程思想和方法的改变,第二阶段的学习让我对于理解 ...

  2. Javascript面向对象研究心得

    这段时间正好公司项目须要,须要改动fullcalendar日历插件,有机会深入插件源代码.正好利用这个机会,我也大致学习了下面JS的面向对象编程,感觉收获还是比較多的. 所以写了以下这篇文章希望跟大家 ...

  3. 【Java心得总结五】Java容器上——容器初探

    在数学中我们有集合的概念,所谓的一个集合,就是将数个对象归类而分成为一个或数个形态各异的大小整体. 一般来讲,集合是具有某种特性的事物的整体,或是一些确认对象的汇集.构成集合的事物或对象称作元素或是成 ...

  4. .NET应用架构设计—面向对象分析与设计四色原型模式(彩色建模、领域无关模型)(概念版)

    阅读目录: 1.背景介绍 2.问自己,UML对你来说有意义吗?它帮助过你对系统进行分析.建模吗? 3.一直以来其实我们被一个缝隙隔开了,使我们对OOAD遥不可及 4.四色原型模式填补这个历史缝隙,让我 ...

  5. JS面向对象的程序设计

    面向对象的语言有一个标志,即拥有类的概念,抽象实例对象的公共属性与方法,基于类可以创建任意多个实例对象,一般具有封装.继承.多态的特性!但JS中对象与纯面向对象语言中的对象是不同的,ECMA标准定义J ...

  6. 20145206《Java程序设计》实验二Java面向对象程序设计实验报告

    20145206<Java程序设计>实验二Java面向对象程序设计实验报告 实验内容 初步掌握单元测试和TDD 理解并掌握面向对象三要素:封装.继承.多态 初步掌握UML建模 熟悉S.O. ...

  7. 十天来学习java的心得体会

    有关学习java是几天来的心得体会: 十天学习java遇到很多问题,每个问题都是经过反复的看书本以及上网查找资料来解决的,发现这一点真的需要自己来而不是去遇到什么问题就去依靠他人(师兄.同学).在其中 ...

  8. C语言课设心得分享(一)

    今儿上完课设,老师果然讲的比较少,周四还不用去,看来还是学生自己折腾.我在做课设的过程中,攒了一些心得/体会,希望能和大家分享分享,也希望能一起探讨探讨.如果是我能回答的问题,我很乐意能够提供帮助. ...

  9. VBA的一些使用心得

    VBA的知识比较零散,因此开一贴记录一下使用VBA时的一些方法和心得.主要针对Excel,参考在这里 1. Collection Class 大部分情况下,Collection Class是比数组(A ...

随机推荐

  1. NOI 2018 酱油记

    转眼离 NOI 2018 已经过了一个星期了,退役的我还是随便来水水吧. 语法.错字之类的可能会很多,但是我也不拘这点小节了. 恭喜 yww, zjt, sk 进队,zwl, myh au , yay ...

  2. MSSqlServer 数据库降级及数据转移

    --MSSqlServer数据库降级及数据转移--MS SQL SERVER高版本数据库(Database_A)恢复数据到低版本数据库(Database_B)中--1.数据库结构对象(包含表.视图.函 ...

  3. 修改django 后台admin用户的密码

    python manage.py shellfrom django.contrib.auth.models import User from django.contrib.auth.models im ...

  4. Struts2 学习(二)

    一.Struts2 配置文件 1.配置多个配置文件 在大部分应用里,随着应用规模的增加,系统中Action的数量也会大量增加,导致struts.xml配置文件变得非常臃肿. 为了避免struts.xm ...

  5. java爬虫之入门基础

    相比于C#,java爬虫,python爬虫更为方便简要,首先呢,python的urllib2包提供了较为完整的访问网页文档的API,再者呢对于摘下来的文章,python的beautifulsoap提供 ...

  6. GIT学习笔记——第一章

    git之vim编辑器退出命令 # 学习笔记 张文军微博主页  张文军码云主页   张文军新浪云主页  张文军博客主页 ## 刚学习git,好多东西没接触过,进入vim后不知道如何出来了,网上找了很多都 ...

  7. gradle -v不是外部命令, 内部命令,或批处理文件

    安装完gradle并且配置了环境变量之后,使用windos+R,cmd 进入Dos命令gradle -v检测版本号出现了: 1 --首先找到gradle文件所在目录 一般是在C:\Users\su\. ...

  8. servlet开发(二)之servlet的线程安全问题

    之所以考虑线程安全问题,是因为引入了多线程.多线程指的是这个程序(一个进程)运行时产生了不止一个线程.如果不考虑多线程的话,程序执行只有一条路径,就像人在敲代码的时候只能敲代码,不能戴上耳机听歌.引入 ...

  9. SPOJ2666 QTREE4

    我是萌萌的传送门 我是另一个萌萌的传送门 一道树分治……简直恶心死了……我在调代码的时候只想说:我*************************************************…… ...

  10. JVM jmap

    需求:经常会因为OOM而导致系统挂掉,很多服务无法连接,所以准备了解一下. 参考:http://www.open-open.com/lib/view/open1390916852007.html 一. ...