C与Cpp的区别——重新学习Cpp
ฅ'ω'ฅ♪

C & CPP


Cpp和C的区别

Cpp是C的超集,兼容大部分C的语法的结构。

  1. 设计思想 CPP面向对象,具有封装、继承、多态三种特性; C是面向过程的结构化编程。

  2. 关键字

    1. 数量:C99有32个关键字,CPP98有63关键字。
    2. 具体
      1. struct:C中不能放函数,只能放函数指针;Cpp中可以放函数 。
      2. malloc:C中可以将其返回值赋给任意类型的指针;Cpp中必须强制类型转换。
      3. ...
  3. 返回值 没有返回值:C默认指定为int;Cpp必须指定为void。

  4. 函数参数列表 没有指定参数列表:C可以接收任意多个参数;Cpp会进行严格的参数类型检测,不接受任何参数。 缺省参数:C不支持缺省参数;Cpp支持指定默认值。

    Cpp中使用缺省函数有可能产生二义性。

  5. 函数重载:C中没有函数重载;Cpp支持函数重载。 key:Cpp的名字修饰与C不同
    mangling overload

  6. 泛型编程:Cpp支持,比如模板类、函数模板、类模板。

  7. 引用:C没有引用;Cpp有引用。

  8. 默认链接属性:C对于变量默认链接属性是内链接;Cpp默认是外链接。

  9. 输入输出流:cin,cout之所以效率低,是因为先把要输出的东西存入缓冲区,再输出,导致效率降低。可以使用std::ios::sync_with_stdio(false);取消同步。

  10. 动态管理内存:C中使用malloc/free;Cpp使用new/delete。 malloc_new


Cpp中的名称修饰(Name mangling)

Name mangling

维基:https://en.wikipedia.org/wiki/Name_mangling

C++ compilers are the most widespread users of name mangling. The first C++ compilers were implemented as translators toC source code, which would then be compiled by a C compiler to object code; because of this, symbol names had to conform to C identifier rules. Even later, with the emergence of compilers which produced machine code or assembly directly, the system'slinker generally did not support C++ symbols, and mangling was still required.

大概意思:C++编译器将C++代码转换为C代码,然后使用C编译器来编译从而得到目标代码。因此,符号的名称需要符合C语言的标志符规则。

为什么需要“修饰”

Consider the following two definitions of f() in a C++ program:

int  f (void) { return 1; }
int  f (int)  { return 0; }
void g (void) { int i = f(), j = f(0); }

These are distinct functions, with no relation to each other apart from the name. If they were natively translated into C with no changes, the result would be an error — C does not permit two functions with the same name. The C++ compiler therefore will encode the type information in the symbol name, the result being something resembling:

大概意思:不同的函数,相互之间除了名称没有其他关联。如果这些函数不做任何改变而直接转换为C代码,会带来一个错误——C语言不允许存在两个同名的函数。

所以C++编译器需要将函数的签名信息编码到函数的符号名称中

int  __f_void (void) { return 1; }
int  __f_int (int)  { return 0; }
void __g_void (void) { int i = __f_v(), j = __f_i(0); }

Notice that g() is mangled even though there is no conflict; name mangling applies toall symbols.

注意函数g() 的名称也被修饰了,即使不存在与函数g() 的名称相冲突的地方:名称修饰会应用到所用的符号上。

Complex example

GNU GCC 3.x 的编译器实现中的名称修饰规则

namespace wikipedia 
{
   class article 
   {
   public:
      std::string format (void); 
         /* = _ZN9wikipedia7article6formatEv */
      bool print_to (std::ostream&); 
         /* = _ZN9wikipedia7article8print_toERSo */
      class wikilink 
      {
      public:
         wikilink (std::string const& name);
            /* = _ZN9wikipedia7article8wikilinkC1ERKSs */
      };
   };
}
  1. 所有修饰的符号都是以_Z开头
  2. 下划线后紧跟一个大写字母的名称在C语言中是保留的标志符 (避免与用户的标志符相冲突)
  3. 对于嵌套的名称(命名空间、类等等),紧跟着一个N。
  4. 接着是一系列的 pairs 。(<标志符长度,标志符>)
  5. 最后接着一个E。

名称 wikipedia::article::format

被修饰为:_ZN9wikipedia7article6formatE

For functions, this is then followed by the type information; as format() is avoid function, this is simply **v**; hence:

对于函数,接着的是类型信息,正如format()的参数的类型为void,所以名称中简单的加入一个 v 来表示。

被修饰为:_ZN9wikipedia7article6formatEv

而对于 *print_to *

参数的类型为std::ostream(准确来说是 std::basic_ostream<char, char_traits \ > )

修饰时将用一个特定的别名So来表示。实际上参数的类型是引用,所以实际中采用的修饰名为RSo

最终得到的完整的修饰名称为:_ZN9wikipedia7article8print_toERSo

Handling of C symbols when linking from C++

The job of the common C++ idiom:

#ifdef __cplusplus 
extern "C" {
#endif
    /* ... */
#ifdef __cplusplus
}
#endif

