由endl对printf和cout的思考

摘要:
所以,c++里面的缓冲区意义是非常大的,注意printf是没有缓冲区的。有人做的测评:https://blog.csdn.net/eternity666/article/details/78001513在vs下cout是对printf的封装,还要保持同步。典型地我们会举下面这个例子:classAnimal{public:virtualvoidshout()=0;};classDog:publicAnimal{public:virtualvoidshout(){cout˂˂"汪汪!我们将上面的例子改写为:classAnimal{public:voidshout(){cout˂˂"发出动物的叫声"˂˂endl;};};classDog{public:voidshout(){cout˂˂"汪汪!

【前言】二者的区别就不介绍了。二者使用方法:

printf("%s",a);

cout<<a<<endl;

endl的作用是什么?

一、endl作用

众所周知,endl有一个换行的作用,第二个作用就是清空缓冲区buffer。

为什么要清空缓冲区呢?

首先思考缓冲区存在的作用,缓冲区的作用一是为了避免频繁的I/O操作对磁盘的损耗,二是减少存取时的函数调用的损耗。所以,c++里面的缓冲区意义是非常大的,注意printf是没有缓冲区的。

我们上面即使没有加上endl,输出也会马上输出出来,那清空缓冲区的作用在哪呢?其实,我们这个程序太简单,不能体现。但是cout什么时候清空缓冲区我们是不确定的,这对于多线程来说增加了不可控,多线程只有栈区独享,所有线程共享原主进程的缓冲区,如果不及时清空缓冲区可能会造成串数据;cin更是从缓冲区读取数据,其中很多细节。

二、cout和printf时间复杂度比较

cout是对"<<”运算符的重载。其运行时间也慢于printf。有人做的测评:https://blog.csdn.net/eternity666/article/details/78001513

在vs下cout是对printf的封装,还要保持同步。但是在g++下不是,有人做的测试,g++的cout速度反而比printf快10倍左右。https://blog.csdn.net/qq_26398495/article/details/55105094

所以,在linux端编程,用cout就可以,没必要用printf.,而且更加明晰。

三、printf怎么实现“C多态”?

今天有个同学考我一个题目,很有意思。我们知道printf的参数列表,参数个数是不确定的,这就很类似C++里面的多态,那printf是怎么实现C“多态”的呢?

我第一次思考这个问题,就先写下当时怎么思考的吧?当然,他的问题是没有回答上来。

  T1、C++里面的多态是怎么实现的?

C++是静态编译型语言,所谓的静态联编,但是使用虚函数即可实现动态联编,类似动态链接库。实现方法就是编译器提前生成vptr指针,运行的时候在通过指针找到虚函数表,找到函数入口地址。从指针的角度,好像和C多态有点关系,用函数指针?

顺着这个思路,我们知道,多态有编译期多态和运行期多态。编译期->重载,运行期->重写。

  T2、重载重写(重定义?)三者的区别 
(1)成员函数重载特征:
相同的范围(在同一个类中);
函数名字相同
参数不同;
virtual关键字可有可无;
(2) 重写(覆盖)是指派生类函数覆盖基类virtual函数,特征是:
不同的范围,分别位于基类和派生类中;
函数的名字相同;
参数相同;
基类函数必须有virtual关键字;
(3)重定义(隐藏)是指派生类的函数屏蔽了与其同名的基类函数,规则如下:
如果派生类的函数和基类的函数同名,但是参数不同,此时,不管有无virtual,基类的函数被隐藏;
如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有vitual关键字,此时,基类的函数被隐藏。
(条款36:不应该重定义任何非虚函数,既然需要你重定义,那为什么不直接在基类定义虚函数?非虚函数就是表明此函数具有“不变性凌驾特异性”,所以尽量不要重定义)

  T3、C++里面的运行期和编译期多态

(1)运行期多态(重写)

运行期多态的设计思想要归结到类继承体系的设计上去。对于有相关功能的对象集合,我们总希望能够抽象出它们共有的功能集合,在基类中将这些功能声明为虚接口(虚函数),然后由子类继承基类去重写这些虚接口,以实现子类特有的具体功能。典型地我们会举下面这个例子:

class Animal
{
    public :
    virtual void shout() = 0;
};
class Dog :public Animal
{
    public:
    virtual void shout(){ cout << "汪汪!"<<endl; }
};
class Cat :public Animal
{
    public:
    virtual void shout(){ cout << "喵喵~"<<endl; }
};
class Bird : public Animal
{
    public:
    virtual void shout(){ cout << "叽喳!"<<endl; }
};

int main(){
    Animal * anim1 = new Dog;
    Animal * anim2 = new Cat;
    Animal * anim3 = new Bird;
    //藉由指针(或引用)调用的接口,在运行期确定指针(或引用)所指对象的真正类型,调用该类型对应的接口
    anim1->shout();
    anim2->shout();
    anim3->shout();
    //delete 对象
    ...
    return 0;
}                

运行期多态的实现依赖于虚函数机制。当某个类声明了虚函数时,编译器将为该类对象安插一个虚函数表指针,并为该类设置一张唯一的虚函数表,虚函数表中存放的是该类虚函数地址。运行期间通过虚函数表指针与虚函数表去确定该类虚函数的真正实现。

总结:运行期多态通过虚函数发生于运行期

(2)编译期多态(重载)

对模板参数而言,多态是通过模板具现化和函数重载解析实现的。以不同的模板参数具现化导致调用不同的函数,这就是所谓的编译期多态。相比较于运行期多态,实现编译期多态的类之间并不需要成为一个继承体系,它们之间可以没有什么关系,但约束是它们都有相同的隐式接口。我们将上面的例子改写为:

