C++的前世今生

开始学习C++

1、C++由来

1979 年,美国 AT&T 公司贝尔实验室的 Bjarne Stroustrup 博士在 C 语言的基础上引入并扩充了面向对象的概念,发明了一种新的程序语言。为了表达该语言与 C 语言的渊源关系,它被命名为 C++。

C++是一种面向对象编程(OOP)的语言。C++是在 C 的基础之上添加面向对象的功能,最初这门语言并不叫 C++而是 C with classes (带类的 C)。 更多C++的历史这个网站讲的更多http://www.cplusplus.com/info/history/

C++的应用很多,比如我们平时用的QQ、英雄联盟、魔兽世界、Windows桌面、KDE桌面、GNOME桌面、谷歌浏览器、vmware虚拟机、WPS、Photoshop、360等软件都是C++开发出来的。

2、从C到C++

从某种意义上说C++是C语言的超集,但是毕竟是C++,对C语言中的一些东西,并不是兼容的,有一些不一样的东西。

C++的类型检查更严格

在C语言中我们知道void*类型,可以转换成任何类型,const也可以转化成非const类型的。但是在C++中是不行的,会直接报错,当然在C++中也可以像C语言中那样使用强制类型转化。C++中有自己实现的强制类型转换的方法,我们后续再说。

举个例子:

#include <stdio.h>
#include <stdlib.h>
int main()
{
    const int a = 10;
    int *pa = &a;          //error
    
    char *p = malloc(100); //error
    
    int arr[2][3];
    int **pArr = arr;      //error 把int(*p)[3]类型赋值给了int**
    return 0;
}

这段程序在C语言中就报一个warning,而在C++中都是error

C++中新增了bool类型

C语言中没有这个类型,C++中新增加了这个类型,可以用true和false进行赋值,其本质就是一个字节的char类型变量。除了用true和false进行赋值以外,也可以用别的值进行赋值,不过从我测试来看,只有用0和false进行赋值的时候打印为0 ,其余的都会打印出1。

C++中的枚举

C 语言中枚举元素类型本质就是整型, 枚举变量可以用任意整型赋值。 而 C++中枚举变量, 只能用被枚举出来的元素赋值。 否则报错。

C++中的结构体

在C语言中定义了一个结构体,当想要定义一个该结构体类型的变量的时候,必须写成struct type xxx;而在C++中只需要写type xxx;即可,可以省略struct

在C++中的结构体中可以放函数作为成员,而C语言中是不行的。

C++中部分表达式可以赋值

C 语言中表达式通常不能作为左值的,即不可被赋值,C++中某些表达式是可以赋值的。

int a,b = 5;
(a = b) = 10; //C++中允许,C语言中不可以
cout<<"a:"<<a<<endl<<"b:"<<b<<endl; //打印结果a:10  b:4354270

在 C 语言中(a = b) = 10;(a<b? a:b) = 200; 这种语句是完全编译不过的, 但在 C++中编译是可以通过的。 当然, 此现象涉及到表达的返回引用的问题, 后面的知识点会讲到, 这里并不作展开

NULL 、nullptr、0

实际上在C语言中,NULL通常被定义为如下:

#define NULL ((void *)0)

因为C++中不能将void *类型的指针隐式转换成其他指针类型,而又为了解决空指针的问题,所以C++中一直0来表示空指针。但是到了C++11的时候,C++中引入了nullptr来代替上面两个,所以现在都用nullptr。

3、C++中的输入输出cin、cout详解

cin 和 cout 是 C++的标准输入和输出流对象。 他们在头文件 iostream 中定义, 其意义作用类似于 C 语言中的 scanf 和 printf。

cin输入字符串的时候遇到空格会自动折断,不会显示空格之后的字符

cout进制输出

cout 中引用了流算子(dec/hex/oct)的概念, 可以更方便的使用进制输出

int main()
{
    int i = 123;
    cout<<i<<endl;
    cout<<"dec:"<<dec<<i<<endl; //10 进制输出
    cout<<"hex:"<<hex<<i<<endl; //16 进制输出
        cout<<i<<endl;              //此时打印内容和上面的16进制一样
    cout<<"oct:"<<oct<<i<<endl; // 8 进制输出
    return 0;
}

cout设置域宽、对齐、填充

控制域宽、对齐、填充的流算子,分别是 setiosflags(ios::left),setw(10),setfill('0'),使用这几个需要包含头文件iomanip

#include<iostream>
#include<iomanip>

using namespace std;

int main()
{
    cout<<setw(10)<<1234<<endl;//设置域宽
    cout<<setw(10)<<setfill('0')<<1234<<endl;//设置填充0
    cout<<setw(10)<<setfill('0')<<setiosflags(ios::left)<<1234<<endl;//设置左对齐
    cout<<setw(10)<<setfill('-')<<setiosflags(ios::right)<<1234<<endl;//设置右对齐
    return 0;
}