通常C++中的extern “C”的惯用法就是保证C++编译器像C语言那样对待这些符号不进行修饰。C语言中的定义是不进行修饰的,C++编译器需要避免对那些符号引用的修饰。

For example, the standard strings library, <string.h> usually contains something resembling:

#ifdef __cplusplus
extern "C" {
#endif

void *memset (void *, int, size_t);
char *strcat (char *, const char *);
int   strcmp (const char *, const char *);
char *strcpy (char *, const char *);

#ifdef __cplusplus
}
#endif

Thus, code such as:

if (0==strcmp(argv[1], "-x")) 
    strcpy(a, argv[2]);
else 
    memset (a, 0, sizeof(a));

这会是正确的,没有被C++编译器修饰过的 strcmp 和 memset。

而如果没有使用 extern “C”,C++编译器(SunPro版本)将会产生下边的代码

if (0==__1cGstrcmp6Fpkc1_i_(argv[1], "-x")) 
    __1cGstrcpy6Fpcpkc_0_(a, argv[2]);
else 
    __1cGmemset6FpviI_0_ (a, 0, sizeof(a));

由于上边的这些函数符号在C语言运行时库中并不存在,所以最后将发生链接错误。


C & CPP实现 重载

Overload

Cpp实现函数重载很大程度上依赖与编译器对函数名的 Mangling。

在 C/Cpp 程序中,我们可以忽略函数的返回值。在这种情况下,编译器和程序员都不知道哪个 Function 函数被调用。

所以只能靠参数而不能靠返回值类型的不同来区分重载函数

可变参数实现重载

但是可以利用==可变参数==实现C语言的函数重载。

ANSI C 标准中,有可变参数的概念,可以通过一组宏实现

函数 描述
col 3 is right-aligned
va_list arg_ptr 定义一个可变参数列表指针
va_start(arg_ptr, argN) 让arg_ptr指向参数argN
va_arg(arg_ptr, type) 返回类型为type的参数指针,并指向下一个参数
va_copy(dest, src) 拷贝参数列表指针,src->dest,
va_end(arg_ptr) 清空参数列表,并置参数指针arg_ptr无效。每个va_start()必须与一个va_end()对应
#include<stdio.h>  
#include<stdarg.h>  
int getMax(int n, ...)  
{  
        va_list va;  
        va_start(va,n); // init va, pointing to the first argument  
        int tmp,smax=-1;  
        int i;  
        for(i=0;i<n;i++)  
        {  
                tmp=va_arg(va,int); // get the next argument, the type is int  
                if(tmp>smax) 
                    smax=tmp;  
        }  
        va_end(va);  
        return smax;  
}  
int main()  
{  
        printf("%d/n",getMax(4,9,5,2,19));  
        printf("%d/n",getMax(6,1,3,4,5,2,0));  
}  

函数指针实现重载

#include <stdio.h>
#include <stdlib.h>

typedef struct _int_param
{
    int param1;
    int param2;
}INT_PARAM;
typedef struct _double_param_
{
    double param1;
    double param2;
}DOUBLE_PARAM;

typedef void* (*ADDFUNC)(void*);

void* int_add_func(void* wParam)
{
    INT_PARAM* lParam = (INT_PARAM*)wParam;
    int res = lParam->param1 + lParam->param2;
    printf("result = %d\n", res);
}

void* double_add_func(void* wParam)
{
    DOUBLE_PARAM* lParam = (DOUBLE_PARAM*)wParam;
    double res = lParam->param1 + lParam->param2;
    printf("result = %f\n", res);
}
void* add_func(ADDFUNC f, void* wParam)
{
    return f(wParam);
}
int main()
{
    INT_PARAM val1 = {10, 20};
    DOUBLE_PARAM val2 = {30.5, 40.5};
    add_func(int_add_func, &val1);
    add_func(double_add_func, &val2);
    return 0;
}

内置函数实现重载

struct s1
{
    int a;
    int b;

    double c;
};

struct s2
{
    long long a;
    long long b;
};

void gcc_overload_s1(struct s1 s)
{
    printf("Got a struct s1: %d %d %f\n", s.a, s.b, s.c);
}

void gcc_overload_s2(struct s2 s)
{
    printf("Got a struct s2: %lld %lld\n", s.a, s.b);
}

// warning: dereferencing type-punned pointer will break strict-aliasing rules
#define gcc_overload(A)\
    __builtin_choose_expr(__builtin_types_compatible_p(typeof(A), struct s1),\
        gcc_overload_s1(*(struct s1 *)&A),\
    __builtin_choose_expr(__builtin_types_compatible_p(typeof(A), struct s2),\
        gcc_overload_s2(*(struct s2 *)&A),(void)0))

malloc与new的区别

malloc and new

new_delete