class Animal
{
    public :
    void shout() { cout << "发出动物的叫声" << endl; };
};
class Dog
{
    public:
    void shout(){ cout << "汪汪!"<<endl; }
};
class Cat
{
    public:
    void shout(){ cout << "喵喵~"<<endl; }
};
class Bird
{
    public:
    void shout(){ cout << "叽喳!"<<endl; }
};
template <typename T>
void animalShout(T & t){
    t.shout();
    }
int main(){
    Animal anim;
    Dog dog;
    Cat cat;
    Bird bird;
    animalShout(anim);
    animalShout(dog);
    animalShout(cat);
    animalShout(bird);
    getchar();
}

在编译之前,函数模板中t.shout()调用的是哪个接口并不确定。在编译期间,编译器推断出模板参数,因此确定调用的shout是哪个具体类型的接口。不同的推断结果调用不同的函数,这就是编译器多态。这类似于重载函数在编译器进行推导,以确定哪一个函数被调用。

(3)二者的区别:超链接

  T3、那么C实现“多态”是和C++ 类似的吗?

(1)重载实现(编译期)

看printf的源码:

1 #include<stdio.h>
2 #include<stdarg.h>
3  
4  
5 //va_start(arg,format),初始化参数指针arg,将函数参数format 右边第一个参数地址赋值给arg
6 //format必须是一个参数的指针,所以,此种类型函数至少要有一个普通的参数,
7 //从而提供给va_start,这样va_start才能找到可变参数在栈上的位置。
8 //va_arg(arg,char),获得arg指向参数的值,同时使arg指向下一个参数,char用来指名当前参数型
9 //va_end在有些实现中可能会把arg改成无效值,这里,是把arg指针指向了NULL,避免出现野指针
10  
11  
12 voidprintf(constchar*format,...)
13 {
14 va_listarg;
15 va_start(arg,format);
16  
17     while(*format)
18 {
19         charret=*format;
20         if(ret=='%')
21 {
22             switch(*++format)
23 {
24             case'c':
25 {
26                         charch=va_arg(arg,char);
27 putchar(ch);
28                         break;
29 }
30             case's':
31 {
32                         char*pc=va_arg(arg,char*);
33                         while(*pc)
34 {
35                             putchar(*pc);
36                             pc++;
37 }
38                         break;
39 }
40             default:
41                 break;
42 }
43 }
44         else
45 {
46             putchar(*format);
47 }
48         format++;
49 }
50 va_end(arg);
51 }
52 intmain()
53 {
54     printf("%s%s%c%c%c%c%c!
","welcome","to",'C','h','i','n','a');
printf("%s %c","abc","c"
);//重载了使用
55     system("pause");
56     return0;
57 }

void printf(const char *format,...);。注意到"..."这个可变参东西了吧,printf的“...”内部实际上是通过 __VA_ARGS__ 这个宏来实现的:链接。他有三个参数,具体可参考:这里

(2)重写(运行期)

这个就要通过函数指针了吧:https://blog.csdn.net/philip_puma/article/details/25973139

免责声明:文章转载自《由endl对printf和cout的思考》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇asp.net获取客户端的MAC地址ASP.NET Core文件压缩最佳实践下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

Awk命令

一. AWK入门指南 Awk是一种便于使用且表达能力强的程序设计语言,可应用于各种计算和数据处理任务。本章是个入门指南,让你能够尽快地开始编写你自己的程序。第二章将描述整个语言,而剩下的章节将向你展示如何使用Awk来解决许多不同方面的问题。纵观全书,我们尽量选择了一些对你有用、有趣并且有指导意义的实例。 1.1 起步 有用的awk程序往往很简短,仅仅一两...

终于理解二级指针的作用了

之前学习swap函数时,知道传递指针可以实现对要交换变量本尊的修改,而直接传递值做不到这一点.究其原因,是因为函数传递参数时是以拷贝的形式,因此函数内部对其拷贝进行操作,不会影响到本尊. 如果想要通过函数实现对一级指针的值进行修改该如何去做呢?如果直接把它传进去,其实修改的是它的拷贝,而对它并没有影响.这个时候就是二级指针出场的时候了. #include...

zbar配置

1、下载安装zbar,选择full全部安装。也可以从这里下载。  2、由于官方给的是32位的,继续下载64位的zbar 替换文件: 将ZBarWin64-master\lib 文件夹下 libzbar64-0.lib 文件复制到ZBar安装路径下的 lib 文件夹; 将 ZBarWin64-master\lib 文件夹下 libzbar64-0.dl...

Linux 系统编程 学习:3-进程间通信1:Unix IPC(2)信号

背景 上一讲我们介绍了Unix IPC中的2种管道。 回顾一下上一讲的介绍,IPC的方式通常有: Unix IPC包括:管道(pipe)、命名管道(FIFO)与信号(Signal) System V IPC:消息队列、信号量、共享内存 Socket(支持不同主机上的两个进程IPC) 我们在这一讲介绍Unix IPC,中有关信号(Signal)的处理。...

C语言基础:C语言指针(6)

上一节我们讲到了指针和数组, 这次我们来讲解一下指针和字符串, 这次的内容和上一节有相似的地方, 也有全新学习的地方, 让我们一起来看看吧~~下面我们来看一个小例子: #include <stdio.h> int main() { char name[] = "abcde"; name[0] = 'A';...

TLV(类型—长度—值)格式及编码

转自:http://www.cnblogs.com/tml839720759/archive/2014/07/13/3841820.html 引子:   前段时间在项目中第一次接触TLV,项目中用这种格式来传输图片,语音等。 关于TLV   TLV是一种可变的格式,意为:Type类型, Lenght长度,Value值。Type:该字段是关于标签和编码格式的...