cout设置浮点数精度

采用流算子setprecision 和 setiosflags 即可以实现输出浮点数的精度。使用这几个需要包含头文件iomanip

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

int main()
{
    float f = 1.234;
    cout<<"default :"<<f<<endl;
    cout<<"format:"<<setprecision(2)<<setiosflags(ios::fixed)<<f<<endl;//保留两位小数
    cout<<"format:"<<setprecision(4)<<setiosflags(ios::fixed)<<f<<endl;//保留四位小数
    return 0;
}

4、函数重载(Function Overload)

大家都知道在C语言中是不可能存在函数名一模一样的函数的,但是在C++中在某些情况却是可以的,相同的函数名之间的函数就构成了函数重载。

重载的要求如下:

1、函数名相同

2、参数个数不同、或者参数的类型不同、或者参数顺序不同

3、返回值类型,不作为重载的标准

相同函数名的函数,在调用的时候会根据语境自动选择相应的函数,所以这一点不用担心。

重载的一个例子:

#include <iostream>
using namespace std;
void print(double a)
{
    cout<<a<<endl;
}
void print(int a)
{
    cout<<a<<endl;
}
int main()
{
    print(1); // print(int)
    print(1.1); // print(double)
    print('a'); // print(int)
    print(1.11f); // print(double)
    return 0;
}  

两个print函数就构成了重载。在调用的时候就会根据传进来的参数,自动匹配调用相应的函数。

C++中允许那些隐式类型转化,自动调用相关的重载函数。

重载函数的匹配规则是:

1、严格匹配,找到则调用。

2、通过隐式转换寻求一个匹配,找到则调用。

重载原理

C++利用 Name Mangling(命名倾轧)技术,来改变函数名,区分参数不同的同名函数。实现原理:用 v-c- i-f- l- d 表示 void char int float long double 及其引用。就是说,将一个函数名在他编译的时候会将这个函数根据他的参数改变这个函数名,比如上面的print,void print(int a)可能编译后函数名会成为print_i,而void print(double a),经过编译后变成print_d,这个过程是编译器内部的原理了,我们根本不会看到这个过程,只是分析一些内部原理。具体平台,实现有差异。这就是所谓的命名倾轧。

extern "C"

C/C++的编译都是以文件为单位进行编译的。Name Mangling(命名倾轧) 依据函数声明来进行倾轧的。若声明被倾轧,则调用为倾轧版本,若声明为非倾轧版本,则调用也为非倾轧版本。C++ 默认所有函数倾轧。若有特殊函数不参与倾轧,则需要使用 extercn "C" 来进行声明。

其实这个extern "C"主要就是用在兼容C语言上。

C++ 既然完全兼容 C 语言,那么就面临着,完全兼容 C 的类库。由.c 文件的生成的库文件中函数名,并没有发生 namemangling 行为,而我们在包含.c 文件所对应的.h 文件时,.h文件要发生 name manling 行为,因而会在链接的时候发生的错误。C++为了避免上述错误的发生,重载了关键字 extern。只需要在避免 name manling的函数前,加 extern "C",如有多个,则 extern "C"{ } 将函数的声明放入{}中即可。

我们可以在IDE中点进去C语言中的一个库进去看看,都是把整个声明的头文件包含在 extern "C"{ }的大括号当中的,从而实现了兼容C语言。

5、默认参数(Default Args)

通常情况下,函数在调用时,形参从实参那里取得值。C++给出了可以不用从实参取值的方法,给形参设置默认值,称为默认参数。

#include <iostream>
using namespace std;
void print(int a = 100)
{
    cout<<a<<endl;
}
int main()
{
    print();//此print()没有传参,使用默认参数100
    print(99);//自己传参,就不适用默认参数了
    print(88);
    return 0;
}  

默认参数规则

1、默认的顺序,是从右向左,不能跳跃。

void print(int a = 100 , int b , int c);这样写就是错的,有默认参数的话,必须从右往左实现

2、函数声明和定义一体时,默认认参数在定义(声明)处。声明在前,定义在后,默认参数只能在声明处。类中也是写在声明处。

3、默认值可以是常量,全局变量,或是一个函数。

4、实参个数 + 默认参数的个数 >= 形参个数

规则冲突(conflict)

一个函数,不能既作重载,又作默认参数的函数。当你少写一个参数时,系统无法确认是重载还是默认参数,此时往往回产生 ambiguous(二义性) 的编译错误。

#include <iostream>
using namespace std;

void print(int a)
{

}
void print(int a,int b =10)
{

}
int main()
{
    print(10);  //编译错误
    return 0;
}

上面这两个实现甚至以后我们实现时,当两者要实现同样的功能时,优先选用默认参数。相比较重载而言,默认参数更有实用价值,默认更接近生活的实际

Last modification:October 6th, 2019 at 02:10 pm
如果觉得我的文章对你有用,请随意赞赏

Leave a Comment