当前位置:   article > 正文

C++相对C增加的东西

C++相对C增加的东西

相关概念

什么是面向过程的语言?

  • 面向过程就是自顶向下、逐层求解的一种程序设计方法。函数分解法。
  • 过程,就是实现从某个输入集合到某个输出集合的一个映射。 C 和 C++ 的函数都是过程。
  • 模块 = 函数 1+ 函数 2+…

面向过程的语言 :FORTRAN 、 COBOL 、 ALGOL 、 PASCAL 、 BASIC 、C 等等

什么是面向对象的语言?

  • 面向对象的程序中,类才是程序的组成模块。
  • 模块 = 类 1+ 类 2+… = 对象 1+ 对象 2+…
  • 类 = 变量 + 函数 =

面向对象的语言: C++ 、 java 、 python 等等

C++ 语言是面向过程的还是面向对象的?

C++ 语言是对 C 语言的扩展和改进,从这个意义上说,它是面向过程的。

C++ 语言引入了全新的面向对象的特性,这些是C 语言等面向过程的语言所没有的,所以它又是面向对象的。

综合上述, C++ 是一门混合型语言,它既可以面向过程,也可以面向对象,这取决于使用它的程序员是如何使用 C++ 来设计他的程序的。

对象和类

  • 对象:我们把问题空间中的事物和它们在解空间中的表示统称为对象。

    一个汽车是一个对象,组成汽车的发动机也是一个对象

    张三是一个对象,李四也是一个对象

  • 类是具有相同行为和属性的一组对象的集合体 , 对象是类的一个实例。

    你是一个对象,我也是一个对象,我们都有手有脚,会走路会说话。

    这个地球上的所有和你我一样的对象,就构成了“人”类。

  • 类可以看成是一种类型。更高级的,可以灵活扩展的类型。

    包含属性(类的成员变量)和方法(类的成员函数)。

类和类之间的关系

  • 没关系:鱼类和汽车类,无关。
  • is a sort of:人类、鸟类和动物类,人类 is a sort of 动物类。
  • has a:汽车类和发动机类,汽车类has a 发动机类。

面向对象的程序设计方法( OOP )

  • 万物皆对象。
  • 程序就是一组对象,对象之间通过发送消息互相通知做什么。
  • 每一个对象都有它自己的由其他对象构成的存储区。
  • 每一个对象都有一个类型。
  • 一个特定类型的所有对象都能接收相同的消息

C++相对C的增加之处

1. 类

#include <iostream>
using namespace std;

//People是一个类(类 就是 类型)
struct People{
    //成员变量,或者叫做属性
    char *name;
    int age;

    //成员函数,或者叫做方法
    void talk(){ cout << "talk something!" << endl;}
    void walk(){ cout << "he is walking!\n";}
};

