-
浅谈c++
2012-06-13 23:26:49学了一年C++了,积累了一点知识,来分享下。...1.浅谈C++的学习路线 看了很多书,发现有许多书都是先介绍面向对象(OO),但是我认为这么做不好,因为C++并不是纯面向对象式的语言。人们常称C为中级语言学了一年C++了,积累了一点知识,来分享下。我说的C++是纯C++(ISOC++),并不是微软修改过的“托管式”C++。
很多人都说C++很难,很复杂。首先,我必须承认,C++的确复杂,但是并没有想象中的难,最重要的是掌握方法。
1.浅谈C++的学习路线
看了很多书,发现有许多书都是先介绍面向对象(OO),但是我认为这么做不好,因为C++并不是纯面向对象式的语言。人们常称C为中级语言(或者是终极),为何是中级而不是高级?因为不支持面向对象。
C++向下兼容C,有一说是“C++是C的超集,提供了对象层”,“兼容”与“超集”一说,只能针对C++98标准之前而言,如今C/C++都有了11标准,他们的有些新特性不再兼容。
a.浅谈入门
学习C++不需要先学C,按斯特劳斯特鲁普大神的话说,最好直接学C++,因为过程式思想必然会对你接受面向对象产生一定影响。在这里推荐一本入门书籍《C++Primer Plus》,这是本很详细的书,但是有个缺点,很多细节感觉作者只是提到了一点,但是并不详细,有种吊胃口的感觉。
在开始阶段切忌看《C++程序设计语言》,它很难,不要疑惑,C++是个大家伙,没有牢固掌握基础知识之前看这本,完全是浪费时间。入门阶段只要上述一本书即可,要仔细啃,你会发现自己每天在进步。
b.初识面向对象
面向对象是很老的东西了,而且直到今天,我一之觉得它被过度包装。说白了,就是在C上再抽象出一层广义的对象层,你可以理解为一种数据结构。很是厌恶用“封装,继承,多态”来概括这种特性,至少个人觉得有点这样形容有点繁琐,甚至是难以理解。我们应该从对象模型来理解。
封装:每个类只是各种变量和函数的集合体,只是加上了作用域,而且这些作用域依靠public,private,protected来实现accesslevel。
继承:比如A继承子B,那么A对象的空间内除了有自身的变量和函数外还有A的一切,没错,是一切包括private,当然一点需要指出函数并不是存储在对象内 的,它依靠对象地址+偏移+函数地址来实现调用(在这里,我没有区分inline与非inline)。
多态:用基类指针来访问子类的函数,首先要明白指针是怎么回事,关于指针的强转只是改变对象的读取方式,那多态来说,每个对象都有一个vptr,它是一 个三级指针,只想VirtualFunctionTable,此时对于对象指针的修改只是修改vptr能够在vtab内的偏移范围,至于vtab的内部实现,如果感兴趣可以看下 《深度探索C++对象模型》。
b.复用初瞥
我在这里主要说的是模板,模板有很多学问,我至今没有完全弄懂,尤其是涉及到模板类的多重继承性问题时,总是会有错误,不过之前好像发现有种情况肯定会出错。我想说的是尽量避免在使用模板类时使用多重继承。模板其实就是将类型模糊化,并且在之后声明时产生代码,替换原先的typename。
2.谈谈我在平时遇到的问题以及一些小窍门
a. accesslevel很多时候会失效,比如在使用virtual时,基类使用public,子类为private,那么使用多态时,private会失效,我不知道这是不是缺陷,但是可以看出一点,vtab对函数的调用与accesslevel无关。
b.如果你的类内部无任何变量时,那么sizeof(obj)=1,如果这个类是virtual基类,且它的子类都没有变量,那么就会很有趣了,我们假设这个基类为A;B1,B2:virtualpublic A;C:publicB1,B2
这是一个菱形的多重继承,此时cout<<sizeof(objA)<<sizeof(objB1)<<sizeof(objB2)<<sizeof(objC);结果为1448(G++4.3)。这就说明虚基类是通过指针实现的。
c.static内没有this,它并不是只能调用static成员的
d.protected的继承使用仅对继承类this有效,哪怕是this向上强转,那么protected也会变得像priate一样
-
浅谈C++
2016-08-01 20:31:49程序执行期间,从外设接收信息的操作称为“输入...C++中没有专门的输入输出语句,而是通过系统提供的输入输出流类来实现。 cin用来在程序执行期间给变量输入数据,一般格式为: cin>> [>> >>…>> ]; 其中:>> 称程序执行期间,从外设接收信息的操作称为“输入”,向外设发送信息的操作称为“输出”。
本节介绍从键盘向程序中的变量输入数据以及将程序计算的结果输出到显示器上的基本操作。C++输入输出简介
C++中没有专门的输入输出语句,而是通过系统提供的输入输出流类来实现。- cin用来在程序执行期间给变量输入数据,一般格式为:
cin>> <变量名1>[>> <变量名2> >>…>> <变量名n>];
其中:>> 称为“提取运算符”,程序执行到这条语句便暂停下来,等待从键盘上输入相应数据,直到所列出的所有变量均获得值后,程序方继续执行。 - cout实现将数据输出到显示器的操作,一般格式为:
cout<< <表达式1> [<< <表达式2> <<…<< <表达式n>];
其中:<< 称为“插入运算符”,将紧跟其后的表达式的值输出到显示器上当前光标位置。
cin和cout的书写形式很灵活,如果有多个变量,即使类型不相同,也可以写在同一个语句中或者分成若干个语句输入或输出。
注意,使用cin和cout必须在程序开头增加一行:
# include <iostream.h>
或者两行:
# include <iostream>
using namespace std;数据的输入输出
◆ 1、字符的输入输出
cin:用cin为字符变量输入数据时,输入的各字符之间可以间隔开也可以无间隔,系统会自动跳过输入行中的间隔符(包括空格符,制表符,回车符等)。例如:
char c1, c2, c3;则
cin>>c1; //A
cin>>c2>>c3;
执行过程为:
(1) 程序执行到A行时,等待用户输入数据;
(2) 若输入:Abc <CR> //CR代表回车符
(3) cin分别将字符A、b、c赋给变量c1、c2、c3。
若在第(2)步中输入:A b c <CR>,则结果一样。
cin.get():用于提取键盘上输入的所有字符(包括间隔符等),赋给字符变量。并且,cin.get()函数一次只能提取一个字符的值,其格式为:
cin.get (字符变量);
举例:设有定义char c1, c2, c3, c4;则下列语句的执行过程是:
cin.get (c1);
cin.get (c2);
cin.get (c3);
cin.get (c4);
程序执行过程中若输入:
A b<CR> //Ab间有空格
C<CR>
则字符′A′、空格、′b′、回车分别赋给变量c1、c2、c3、c4;输入缓冲区中保留字符′C′和回车符。
注意,cin与cin.get()是有区别的,cin自动跳过分隔符(如空格、回车);cin.get()不会。
字符输出:字符间无间隔,如需间隔,可在数据间插入间隔符。如′\t′(制表符,自动跳过若干字符位置)、′\n′或endl(表示回车换行,并清空缓冲区)等。
◆ 2、字符串的输入输出- 字符串的输入:用系统提供的函数cin.getline() ,将回车之前输入的所有字符都放入字符数组中,并以回车作为结束。
- 字符串的输出:可以直接输出串常量(常用于输出一些提示信息),也可以用字符数组名直接输出字符数组。
举例:
char city[11];
cin.getline(city, 10); //由键盘输入城市名
cout << “城市名:“ << city << endl;
其中:cin.getline() —— 第1个参数是已经定义的字符数组名,第2个参数是读入字符的最多个数(包括字符串结束符‘\0’)。
【执行结果】:从输入行中提取最多9个字符(由cin.getline()的第2个参数指明),再加上一个‘\0’字符,输入到字符数组city(由cin.getline()的第1个参数指明)。输出时,字符数组输出串结束符前的所有字符。
◆ 3、十进制数据的输入输出
请看下例:
int i, j;
float x, y;
cout<<”Input i, j, x, y:”<<endl; //D
cin>>i>>j; //E
cin>>x>>y; //要求输入十进制数据
cout<<"i="<<i<<'\t'<<"j="<<j<<endl;
cout<<"x+y="<<x+y<<endl;
程序执行到D行,将输出提示信息:
Input i, j, x, y:
并停留在E行,等待用户输入数据,若输入:
10 20<CR>
4.5 8.6 <CR>
则程序输出:
i=10 j=20x+y=13.1
C++ 条件运算符(三目运算符)
在某些情况下,可以用条件运算符“ ? : ”来简化if语句。基本格式
“ ? : ”是一个三元运算符,其构成的表达式格式为:
<表达式1> ? <表达式2> : <表达式3>执行流程
条件运算符的执行流程示意图如上图所示,描述如下:
(1) 计算表达式1的值;
(2) 若表达式的值为真(或非0),则只计算表达式2,并将其结果作为整个表达式的值;反之,即表达式1的值为假(或为0),则只计算表达式3,并将其结果作为整个表达式的值。应用举例
例如:
if (a>b) max=a;
else max=b;
可写成:
max=a>b?a:b;
又如:设有定义:int a=6,b=7, 则:
min = a<b?a:b; //min=6
min = a<b?++a:++b; //min=7 a=7 b=7
min = a<b?a++:b++; //min=6 a=7 b=7嵌套使用
条件运算符还可以嵌套使用,如求三个数中的最大值:
if (a>=b) t=a; else t=b;
if (t>=c) max=t; else max=c;
可改写成:
max =(t=a>=b?a:b)>= c ? t : c;
又如:
if (a>b) cout<<″greater than″;
else if (a==b) cout<<″equal to″;
else cout<<″less than″;
可改写成:
cout<<(a>b? "greater than":a==b?"equal to":"less than");
最后提醒:- 条件运算符“?:”只能在某些情况下简化if结构。
- 条件运算符嵌套使用时,要注意其结合性,即运算顺序,建议谨慎使用
- cin用来在程序执行期间给变量输入数据,一般格式为:
-
浅谈C++模板元编程
2020-08-28 12:03:21本篇文章主要介绍了浅谈C++模板元编程,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧 -
浅谈C++的浅拷贝出现的错误
2020-08-31 18:28:44下面小编就为大家带来一篇浅谈C++的浅拷贝出现的错误。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧 -
浅谈C++ 虚函数分析
2020-08-25 02:18:14主要介绍了浅谈C++ 虚函数分析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧 -
浅谈c++的编译和运行
2020-12-31 01:32:29命令行编译: ...以上这篇浅谈c++的编译和运行就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持软件开发网。 您可能感兴趣的文章:C/C++程序编译流程详解解析C语言与C++的编译模型 -
浅谈c++ hook 钩子的使用介绍
2020-08-28 20:42:06本篇文章主要介绍了浅谈c++ hook 钩子的使用介绍,详细的介绍了c++ hook 钩子的原理和运行机制,有兴趣的可以了解一下 -
浅谈c++中的输入输出方法
2020-09-02 05:36:35下面小编就为大家带来一篇浅谈c++中的输入输出方法。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧 -
浅谈C++指针(必看)
2020-09-02 04:34:07下面小编就为大家带来一篇浅谈C++指针(必看)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧 -
浅谈C++左值引用和右值引用
2020-08-31 17:26:48下面小编就为大家带来一篇浅谈C++左值引用和右值引用。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧 -
浅谈C++继承中的名字查找
2020-08-31 17:25:40下面小编就为大家带来一篇浅谈C++继承中的名字查找。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧 -
浅谈C++如何求等差素数列
2020-08-18 17:41:53主要介绍了浅谈C++如何求等差素数列,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧 -
浅谈C++中虚函数实现原理揭秘
2020-09-02 06:24:56下面小编就为大家带来一篇浅谈C++中虚函数实现原理揭秘。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧 -
浅谈C++中的mutable和volatile关键字
2020-09-02 05:52:45下面小编就为大家带来一篇浅谈C++中的mutable和volatile关键字。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧 -
浅谈C++重载、重写、重定义
2020-09-02 05:21:57下面小编就为大家带来一篇浅谈C++重载、重写、重定义。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧 -
浅谈C++中的多线程(一)
2019-03-23 23:11:23本篇文章围绕以下几个问题展开: ...传统的C++(C++98)中并没有引入线程这个概念。linux和unix操作系统的设计采用的是多进程,进程间的通信十分方便,同时进程之间互相有着独立的空间,不会污染其他进程的数...本篇文章围绕以下几个问题展开:
- 何为进程?何为线程?两者有何区别?
- 何为并发?C++中如何解决并发问题?C++中多线程的语言实现?
- 同步互斥原理以及多进程和多线程中实现同步互斥的两种方法
- Qt中的多线程应用
引入
传统的C++(C++98)中并没有引入线程这个概念。linux和unix操作系统的设计采用的是多进程,进程间的通信十分方便,同时进程之间互相有着独立的空间,不会污染其他进程的数据,天然的隔离性给程序的稳定性带来了很大的保障。而线程一直都不是linux和unix推崇的技术,甚至有传言说linus本人就非常不喜欢线程的概念。随着C++市场份额被Java、Python等语言所蚕食,为了使得C++更符合现代语言的特性,在C++11中引入了多线程与并发技术。
一.何为进程?何为线程?两者有何区别?
1.何为进程?
进程是一个应用程序被操作系统拉起来加载到内存之后从开始执行到执行结束的这样一个过程。简单来说,进程是程序(应用程序,可执行文件)的一次执行。进程通常由程序、数据和进程控制块(PCB)组成。比如双击打开一个桌面应用软件就是开启了一个进程。
传统的进程有两个基本属性:可拥有资源的独立单位;可独立调度和分配的基本单位。对于这句话我的理解是:进程可以获取操作系统分配的资源,如内存等;进程可以参与操作系统的调度,参与CPU的竞争,得到分配的时间片,获得处理机(CPU)运行。
进程在创建、撤销和切换中,系统必须为之付出较大的时空开销,因此在系统中开启的进程数不宜过多。比如你同时打开十几个应用软件试试,电脑肯定会卡死的。于是紧接着就引入了线程的概念。
2.何为线程?
线程是进程中的一个实体,是被系统独立分配和调度的基本单位。也有说,线程是CPU可执行调度的最小单位。也就是说,进程本身并不能获取CPU时间,只有它的线程才可以。
引入线程之后,将传统进程的两个基本属性分开了,线程作为调度和分配的基本单位,进程作为独立分配资源的单位。我对这句话的理解是:线程参与操作系统的调度,参与CPU的竞争,得到分配的时间片,获得处理机(CPU)运行。而进程负责获取操作系统分配的资源,如内存。
线程基本上不拥有资源,只拥有一点运行中必不可少的资源,它可与同属一个进程的其他线程共享进程所拥有的全部资源。
线程具有许多传统进程所具有的特性,故称为“轻量型进程”。同一个进程中的多个线程可以并发执行。
3.进程和线程的区别?
其实根据进程和线程的定义已经能区分开它们了。
线程分为用户级线程和内核支持线程两类,用户级线程不依赖于内核,该类线程的创建、撤销和切换都不利用系统调用来实现;内核支持线程依赖于内核,即无论是在用户进程中的线程,还是在系统中的线程,它们的创建、撤销和切换都利用系统调用来实现。
但是,与线程不同的是,无论是系统进程还是用户进程,在进行切换时,都要依赖于内核中的进程调度。因此,无论是什么进程都是与内核有关的,是在内核支持下进程切换的。尽管线程和进程表面上看起来相似,但是他们在本质上是不同的。
根据操作系统中的知识,进程至少必须有一个线程,通常将此线程称为主线程。
进程要独立的占用系统资源(如内存),而同一进程的线程之间是共享资源的。进程本身并不能获取CPU时间,只有它的线程才可以。
4.其他
进程在创建、撤销和切换过程中,系统的时空开销非常大。用户可以通过创建线程来完成任务,以减少程序并发执行时付出的时空开销。例如可以在一个进程中设置多个线程,当一个线程受阻时,第二个线程可以继续运行,当第二个线程受阻时,第三个线程可以继续运行......。这样,对于拥有资源的基本单位(进程),不用频繁的切换,进一步提高了系统中各种程序的并发程度。
在一个应用程序(进程)中同时执行多个小的部分,这就是多线程。这小小的部分虽然共享一样的数据,但是却做着不同的任务。
二.何为并发?C++中如何解决并发问题?C++中多线程的语言实现?
1.何为并发?
1.1.并发
在同一个时间里CPU同时执行两条或多条命令,这就是所谓的并发。
1.2.伪并发
伪并发是一种看似并发的假象。我们知道,每个应用程序是由若干条指令组成的。在现代计算机中,不可能一次只跑一个应用程序的命令,CPU会以极快的速度不停的切换不同应用程序的命令,而让我们看起来感觉计算机在同时执行很多个应用程序。比如,一边听歌,一边聊天,还能同时打游戏,我们误以为这是并发,其实只是一种伪并发的假象。
主要,以前的计算机都是单核CPU,就不太可能实现真正的并发,只能是不同的线程占用不同的时间片,而CPU在各个线程之间来回快速的切换。
伪并发的模型大致如下:
整个框代表一个CPU的运行,T1和T2代表两个不同的线程,在执行期间,不同的线程分别占用不同的时间片,然后由操作系统负责调度执行不同的线程。但是很明显,由于内存、寄存器等等都是有限的,所以在执行下一个线程的时候不得不把上一个线程的一些数据先保存起来,这样下一次执行该线程的时候才能继续正确的执行。
这样多线程的好处就是更大的利用CPU的空闲时间,而缺点就是要付出一些其他的代价,所以多线程是否一定要单线程快呢?答案是否定的。这个道理就像,如果有3个程序员同时编写一个项目,不可避免需要相互的交流,如果这个交流的时间远远大于编码的时间,那么抛开代码质量来说,可能还不如一个程序猿来的快。
理想的并发模型如下:
可以看出,这是真正的并发,真正实现了时间效率上的提高。因为每一个框代表一个CPU的运行,所以真正实现并发的物理基础的多核CPU。
1.3.并发的物理基础
慢慢的,发展出了多核CPU,这样就为实现真并发提供了物理基础。但这仅仅是硬件层面提供了并发的机会,还需要得到语言的支持。像C++11之前缺乏对于多线程的支持,所写的并发程序也仅仅是伪并发。
也就是说,并发的实现必须首先得到硬件层面的支持,不过现在的计算机已经是多核CPU了,我们对于并发的研究更多的是语言层面和软件层面了。
2.C++中如何解决并发问题?
显然通过多进程来实现并发是不可靠的,C++中采用多线程实现并发。
线程算是一个底层的,传统的并发实现方法。C++11中除了提供thread库,还提供了一套更加好用的封装好了的并发编程方法。
C++中更高端的并发方法:(此内容因本人暂未理解,暂时搁置,待理解之时会前来更新,请读者朋友谅解)
3.C++中多线程的语言实现?
这里以一个典型的示例——求和函数来讲解C++中的多线程。
单线程版:
#include <iostream> #include <vector> #include <algorithm> using namespace std; int GetSum(vector<int>::iterator first,vector<int>::iterator last) { return accumulate(first,last,0);//调用C++标准库算法 } int main() { vector<int> largeArrays; for(int i=0;i<100000000;i++) { if(i%2==0) largeArrays.push_back(i); else largeArrays.push_back(-1*i); } int res = GetSum(largeArrays.begin(),largeArrays.end()); return 0; }
多线程版:
#include <iostream> #include <vector> #include <algorithm> #include <thread> using namespace std; //线程要做的事情就写在这个线程函数中 void GetSumT(vector<int>::iterator first,vector<int>::iterator last,int &result) { result = accumulate(first,last,0); //调用C++标准库算法 } int main() //主线程 { int result1,result2,result3,result4,result5; vector<int> largeArrays; for(int i=0;i<100000000;i++) { if(i%2==0) largeArrays.push_back(i); else largeArrays.push_back(-1*i); } thread first(GetSumT,largeArrays.begin(), largeArrays.begin()+20000000,std::ref(result1)); //子线程1 thread second(GetSumT,largeArrays.begin()+20000000, largeArrays.begin()+40000000,std::ref(result2)); //子线程2 thread third(GetSumT,largeArrays.begin()+40000000, largeArrays.begin()+60000000,std::ref(result3)); //子线程3 thread fouth(GetSumT,largeArrays.begin()+60000000, largeArrays.begin()+80000000,std::ref(result4)); //子线程4 thread fifth(GetSumT,largeArrays.begin()+80000000, largeArrays.end(),std::ref(result5)); //子线程5 first.join(); //主线程要等待子线程执行完毕 second.join(); third.join(); fouth.join(); fifth.join(); int resultSum = result1+result2+result3+result4+result5; //汇总各个子线程的结果 return 0; }
C++11中引入了多线程技术,通过thread线程类对象来管理线程,只需要#include <thread>即可。thread类对象的创建意味着一个线程的开始。
thread first(线程函数名,参数1,参数2,......);每个线程有一个线程函数,线程要做的事情就写在线程函数中。
根据操作系统上的知识,一个进程至少要有一个线程,在C++中可以认为main函数就是这个至少的线程,我们称之为主线程。而在创建thread对象的时候,就是在这个线程之外创建了一个独立的子线程。这里的独立是真正的独立,只要创建了这个子线程并且开始运行了,主线程就完全和它没有关系了,不知道CPU会什么时候调度它运行,什么时候结束运行,一切都是独立,自由而未知的。
因此下面要讲两个必要的函数:join()和detach()
如:thread first(GetSumT,largeArrays.begin(),largeArrays.begin()+20000000,std::ref(result1)); first.join();
这意味着主线程和子线程之间是同步的关系,即主线程要等待子线程执行完毕才会继续向下执行,join()是一个阻塞函数。
而first.detach(),当然上面示例中并没有应用到,则表示主线程不用等待子线程执行完毕,两者脱离关系,完全放飞自我。这个一般用在守护线程上:有时候我们需要建立一个暗中观察的线程,默默查询程序的某种状态,这种的称为守护线程。这种线程会在主线程销毁之后自动销毁。
C++中一个标准线程函数只能返回void,因此需要从线程中返回值往往采用传递引用的方法。我们讲,传递引用相当于扩充了变量的作用域。
我们为什么需要多线程,因为我们希望能够把一个任务分解成很多小的部分,各个小部分能够同时执行,而不是只能顺序的执行,以达到节省时间的目的。对于求和,把所有数据一起相加和分段求和再相加没什么区别。
PS:因篇幅限制,本篇文章先写到这里,余下内容会在下一篇文章中讲解。谢谢读者朋友们的阅读,欢迎提出宝贵意见,期待与您交流!
-
浅谈c++如何实现并发中的Barrier
2020-08-18 19:41:44主要介绍了浅谈c++如何实现并发中的Barrier,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧 -
浅谈C++中virtual的三种用法
2020-08-18 18:48:47主要介绍了浅谈C++中virtual的三种用法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧 -
浅谈c++ stl迭代器失效的问题
2020-08-31 21:57:10下面小编就为大家带来一篇浅谈c++ stl迭代器失效的问题。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧,祝大家游戏愉快哦 -
浅谈C++多态性
2019-02-10 18:19:29浅谈C++多态性C++编程语言是一款应用广泛,支持多种程序设计的计算机编程语言。我们今天就会为大家详细介绍其中C++多态性的一些基本知识,以方便大家在学习过程中对此能够有一个充分的掌握。
多态性可以简单地概括为“一个接口,多种方法”,程序在运行时才决定调用的函数,它是面向对象编程领域的核心概念。多态(polymorphism),字面意思多种形状。
C++多态性是通过虚函数来实现的,虚函数允许子类重新定义成员函数,而子类重新定义父类的做法称为覆盖(override),或者称为重写。(这里我觉得要补充,重写的话可以有两种,直接重写成员函数和重写虚函数,只有重写了虚函数的才能算作是体现了C++多态性)而重载则是允许有多个同名的函数,而这些函数的参数列表不同,允许参数个数不同,参数类型不同,或者两者都不同。编译器会根据这些函数的不同列表,将同名的函数的名称做修饰,从而生成一些不同名称的预处理函数,来实现同名函数调用时的重载问题。但这并没有体现多态性。
多态与非多态的实质区别就是函数地址是早绑定还是晚绑定。如果函数的调用,在编译器编译期间就可以确定函数的调用地址,并生产代码,是静态的,就是说地址是早绑定的。而如果函数调用的地址不能在编译器期间确定,需要在运行时才确定,这就属于晚绑定。
那么多态的作用是什么呢,封装可以使得代码模块化,继承可以扩展已存在的代码,他们的目的都是为了代码重用。而多态的目的则是为了接口重用。也就是说,不论传递过来的究竟是那个类的对象,函数都能够通过同一个接口调用到适应各自对象的实现方法。最常见的用法就是声明基类的指针,利用该指针指向任意一个子类对象,调用相应的虚函数,可以根据指向的子类的不同而实现不同的方法。如果没有使用虚函数的话,即没有利用C++多态性,则利用基类指针调用相应的函数的时候,将总被限制在基类函数本身,而无法调用到子类中被重写过的函数。因为没有多态性,函数调用的地址将是一定的,而固定的地址将始终调用到同一个函数,这就无法实现一个接口,多种方法的目的了。
笔试题目:
第一个p->foo()和p->fuu()都很好理解,本身是基类指针,指向的又是基类对象,调用的都是基类本身的函数,因此输出结果就是1、2。#include<iostream>using namespace std;class A{public: void foo() { printf("1\n"); } virtual void fun() { printf("2\n"); }};class B : public A{public: void foo() { printf("3\n"); } void fun() { printf("4\n"); }};int main(void){ A a; B b; A *p = &a; p->foo(); p->fun(); p = &b; p->foo(); p->fun(); return 0;}
第二个输出结果就是1、4。p->foo()和p->fuu()则是基类指针指向子类对象,正式体现多态的用法,p->foo()由于指针是个基类指针,指向是一个固定偏移量的函数,因此此时指向的就只能是基类的foo()函数的代码了,因此输出的结果还是1。而p->fun()指针是基类指针,指向的fun是一个虚函数,由于每个虚函数都有一个虚函数列表,此时p调用fun()并不是直接调用函数,而是通过虚函数列表找到相应的函数的地址,因此根据指向的对象不同,函数地址也将不同,这里将找到对应的子类的fun()函数的地址,因此输出的结果也会是子类的结果4。
笔试的题目中还有一个另类测试方法。即
B *ptr = (B *)&a; ptr->foo(); ptr->fun();
问这两调用的输出结果。这是一个用子类的指针去指向一个强制转换为子类地址的基类对象。结果,这两句调用的输出结果是3,2。
并不是很理解这种用法,从原理上来解释,由于B是子类指针,虽然被赋予了基类对象地址,但是ptr->foo()在调用的时候,由于地址偏移量固定,偏移量是子类对象的偏移量,于是即使在指向了一个基类对象的情况下,还是调用到了子类的函数,虽然可能从始到终都没有子类对象的实例化出现。
而ptr->fun()的调用,可能还是因为C++多态性的原因,由于指向的是一个基类对象,通过虚函数列表的引用,找到了基类中fun()函数的地址,因此调用了基类的函数。由此可见多态性的强大,可以适应各种变化,不论指针是基类的还是子类的,都能找到正确的实现方法。
令人迷惑的隐藏规则//小结:1、有virtual才可能发生多态现象// 2、不发生多态(无virtual)调用就按原类型调用#include<iostream>using namespace std;class Base{public: virtual void f(float x) { cout<<"Base::f(float)"<< x <<endl; } void g(float x) { cout<<"Base::g(float)"<< x <<endl; } void h(float x) { cout<<"Base::h(float)"<< x <<endl; }};class Derived : public Base{public: virtual void f(float x) { cout<<"Derived::f(float)"<< x <<endl; //多态、覆盖 } void g(int x) { cout<<"Derived::g(int)"<< x <<endl; //隐藏 } void h(float x) { cout<<"Derived::h(float)"<< x <<endl; //隐藏 }};int main(void){ Derived d; Base *pb = &d; Derived *pd = &d; // Good : behavior depends solely on type of the object pb->f(3.14f); // Derived::f(float) 3.14 pd->f(3.14f); // Derived::f(float) 3.14 // Bad : behavior depends on type of the pointer pb->g(3.14f); // Base::g(float) 3.14 pd->g(3.14f); // Derived::g(int) 3 // Bad : behavior depends on type of the pointer pb->h(3.14f); // Base::h(float) 3.14 pd->h(3.14f); // Derived::h(float) 3.14 return 0;}
本来仅仅区别重载与覆盖并不算困难,但是C++的隐藏规则使问题复杂性陡然增加。
这里“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
(1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual
关键字,基类的函数将被隐藏(注意别与重载混淆)。
(2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual
关键字。此时,基类的函数被隐藏(注意别与覆盖混淆)。
上面的程序中:
(1)函数Derived::f(float)覆盖了Base::f(float)。
(2)函数Derived::g(int)隐藏了Base::g(float),而不是重载。
(3)函数Derived::h(float)隐藏了Base::h(float),而不是覆盖。
C++纯虚函数
一、定义
纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加“=0”
virtual void funtion()=0
二、引入原因
1、为了方便使用多态特性,我们常常需要在基类中定义虚拟函数。
2、在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。
为了解决上述问题,引入了纯虚函数的概念,将函数定义为纯虚函数(方法:virtual ReturnType Function()= 0;),则编译器要求在派生类中必须予以重写以实现多态性。同时含有纯虚拟函数的类称为抽象类,它不能生成对象。这样就很好地解决了上述两个问题。
三、相似概念
1、多态性
指相同对象收到不同消息或不同对象收到相同消息时产生不同的实现动作。C++支持两种多态性:编译时多态性,运行时多态性。
a、编译时多态性:通过重载函数实现
b、运行时多态性:通过虚函数实现。
2、虚函数
虚函数是在基类中被声明为virtual,并在派生类中重新定义的成员函数,可实现成员函数的动态覆盖(Override)
3、抽象类
包含纯虚函数的类称为抽象类。由于抽象类包含了没有定义的纯虚函数,所以不能定义抽象类的对象。
再分享一下我老师大神的人工智能教程吧。零基础!通俗易懂!风趣幽默!还带黄段子!希望你也加入到我们人工智能的队伍中来!https://blog.csdn.net/jiangjunshow
-
浅谈C++的语句语法与强制数据类型转换
2020-09-03 09:19:25主要介绍了浅谈C++的语句语法与强制数据类型转换,是C++入门学习中的基础知识,需要的朋友可以参考下 -
浅谈C++的几种从键盘输入方式
2020-08-27 05:08:46今天小编就为大家分享一篇浅谈C++的几种从键盘输入方式,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧 -
浅谈 C++ 元编程
2020-06-30 16:09:08浅谈 C++ 元编程 2017/5/2 -> 2020/1/20 原文为 2017 程序设计语言结课论文,现已根据 C++ 17 标准更新。 摘要 随着 C++ 11/14/17 标准的不断更新,C++ 语言得到了极大的完善和补充。元编程作为一种新兴的编程... -
浅谈C++中派生类对象的内存布局
2020-08-31 21:40:57下面小编就为大家带来一篇浅谈C++中派生类对象的内存布局。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧 -
浅谈C++ 类的实例中 内存分配详解
2020-08-31 20:21:51下面小编就为大家带来一篇浅谈C++ 类的实例中 内存分配详解。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧 -
浅谈C++ Explicit Constructors(显式构造函数)
2020-08-31 19:33:15下面小编就为大家带来一篇浅谈C++ Explicit Constructors(显式构造函数)。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧 -
浅谈c++中的while(cin)问题
2020-08-30 15:55:35下面小编就为大家带来一篇浅谈c++中的while(cin)问题。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧 -
浅谈C++中char型变量的地址输出
2020-08-29 06:33:34下面小编就为大家带来一篇浅谈C++中char 型变量的地址输出。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