new[]

  1. 类型
    1. malloc/free是C标准库函数
    2. new/delete是Cpp的操作运算符/关键字,实际上是调用了运算符重载函数::operator new()::operator delete()
      可以在全局或者类的作用域中提供自定义的new和delete运算符的重载函数,以改变默认的malloc和free内存开辟释放行为,比如说实现内存池。
  2. 操作
    1. malloc/free 单纯分配内存空间并释放。
    2. new/delete 基于前者实现,在开辟/释放空间后,会调用对象的构造函数/析构函数进行初始化/清理。
  3. 成功返回
    1. malloc 成功后会返回 void* ,如需使用必须强制类型转换。
    2. new 成功后会直接返回对应类型的指针。
  4. 失败返回
    1. malloc 开辟内存失败之后会返回nullptr指针,需要检查判空(也只能这样)。
    2. new 开辟内存失败之后会抛出bad_alloc类型的 异常,可以捕获异常。用户可以指定处理函数或重新制定分配器(new_handle)
  5. 能否重载:只有new/delete能被重载。
  6. 调用
    1. malloc对开辟的空间大小需要严格指定。
    2. new只需要内置类型/对象名。
  7. 空间 PS:堆是操作系统维护的一块内存,而自由存储区是C++中通过new与delete动态分配和释放对象的抽象概念。堆与自由存储区并不等价。

    1. malloc在堆(heap)上分配内存。
    2. new申请内存位置有一个抽象概念——自由存储区(free store),它可以在堆上,也可以在静态存储区上分配,这主要取决于operator new实现细节,取决与它在哪里为对象分配空间。
  8. 空间不足

    1. 使用malloc:malloc本身不会进行尝试,可以由开发者再使用realloc进行扩充或缩小。
    2. 使用new:不能被如前者一样直观地改变。当空间不足时会触发new_handler机制,此机制留有一个set_new_handler句柄,看看用户是否设置了这个句柄,如果设置了就去执行,句柄的目的是看看能不能尝试着从操作系统释放点内存,找点内存,如果空间确实不足就抛出bad_alloc异常;

什么时候用new[ ]申请,但可以用delete释放?

int *p = new int;   delete p;
int *p = new int;   delete []p;
int *p = new int[10];   delete []p;
int *p = new int[10];   delete p;

对于内置类型来说,这样混用是可以的,因为对于内置类型没有什么所谓的构造和析构,因此在这里的内存管理和调用malloc,free的含义是一样的,不存在什么问题。

class Test
{
public:
    Test(){}
private:
    int ma;
};
Test *p = new Test;   delete p;
Test *p = new Test;   delete []p;
Test *p = new Test[10];   delete []p;
Test *p = new Test[10];   delete p;

以上四条代码,也不存在任何问题,因为这个类没有提供析构,也就是说Test类对象不需要进行任何有效的析构。

class Test
{
public:
    Test(){}
    ~Test(){}
private:
    int ma;
};
Test *p = new Test;   delete p;         // OK!
Test *p = new Test;   delete []p;       // Failed!
Test *p = new Test[10];   delete []p;   // OK!
Test *p = new Test[10];   delete p;     // Failed!

定义了析构函数,在释放内存之前需要在内存中析构对象,如果执行delete[ ]p,编译器就认为这里有很多对象要析构,多少个对象呢?记录对象个数的数字就在对象内存上面的4个字节存放,因此它从那里开始释放内存了,这肯定是要发生错误的!

使用new

  1. new运算符
  2. operator new();
  3. placement new();
  4. 定位new表达式 内存中new一块地址,然后将大小合适的实例化对象放入该地址中。 格式:new (place_address) type(initializer-list)
    place_address必须是一个指针,initializer-list是类型的初始化列表。
#include<iostream>
using namespace std;
class Array
{
public :
    Array (size_t size = 10)
        :_size(size)
        , _a(0)
    {
        cout<<"Array(size_t size)" <<endl;
        if (_size > 0)
        {
            _a = new int[ size];
        }
    }   
    ~Array()
    {
        cout<<"~Array()" <<endl;
        if (_a )
        {
            delete[] _a ;
            _a = 0;
            _size = 0;
        }
    }
private :
    int* _a ;
    size_t _size ;
};
void Test ()
{
    //malloc/free + 定位操作符new()/显示调用析构函数,模拟 new和delete的行为
    Array* p1 = (Array*) malloc(sizeof(Array));
    new(p1)Array(100);
    p1->~Array();
    free(p1);
    //malloc/free+多次调用定位操作符new()/显示调用析构函数,模拟
    //new[]和delete[] 的行为
    Array* p2 = (Array*) malloc(sizeof(Array)*10);
    for(int i = 0; i < 10; ++i )
    {
        new(p2 +i) Array;
    }
    for(int i = 0; i < 10; ++i )
    {
        p2[i].~Array();
    }
    free(p2 );
}
int main()
{
    Test();
    return 0;
}
CANCEL

-评论-

Here you can post what you want to say, if you have more information please contact me by the following way.

-昵称-
-QQ-
-邮箱-
想说些什么?
-SUBMIT-

-电联 Phone-

+86 18520664652

-邮箱 Email-

boogieLing_o@163.com

boogieLing_o@qq.com

Your name. OS platform Browser model

What do you want to say?

created time

游說萬乘苦不早,著鞭跨馬涉遠道。

阿凌的貓爬架

幸會,

激活Ubuntu

转到“设置”以激活Ubuntu。

R0's board.