int main(int argc, char *argv[])
{
    People a, b;    //a是一个People类型的对象,b也是一样

    a.walk();
    b.talk();

    cout << "sizeof(a) = " << sizeof(a) << endl;
    cout << "sizeof(People) = " << sizeof(People) << endl;
    cout << "sizeof(b)= " << sizeof(b) << endl;

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27

2. C++的命名空间

#include <iostream>
using namespace std;

//A是一个命名空间
namespace A{
    int a;

    void foo(){cout << "A::foo" << endl;}
}   //通常情况下,最后不加分号,加上不会报错。

int main(int argc, char *argv[])
{
    A::a = 10;

    A::foo();

    cout << "A::a = " << A::a << endl;

   return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

想像一下,如果每次调用变量a,都要在前面加上A::,岂不是很复杂,解决方法就是使用using namespace A导出命名空间A里的符号。用法如下所示:

#include <iostream>
using namespace std;

namespace A{
    int a;

    void foo(){cout << "A::foo" << endl;}
}
using namespace A; //导出B名字空间的内容到当前名字空间。
//using B::a;      //只导出B名字空间里的a,引用foo();是需要在前面补充命名空间A::

int main(int argc, char *argv[])
{
    A::a = 10;
    cout << "A::a = " << A::a << endl;
    a = 200;
    cout << "A::a = " << a << endl;

    A::foo();
    foo();

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

如果命名空间B中有变量跟全局变量重名,那会出现什么情况呢?

#include <iostream>
using namespace std;

namespace A{
    int a;

    void foo(){cout << "A::foo" << endl;}
}
using namespace A; //导出B名字空间的内容到当前名字空间。
//using B::a;      //只导出B名字空间里的a,引用foo();是需要在前面补充命名空间A::

int main(int argc, char *argv[])
{
    A::a = 10;
    cout << "A::a = " << A::a << endl;
    a = 200;
    cout << "A::a = " << a << endl;

    A::foo();
    foo();

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

此时编译的话,编译器会给出错误,如下所示:

b.cpp:  In function ‘int main()’:  
b.cpp:33:  error:  reference to ‘a’ is ambiguous  
b.cpp:18:  error:  candidates are:  int a  
b.cpp:5:  error: int B::a  
b.cpp:33:  error:  reference to ‘a’ is ambiguous  
b.cpp:18:  error:  candidates are:  int a  
b.cpp:5:  error: int B::a  
b.cpp:36:  error:  call of overloaded ‘foo()’ is ambiguous  
b.cpp:19:  note:  candidates are:  void foo()  
b.cpp:12:  note: void B::foo()  
b.cpp:40:  error:  reference to ‘a’ is ambiguous  
b.cpp:18:  error:  candidates are:  int a  
b.cpp:5:  error: int B::a  
b.cpp:40:  error:  reference to ‘a’ is ambiguous  
b.cpp:18:  error:  candidates are:  int a  
b.cpp:5:  error: int B::a
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

大体意思就是说,该程序有歧义,对于变量a,已从命名空间B中导出到当前命名空间中,但是当前命名空间中,也存在一个变量a,这也就造成了模糊不清、有歧义的现状。如果想程序通过,将using namespace B;注释掉即可。

关于无名的命名空间,全局变量和普通的全局函数,都属于无名的命名空间,除了直接调用外,还可以使用::的方式调用,如下所示

#include <iostream>
using namespace std;

namespace B{
    int a;

    void foo();
}
using namespace B;

void B::foo()
{
    cout << "B::foo" << endl;
}

//c和foo属于一个无名的名字空间
int c;
void foo()
{
    cout << "::foo" << endl;
}

int main(int argc, char *argv[])
{
    c = 1;
    cout << "c = " << c << endl;
    ::c = 2;
    cout << "::c = " << ::c << endl;

    B::foo();
    ::foo();

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

c语言中,如果有同名的全局变量和局部变量,程序中访问时,遵循的是就近原则。根据上述调用无名的命名空间符号的方法,可以实现任意访问同名全局变量和局部变量其一的方法,如下所示

#include <iostream>
using namespace std;

int c = 3;
int main(int argc, char *argv[])
{
    int c = 4;
    cout << "c = " << c << endl;
    cout << "::c = " << ::c << endl;
    
    c = 1;
    cout << "c = " << c << endl;
    ::c = 2;
    cout << "::c = " << ::c << endl;
    
    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

对于一个已经定义好的命名空间,我们可以扩展它的符号。如下所示:

#include <iostream>
using namespace std;

namespace B{
    int a;
}

namespace B{    //扩展之前的B名字空间
    int f;
}

using namespace B;

int main(int argc, char *argv[])
{
    B::a = 50;
    B::f = 100;
    cout << "B::a = " << B::a << endl;
    cout << "B::f = " << B::f << endl;

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

3. 布尔类型

c语言中,使用stdbool.h头文件来扩展使用bool类型。但是,stdbool.h提供的bool类型,其实还是还是用typedef定义的。并不是真正的bool类型。C++中提供了真正了bool类型。

如下所示:

#include <iostream>
using namespace std;

typedef int BOOL;    //C语言中的bool实现形式
#define TRUE 1
#define FALSE 0

int main(int argc, char *argv[])
{
    bool cond = true;   //原生支持
    BOOL cond1 = TRUE;

    if(cond)
        cout << "true" << endl;
    if(cond1)
        cout << "TRUE" << endl;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

为了更好的看到这其中的区别,
我们使用gdb调试一下。

(gdb) display cond  
1:  cond  =  true  
(gdb) display cond1  
2:  cond1  =  1
  • 1
  • 2
  • 3
  • 4

如果布尔变量用在算术表达式中,将被隐式的转换为int版,true就是1,false就是0。

4. 引用类型

引用类型是C++引入的一种新类型。引用类型的符号为&,关于引用类型的相关注意问题,见程序注释。

#**include**  <iostream>  
**using**  **namespace**  std;  
  
**int**  main()  
{  
**int**  n  =  10;  
**int**  m  =  1000;  
  
**int**  *pn  =  &n; //指针pn指向变量n  
  
**int**  &rn  =  n; //rn为变量n的引用,引用相当于变量的别名或者外号,因此该处的n和rn的地址是一样的。rn只是n的一个外号,引用类型不占用空间,本条语句没有新加变量。  
  
//1.引用被创建的同时必须初始化  
/*错误用法  
int &rn;  
rn = n;  
*/  
  
//2.不能有NULL引用  
/*错误用法  
int &rn = NULL;  
*/  
  
//3.不能创建常量的引用  
/*错误用法  
int &rn = 100;  
*/  
  
//4.创建引用后 不能改变引用的对象。  
/*语法没错,这样做,只是把m的值赋给rn,这两句话等价于int n = m;  
int &rn = n;  
rn = m;  
cout << "rn = " << rn << endl;  
*/  
  
cout  <<  "&n = "  <<  hex  <<  &n  <<  endl;  
cout  <<  "&pn = "  <<  hex  <<  &pn  <<  endl;  
cout  <<  "&rn = "  <<  hex  <<  &rn  <<  endl;  
  
return 0;  
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41

通过输出,我们可以发现,n的地址和rn的地址是相同的,因此rn是不占用空间的,它就是n的一个别名;

4.1 引用的主要功能

4.1.1 做函数参数

引用类型做函数参数,省去了值复制的过程,加快了程序的执行。

#include <iostream>
using namespace std;

void swap(int &x, int &y)
{
    int tmp;
    tmp = x;
    x = y;
    y = tmp;
}

int main(int argc, char *argv[])
{
    int i = 3, j = 4;
    swap(i, j);

    cout << "i = " << i << endl;
    cout << "j = " << j << endl;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

引用就是变量的别名,是从C++语法角度来理解的。从底层实现来看,引用就是用const指针实现的,只不过C++语法限定了它不能像指针一样使用它。因此swap函数通过引用类型直接访问了main函数的i和j变量。相对C语言里传递变量的指针来修改变量的值的方法,传递变量的引用,代码显的更简练。

4.1.2 做返回值

注意返回局部变量的引用问题。返回一个全局变量的引用时,函数调用可以做左值。

#include <iostream>
using namespace std;

int data = 1;
int &foo()
{
    data = 2;
    return data;
}

int main(int argc, char *argv[])
{
    cout << "data = " << data << endl;
    foo();
    cout << "data = "  << data << endl;
    foo() = 3;
    cout << "data = " << data << endl;

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

5. 变量初始化

int data = 10;    //定义+初始化
int data(10);    //定义变量data的同时,对data用10进行了初始化。等价于上面的一行代码
  • 1
  • 2

6. 分配空间(new、delete和malloc、free)

C语言中,动态分配内存,我们使用的是malloc/free函数,C++中的new/delete就相当于C中的malloc/free。new/delete写出的代码更加紧凑。同时,new操作会触发类的构造函数,delete会触发类的析构函数(后面类的部分,会介绍),因此C++中,就不要再使用malloc/free函数了。

malloc(100);    //分配100个字节,单位为长度。返回值为void *,使用时要强转,如char *p = (char *)malloc(100);
free(p);              //释放malloc分配的空间

new int[100];    //分配100个int,即100 * 4 = 400字节,单位为个数。返回值为对应类型的指针,如int * p = new int[100];
delete[] p;        //释放new出来的空间
  • 1
  • 2
  • 3
  • 4
  • 5

关于delete的使用规则,遵循如下两条即可。

  • new的时候没有带[],那么delete后面无需加[];
    int *p = new int;
    delete p;
    
    • 1
    • 2
  • new的时候带[],那么delete后面也要加[]
    int *p = new int[10];
    delete[] p;
    
    • 1
    • 2

再考虑一下这个问题,new int[10];跟new int(10);有什么区别?

  • new int[10]表示分配10个int的空间,即40字节;
  • new int(10)表示分配一个int的空间,即4字节,并用10初始化之。

7. 缺省参数

函数的声明:没有函数体
函数的定义:也是一种声明,需要分配存储空间
变量的声明:不分配空间,只说明程序中有这个符号
变量的定义:分配空间
比如:

int data; //data分配了空间,所以它是定义,但是其本身也是一种声明,表示该符号可引用。

extern int data; //纯声明,其实现在其它源文件中。
  • 1
  • 2
  • 3

变量的声明和定义与函数的声明和定义有一定区别,注意区分。

缺省参数只能出现在声明中,不能出现在定义中

void foo(int a = 0, int b = 0);
void foo(int a, int b)
{
    //...
}
foo();    //3种方式均可成功调用,没有写的参数用缺省值代替
foo(1);
foo(1, 2);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

缺省参数只能从右向左连续缺省,不能跳过。因此foo(, 2);这种写法是错误的。再比如

void foo(int x, int y, int z)
{
    //...
}
//foo函数的缺省参数写法如下所示
#if 1 //正确
	foo(int x = 0, int y = 1, int z = 2);
	foo(int x, int y, int z);
	foo(int x, int y, int z = 3);
	foo(int x, int y = 5, int z = 4); 
#else    //错误
	foo(int x = 0, int y = 0, int z);
	foo(int x, int y = 0, int z);
	foo(int x = 0, int y, int z);
	foo(int x = 0, int y, int z = 0);
#endif
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

下面是使用函数缺省参数的一个示例程序

#include <iostream>
using namespace std;

void foo(int a = 1, int b = 2, int c = 2);

void foo(int a, int b, int c)
{
    cout << "a = " << a << endl;
    cout << "b = " << b << endl;
    cout << "c = " << c << endl;
}


int main(int argc, char *argv[])
{
    foo();
    foo(0);
    foo(0, 0);
    foo(0, 0, 0);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

8. 重载(overload)

8.1 函数重载

函数重载的表现是函数名相同,函数参数不同(包括参数类型和个数)。如下所示就是函数的重载:

/**add.c**/
int add(int a, int b)
{
    return (a + b);
}
double add(double a, double b)
{
    return (a + b);
}
float add(float a, float b)
{
    return (a + b);
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

使用Linux上的nm命令查看符号表,对比

$ g++  add.c  -c  
$ nm add.o  
0000000e T _Z3adddd  
00000034  T _Z3addff  
00000000 T _Z3addii
  • 1
  • 2
  • 3
  • 4
  • 5

也就是说,这3个名为add的c++函数相当于_Zadddd_Zaddff_Zaddii的3个新函数,这3个函数依次对应double、float、int3个名为add的C++函数(可以用linux上的c++filt命令出函数声明),到了链接阶段(g++ -c,只编译和汇编,不链接,下一步就是链接了),add函数就不重名了。在C语言中没有重载的概念,如果用gcc编译add.c,就会有错误提示:函数add重复定义。

函数重载(overload)可以使功能相同或者相似,并且具有统一接口和调用形式的一类函数,显得代码更紧凑。

8.2 运算符重载

上一节中,不同add函数里里都有a + b,这里的+号,就是重载的operator+运算符。再举个例子:cout<<"abc"<<100<<'d'<<endl;。这里的cout重载的是operator()运算符,具体有operator(int)operator(char)operator(float)operator(char *)等等。

9. 在C++中调用C的库函数

c库函数提供的头文件应该统一写成如下形式,使用extern "C"包起来,目的时告知C++,在链接时把这里面的所有符号(变量、函数等)当成C语言版本的符号来处理。

#ifdef __cplusplus
extern "C"{
#endif
//...
extern int interval;
extern int add(int a, int b);
//...
#ifdef __cplusplus
}
#endif
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

如C++中调用C库提供的add函数

/**add.c**/
double add(double a, double b)
{
    return (a+b);
}
  • 1
  • 2
  • 3
  • 4
  • 5
/**add.h**/
#ifdef __cplusplus
extern "C"{
#endif
    extern double add(double, double);
#ifdef __cplusplus
}
#endif
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
/**main.cpp**/
#include <iostream>
#include "add.h"

using namespace std;

int main(int argc, char *argv[])
{
    cout << "add(1.2, 3.4) = " << add(1.2, 3.4) << endl;

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

执行流程如下

$ gcc add.c  -c  
$ ar rs libadd.a add.o
$ g++  main.cpp  -o appcpp  -L.  -ladd  
$ ./appcpp #c++调用C库  
add(1.2,  3.4)  =  4.6
  • 1
  • 2
  • 3
  • 4
  • 5

上面讲到的都是C++中新增加的基础知识概念。除此之外,C++相比C还新增了很多高级语法(比如智能指针)、库函数(比如list库)等等,高级语言的丰富特性使得我们使用起来更加方便,更加高效,留待大家在后面的学习过程中持续去发现吧。

本文内容由网友自发贡献,转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/208662
推荐阅读
相关标签
  

闽ICP备14008679号