从 C 到 C++ 的一些不同

之前大致对 C 语言的内容进行了学习,这里试着了解一下 C++。

如果我们用过 C++ 编过程序,会发现如果将只属于 C++ 的部分刨掉之后,剩下的东西好像都差不多。也就是说,C++ 是能够兼容 C 语言的,但是 C++ 相较于 C 语言也有些不同。

类型增强

类型检查

// C_language
#include <stdio.h>

int main()
{
    const int a = 1;
    int b = a;
    const int c = b;

    const int *p = &a;
    const int *pp = &b;
    int *ppp = &a;
    int *pppp = &b;

    printf("a = %d\n",a);
    printf("b = %d\n",b);
    printf("c = %d\n",c);
    printf("*p = %d\n",*p);
    printf("*pp = %d\n",*pp);
    printf("*ppp = %d\n",*ppp);
    printf("*pppp = %d\n",*pppp);

    return 0;
}
// C++_language
#include <iostream>

using namespace std;

int main()
{
    const int a = 1;
    int b = a;
    const int c = b;

    const int *p = &a;
    const int *pp = &b;
    int *ppp = &a;
    int *pppp = &b;

    cout<<"a = "<<a<<endl;
    cout<<"b = "<<b<<endl;
    cout<<"c = "<<c<<endl;

    cout<<"*p = "<<*p<<endl;
    cout<<"*pp = "<<*pp<<endl;
    cout<<"*ppp = "<<*ppp<<endl;
    cout<<"*pppp = "<<*pppp<<endl;

    return 0;
}

上边的程序是分别适合于 C 和 C++ 的相同程序,但是在下方的程序在编译时候却出现了问题,提示 “invalid conversion form ... to ...”,这意味着 C++ 中的类型检查会更加严格。

同样类似于下方的语句,在 C 和 C++ 中分别编译得到的结果也是不同的。所以当使用 C++ 编写程序时,最好是都给出显式转换,防止出现错误。

int *p = malloc(10);

布尔类型

C 语言中用 0 和非 0 来分别代替 true 和 false,但是在 C++ 中却定义了布尔类型,并给出了两个关键字 true 和 false 来代表真和假。

#include <iostream>

using namespace std;

int main()
{
    cout<<true<<" "<<false<<endl;

    return 0;
}

结果为:

1 0

而这样的程序在 C 语言中是不能通过编译的。

字符串类型

在 C 语言中如果需要使用字符串,最常见的就是用字符数组和 char 指针来表示一个字符串。而 C++ 也给出了一个关键字 string 来表示字符串类型。

定义和初始化

#include <iostream>

using namespace std;

int main()
{
    string a = "abcde";
    cout<<a<<endl;

    return 0;
}

结果为:

abcde

类型大小

#include <iostream>

using namespace std;

int main()
{
    string a = "hello";

    cout<<a<<endl;
    cout<<"sizeof(a) is "<<sizeof(a)<<endl;
    cout<<"sizeof(string) is "<<sizeof(string)<<endl;

    return 0;
}

结果为:

hello
sizeof(a) is 24
sizeof(string) is 24

字符串运算

#include <iostream>

using namespace std;

int main()
{
    string a = "hello";
    string b = " world";
    string c = a+b;                 // plus and assign

    cout<<a<<endl;
    cout<<b<<endl;
    cout<<c<<endl;

    cout<<(a>b)<<endl;              // compare

    return 0;
}

结果为:

hello
 world
hello world
1

成员函数

#include <iostream>
#include <string>

using namespace std;

int main()
{
    string a = "hello";
    string b = "world";
    cout<<a<<endl;

    for(unsigned i=0;i<a.size();i++)             // size()   size of string
        cout<<a[i];
    cout<<endl;

    const char *str = const_cast<char *>(a.c_str());// c_str() return a C_language string
    cout<<str<<endl;

    cout<<a.find('l')<<endl;               // find()  find a char in string
    cout<<a.erase(0,1)<<endl;              // erase()  erase char in string
    a.swap(b);                             // swap()  swap string
    cout<<a<<endl;

    return 0;
}

结果为:

hello
hello
hello
2
ello
world

字符串数组

#include <iostream>
#include <string>

using namespace std;

int main()
{
    string str[5] = {"a","aa","aaa","aaaa","aaaaa"};

    for(unsigned i=0;i<5;i++)
        cout<<str[i]<<" "<<str[i].size()<<endl;

    return 0;
}

结果为:

a 1
aa 2
aaa 3
aaaa 4
aaaaa 5

上边的结果显示字符串数组中的每个元素可以是不同的,这样的实现比 C 语言中的二维数组要灵活的多。

枚举类型

C 和 C++ 中都有枚举类型,但是两者是有所区别的。

// C_language
#include <stdio.h>

enum SEASON
{SPRING,SUMMER,FALL,WINTER};

int main()
{
    enum SEASON a =  SUMMER;
    enum SEASON b = 2;

    printf("a = %d\n",a);
    printf("b = %d\n",b);

    return 0;
}
// C++_language
#include <iostream>

using namespace std;

enum SEASON
{SPRING,SUMMER,FALL,WINTER};

int main()
{
    SEASON a = SPRING;
    SEASON b = 2;

    cout<<a<<endl;
    cout<<b<<endl;

    return 0;
}

从上边的结果看来:

  • C 定义枚举类型变量的时候类型名要全写,如 enum SEASON
  • C++ 定义枚举类型变量的时候类型名则不必,如 SEASON
  • C 定义枚举类型变量的时候等号右边可以是枚举值或 int
  • C++ 定义枚举类型变量的时候等号右边必须是枚举值

表达式赋值

在 C 和 C++ 中的某些赋值表达式的定义中,由于返回值不同,因此得到的结果和形式也有所差别。

// C_language
#include <stdio.h>

int main()
{
    int a=0,b=1,c=2;
    a=b=c;

    printf("a = %d\n",a);
    printf("b = %d\n",b);
    printf("c = %d\n",c);

    (a=b)=c;                  // error: lvalue required as left operand of assignment
   
    printf("a = %d\n",a);
    printf("b = %d\n",b);
    printf("c = %d\n",c);
    
    return 0;
}
// C++_language
#include <iostream>

using namespace std;

int main()
{
    int a=0,b=1,c=2;

    a=b=c;
    cout<<a<<" "<<b<<" "<<c<<endl;

    a=0,b=1,c=2;
    (a=b)=c;
    cout<<a<<" "<<b<<" "<<c<<endl;

    return 0;
}

在 C 的程序编译过程中会出现编译错误,也就是只能进行右赋值。

而在 C++ 的程序中左赋值和右赋值都是可行的,但是两者得到的结果分别为:

2 2 2
2 1 2

输入和输出

从上边的部分程序中也可以看出,C 和 C++ 中的输入和输出方式也是有所差别的。

cin/cout

C 中使用的标准输入和标准输出都依靠函数进行,如 scanf,printf

而 C++ 中的标准输入和标准输出则是依赖流对象实现的,如 cin,cout

和 Linux 中的标准输入和标准输出相似,C++ 中也存在标准输入,标准输出和标准错误输出等:

流对象 含义 输入/输出设备
cin 标准输入 键盘
cout 标准输出 屏幕
cerr 标准错误输出 屏幕
clog cerr 中的缓冲输出 屏幕

函数重载

C 中是不能出现同名函数的,就算函数参数不同也不行。

但 C++ 却能够在满足某些条件时,使用同名函数:

// C++_language
#include <iostream>

using namespace std;

int func(int a)
{
    return a;
}

int func(int a,int b)
{
    return b;
}


int main()
{
    cout<<func(1)<<endl;
    cout<<func(1,2)<<endl;

    return 0;
}

结果为:

1
2

重载规则

  • 函数名相同
  • 参数个数不同,参数类型不同,参数顺序不同,都能够构成函数重载
  • 返回值类型相同

匹配原则

  • 严格匹配,找到则发生调用
  • 不能严格匹配,则通过隐式转换找到匹配,然后发生调用
#include <iostream>

using namespace std;

int func(int a)
{
    cout<<"int func(int a)"<<endl;
    return a;
}

int func(int a,int b)
{
    cout<<"int func(int a,int b)"<<endl;
    return b;
}

int func(int a,double b)
{
    cout<<"int func(int a,double b)"<<endl;
    return a;
}

int func(double a,int b)
{
    cout<<"int func(double a,int b)"<<endl;
    return b;
}

int main()
{
    cout<<func(1)<<endl;
    cout<<func(1,2)<<endl;
    cout<<func(1,1.2)<<endl;
    cout<<func(1.2,2)<<endl;

    cout<<func(1,'2')<<endl;

    return 0;
}

结果为:

int func(int a)
1
int func(int a,int b)
2
int func(int a,double b)
1
int func(double a,int b)
2
int func(int a,int b)
50

但是如果调用下边的函数语句,则会出现错误:

cout<<func(1.1,1.2)<<endl;

这是因为在 C++ 内部:

  • 允许 int 到 long 和 double 的隐式类型转换
  • 允许 double 到 int 和 float 的隐式类型转换

而 func(1.2,1.2) 会使编译器不知道调用 func(int,int),func(int,double) 还是 func(double,int) 而发生错误。

重载实质

我们知道每个函数名在当前的命名空间中都是唯一的。C++ 重载后的函数名在当前的命名空间中是否也是唯一的呢?

其实 C++ 内部对于函数重载的命名问题使用了 name mangling 技术,对重载函数进行重命名并以此区分参数不同的同名函数。

name mangling 技术使用 v-c-i-f-l-d 表示 void,char,int,float,long,double 及其引用。

如果我们在 Linux 使用 g++ 命令查看编译后的 .s 文件,就能够在该文件中发现 funci,funcid,funcdi,funcii 等字眼,分别对应参数不同的重载函数。

extern “C”

在 C 语言中存在四个作用域修饰符,分别是 auto,static,register 和 extern,作用分别为:

auto

  • auto 只能用来修饰局部变量,可以省略
  • 局部变量若无其它修饰符,则默认为 auto,也就是说,平常使用的变量大部分都是 auto 修饰的
  • 特点:随用随开,用完即销

register

  • 只能修饰局部变量
  • 一般情况下,是将内存中的变量放到 CPU 寄存器中存储,能够提高访问速度
  • 但 CPU 寄存器数目有限,在程序优化阶段通常会被优化为普通的 auto 修饰的变量

extern

  • 只能用来修饰全局变量
  • 使用本文件之外定义的变量需要加 extern 修饰,否则就会造成重定义
  • extern 修饰的变量只能使用,不能重新赋值

static

static 修饰局部变量时:

  • 会更改局部变量的生命周期,使其与进程一致
  • static 修饰的局部变量如果未初始化,会被初始化为 0

static 修饰全局变量时:

  • 会限制该变量的外延性,使其成为只能在本文件内部使用的全局变量
  • 避免了命名污染

而这里所说的 extern 的作用与 C 中的 extern 的作用稍有不同。

这里的 extern ”C“ 主要是用来避免 name mangling。

我们知道 C 中不存在函数重载,C 中头文件定义的函数也是没有发生 name mangling 的。而 C++ 中的 name mangling 会发生在编译阶段和 .h 文件的声明阶段。因此为了避免 C++ 对 C 编写的库函数或者代码进行 name mangling,就需要在 C++ 的对应位置加上 extern ”C“ 来表明该段代码不发生 name mangling。比如在 C 的一些库函数中为了能够在 C++ 都会有 extern “C”。

操作符重载

操作符重载和函数重载都是重载的一种形式,这同样也是 C++ 种特有的功能。该机制可以将同一操作符应用于不同的数据类型上。

// C++_language
#include <iostream>

using namespace std;

typedef struct complex
{
    int x;
    int y;
}COMP;

complex operator+(complex x, complex y)
{
    x.x = x.x + y.x;
    x.y = x.y + y.y;

    return x;
}

int main()
{
    COMP x={1,2},y={3,4};
    COMP p = x+y;

    cout<<p.x<<" "<<p.y<<endl;

    x.x = 5;x.y = 6;
    y.x = 7;y.y = 8;

    p = operator +(x,y);

    cout<<p.x<<" "<<p.y<<endl;

    return 0;
}

结果为:

4 6
12 14

从上边的结果可以看出,利用运算符重载,operator+ 和 + 实现的效果是一样的,这就是运算符重载的作用。

默认参数

C 语言中,函数是不能设置默认参数的,因此在 C 中必须要显式的传递所有的函数参数,否则便会报错。

而在 C++ 中,函数形参是可以有默认值的,这可以省略掉参数赋值的过程,更加便捷。

#include <iostream>

using namespace std;

int func(int x,int y = 10,int z = 20)
{
    return x+y+z;
}

int main()
{

    cout<<func(10)<<endl;
    cout<<func(10,20)<<endl;
    cout<<func(10,20,30)<<endl;

    return 0;
}

结果为:

40
50
60

默认规则

虽然 C++ 中开放了函数默认参数的设置,但是并不是说可以随便的设置默认参数,而是要符合一定的规则:

  • 实参的顺序从左到右给出,形参中默认参数的顺序从右到左给出,不能间隔跳转
  • 函数声明和定义一体时,默认参数在直接在形参列表中
  • 函数声明和定义分开时,声明在前,定义在后,默认参数在声明处
  • 一个函数不能同时充当重载和具有默认参数的函数

如下边的函数重载是行不通的:

int func(int x,int y = 10,int z = 20)
{
    return x+y+z;
}

int func(int x)
{
    return (int)x;
}

引用

引用含义

引用的形式为 “&var”。

引用(reference)本身是一段内存的引用,也就是变量别名。有点类似于 Linux 中的变量别名。

规则

  • 引用本身没有定义,只是一种关系型声明,用来声明它和某个变量的关系
  • 引用的类型与原类型保持一致,且与引用的变量有相同的地址
  • 从上边的形式可以看出,引用的作用大致与指针相似,但是引用本身不分配内存
  • 声明的时候必须初始化,声明之后,不可变更
  • 可对引用,再次进行引用,此时只说明一个变量具有多个别名
  • 这里的 & 也是经过了运算符重载,只有在数据类型时,才是引用,剩下的均为地址
#include <iostream>
#include <typeinfo>

using namespace std;

int main()
{

    int a =3,b =4;
    int &c = a;

    cout<<a<<endl;
    cout<<b<<endl;
    cout<<c<<endl;
    cout<<sizeof(a)<<" "<<sizeof(b)<<" "<<sizeof(c)<<" "<<endl;
    cout<<typeid(a).name()<<" "<<typeid(b).name()<<" "<<typeid(c).name()<<" "<<endl;
    cout<<&a<<" "<<&b<<" "<<&c<<" "<<endl;

    c = b;

    cout<<a<<endl;
    cout<<b<<endl;
    cout<<c<<endl;
    cout<<sizeof(a)<<" "<<sizeof(b)<<" "<<sizeof(c)<<" "<<endl;
    cout<<typeid(a).name()<<" "<<typeid(b).name()<<" "<<typeid(c).name()<<" "<<endl;
    cout<<&a<<" "<<&b<<" "<<&c<<" "<<endl;

    return 0;
}

结果为:

3
4
3
4 4 4
i i i
0x61fe88 0x61fe84 0x61fe88
4
4
4
4 4 4
i i i
0x61fe88 0x61fe84 0x61fe88

跟指针不同的是,引用不能够再次进行赋值。在定义引用 c 的时候,我们为 c 初始化了变量 a,也就是说此时 c 为变量 a 的别名。但在之后我们又想利用下边的赋值将引用 c 指向变量 b:

c=b;

但结果却不是我们想要的那样。从结果来看,c 此时是变量 a 的一个别名,而上式只是一个赋值,将变量 b 的内容同时赋给了 a 和 c。

引用使用

引用一般使用在函数调用中,将变量引入到函数中,进行函数调用,操作实参,而不需要传入指针。

#include <iostream>

using namespace std;

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

int main()
{
    int x = 1,y = 2;
    cout<<x<<" "<<y<<" "<<endl;
    swap(x,y);
    cout<<x<<" "<<y<<" "<<endl;

    return 0;
}

结果为:

1 2
2 1

可以看出,如果要交换两个变量的值,可以直接传入引用,而不必传入指针。

引用实质

我们上边提到引用的作用跟指针类似,但其实引用的本质就是指针,而只是对指针进行了包装。

而指针的形式则可以通过初始化方式和大小进行探究:

#include <iostream>

using namespace std;

struct typec
{
    char &r;
};

struct typei
{
    int &r;
};

struct typed
{
    double &r;
};

int main()
{
    cout<<sizeof(char &)<<" "<<sizeof(int &)<<" "<<sizeof(double &)<<endl;
    cout<<sizeof(typec)<<" "<<sizeof(typei)<<" "<<sizeof(typed)<<endl;

    return 0;
}

结果为:

1 4 8
4 4 4

从上边的结果可以看出:

  • 引用这种数据类型在使用 sizeof 时,会随着 datatype 的变化而变化,这一点有点像常量
  • 而对引用类型进行 sizeof 时,又不会随着 datatype 的变化而变化,这一点有点像指针

再加上引用必须要在定义时初始化,不能改变指向,但却可以改变引用的值。综合起来看就是:

reference = datatype * const var;

但是引用使用也有所限制:

  • 可以定义指针的引用,而不能定义引用的引用
  • 可以定义指针的指针,但是不能定义引用的指针
  • 可以定义指针数组,但是不能定义引用数组,可以定义数组引用
#include <iostream>

using namespace std;

int main()
{
    int x = 1;

    int *p = &x;
    int * &r = p;      // reference to pointer

    cout<<x<<endl;
    cout<<&x<<" "<<&p<<" "<<&r<<endl;
    cout<<*p<<" "<<*r<<endl;

    int y[3] = {1,2,3};
    int (&rr)[3] = y;     // reference to array

    for(int i=0;i<3;i++)
    {
        cout<<y[i]<<" ";
    }
    cout<<endl;

    for(int i=0;i<3;i++)
    {
        cout<<rr[i]<<" ";
    }

    return 0;
}

结果为:

1
0x61fe8c 0x61fe88 0x61fe88
1 1
1 2 3
1 2 3

常引用

就像可以用 const 修饰指针一样,同样可以使用 const 修饰引用,也能够得到一些特殊的效果。

  • const 对象的引用一定要是 const 的

不能将 const 变量给非 const 引用是限制了不能通过引用修改原始变量的值,毕竟不能只通过别名修改原始 const 的值。

而可以将非 const 变量给 const 引用则是只给引用开放了读的权限,而原始值却是可以修改的。

  • const 引用可使用临时对象初始化

临时对象可以理解为不可取地址的对象,主要有:

  • 常量
  • 表达式
  • 函数返回值
  • 类型不同的变量

下边的形式编译都是能够通过的:

#include <iostream>

using namespace std;

int func()
{
    int x = 1;
    return x;
}

int main()
{
    const int &a = 2;
    cout<<a<<endl;

    int b = 2,c = 3;
    const int &d = b+c;
    cout<<d<<endl;

    const int &e = func();
    cout<<e<<endl;

    double f = 3.25;
    const int &g = f;
    cout<<g<<endl;

    return 0;
}

上边的常引用形式都可以理解为它们在内存中产生了临时变量,然后将这一个临时变量给了常引用,进行了初始化。

  • use const whatever possible
  • 使用 const 可以避免对数据进行误操作
  • 使用 const 可以处理 const 和非 const 实参,不然就只能接受非 const 实参
  • 使用 const 引用,可以使函数能够正确生成并使用临时变量

new/delete

虽然 C++ 能够兼容使用 C 中的 alloc 和 free 函数,但是 C++ 还是另外定义了两个关键字 new 和 delete 来完成对堆内存的申请和释放。

new/new[]

new 用来申请单个变量空间,而忽略数据类型

new[] 用来申请连续的变量空间,而忽略数据类型

delete/delete[]

delete 用来释放申请的单个变量空间,与 new 对应

delete[] 用来释放申请的连续变量空间,与 new[] 对应

#include <iostream>
#include <string.h>

using namespace std;

int main()
{
    int *p = new int(1);
    cout<<*p<<endl;
    delete p;

    int *pp = new int[5];
    for(int i=0;i<5;i++)
        pp[i]=i+1;

    for(int i=0;i<5;i++)
        cout<<pp[i]<<" ";

    cout<<endl;
    delete []pp;

    char **ppp = new char *[5];
    ppp[0] = (char *)"apple";ppp[1] = (char *)"orange";ppp[2] = (char *)"banana";
    ppp[3] = (char *)"watermelon";ppp[4] = (char *)"strawberry";
    for(int i=0;i<5;i++)
        cout<<ppp[i]<<" ";

    cout<<endl;
    delete []ppp;

    int (*pppp)[3] = new int [2][3];
    for(int i=0;i<2;i++)
        for(int j=0;j<3;j++)
            pppp[i][j] = i*3+j;

    for(int i=0;i<2;i++)
        for(int j=0;j<3;j++)
            cout<<pppp[i][j]<<" ";

    cout<<endl;
    delete []pppp;

    char (*z)[11] = new char [5][11];
    strcpy(z[0],"apple");
    strcpy(z[1],"orange");
    strcpy(z[2],"banana");
    strcpy(z[3],"watermelon");
    strcpy(z[4],"strawberry");

    for(int i=0;i<5;i++)
        cout<<z[i]<<" ";

    delete []z;

    return 0;
}

结果为:

1
1 2 3 4 5
apple orange banana watermelon strawberry
0 1 2 3 4 5
apple orange banana watermelon strawberry

注意事项

  • new/delete 是关键字,效率要高
  • new/delete 配对使用,new []/delete [] 配对使用
  • 因为实现的机理不同,因此 new 申请的空间不要用 free 释放,alloc 申请的空间不要用 delete 释放
  • new 申请空间失败不会抛出异常,因此不能使用 q == NULL 判断申请空间是否成功

内联函数

在 C 中,有些简短的代码可以使用宏实现,避免了不断的压栈和出栈,但是当时提到说这样会增加预处理的工作量,增加代码的体积,并且宏只是简单的代码替换,不能够检测语法。而 C++ 中的内联函数相比于宏来说:

  • 同样避免了重复开发
  • 增加了类型检查
  • 但是会带来压栈和出栈的开销
#include <iostream>

using namespace std;

inline int func(int x)
{
    return x;
}

int main()
{
    cout<<func(10)<<endl;

    return 0;
}

上边的代码段只是说明了内联函数的语法,并不是说明什么。因为内联函数的优点只要在发生频繁的调用时,才会有所体现。同时由于内联函数的特点,只有当函数体很小,且被多次调用时才考虑使用内联函数。

类型转换

C 中的类型转换有隐式转换和强制转换,但是 C 中能够实现任意类型之间的转换。

隐式转化

不需要显式地进行类型转化就能够完成的类型转换就被称为隐式转换

算术转化

  • 整形提升:char,short,int 等类型在一起运算时,会被提升到 int 类型。
  • 混合提升:运算时,以表达式中最长类型为主,将其他类型均转换成该类型。
    • 若运算中最大范围为 double,则转化为 double。
    • 若运算中最大范围为 float 则转化为 float。
    • 若运算中最大范围为 long long 则转化为 long long。
    • 若运算中最大范围为 int 则转化为 int。
    • 若运算中有 char,short 则一并转化为 int。

赋值转化

整型和实型之间能够进行直接赋值的。操作时,一个加 0 ,一个去小数位。

强制转化

有时候隐式转换不能够满足需求时,就要进行强制类型转化。

在 C++ 中对类型转换进一步做出了限制。

static_cast

语法

static_cast<datatype> (var)

规则

  • 在一个方向上可以做隐式转化,在另一个方向上就可以进行静态转换。
#include <iostream>

using namespace std;

int main()
{
    char a = 65;
    int b = 100;
    double c = 200.25;

    double d = a;

    cout<<a<<" "<<b<<" "<<c<<" "<<d<<endl;

    d = b;
    cout<<a<<" "<<b<<" "<<c<<" "<<d<<endl;

    a = c;

    cout<<static_cast<char>(d)<<" "<<static_cast<int>(d)<<endl;

    return 0;
}

结果为:

A 100 200.25 65
A 100 200.25 100
d 100

对于指针也是如此,可以认为静态类型转换是将 C 风格的类型转换写成了 C++ 风格的类型转换形式。

reinterpret_cast

语法

reinterpret_cast<datatype>(var)

规则

  • 通常为操作数的位模式提供较低层的重新解释,也就是说是将数据以二进制存在形式的重新解释
  • 在双方向都不能进行隐式类型转换的,需要重新解释类型转换。
#include <iostream>

using namespace std;

int main()
{

    int a[5] = {1,2,3,4,5};
    cout<<hex<<*((int *)((int)a+1))<<endl;
    cout<<hex<<*(reinterpret_cast<int *>(reinterpret_cast<int>(a)+1));

    return 0;
}

结果为:

2000000
2000000

上边的过程为:

  • 将数组 a 的起始地址作为 int 解释,对应的是元素 1 的最低字节地址
  • 然后将该值 +1,对应的是元素 2 的次低字节地址
  • 再将该地址解释为 int *,指向的就是以当前地址起始的连续四个字节
  • 再用 * 解引用,以二进制输出就是 2000000

const_cast

语法

const_cast<datatype>(var)            // datatype只能是指针或者引用

规则

  • 用来移除对象的常量属性
  • 使用 const_cast 去除 const 限定的目的不是为了修改内容
  • 因为 const 变量不能通过非 const 引用,因此 const_cast 通常是为了函数能够接受该实际参数
#include <iostream>

using namespace std;

int func(int &b)
{
    return b;
}

int main()
{

    const int a = 1;

    const int &b = a;
    cout<<func(const_cast<int &>(b));

    return 0;
}

但还存在一种未定义行为:

#include <iostream>

using namespace std;

struct A
{
    int x;
};

int main()
{
    const int a = 10;
    int &ra = const_cast<int &>(a);

    ra = 20;
    cout<<a<<" "<<ra<<endl;

    const A c = {30};
    A &rc = const_cast<A &>(c);
    rc.x = 40;
    cout<<c.x<<" "<<rc.x<<endl;

    return 0;
}

结果为:

10 20
40 40

因此,对于 const_cast 来说:

  • const_cast is only safe if you are adding const to an originally non-const variable. Trying to remove the const status from an originally-const object, and then perform the write operation on it will result in undefined behavior.

dynamic_cast

跟多态有关。

命名空间

C 中的命名空间是根据编写完成后的代码自动实现的,而 C++ 中命名空间却可以手动创建,从而对全局空间进行再次划分,以避免不同模块下的命名冲突。

全局命名空间

全局命名空间包含了所有的命名空间,主要有:

  • 全局变量
  • 数据类型
  • 函数
  • 其它命名空间

而自定义的命名空间也可以按照上边的层次进行创建,只是该命名空间中的内容都属于该命名空间。

声明

#include <iostream>
#include <string>

using namespace std;

namespace first
{
    int a = 10;
    typedef struct A{int x;}typeA;
    int func(int x){return x;}

    namespace second
    {
        int a = 20;
        typedef struct A{ int x;}typeA;
        int func(int x){return x;}
    }
}

int main()
{
    return 0;
}

使用

  • 直接指定 namespace,namespace::var = value;
  • 使用 using namespace::var,using namespace::var;
  • 使用 using namespace spacename,如 using namespace std;
#include <iostream>
#include <string>

using namespace std;

namespace first
{
    int a = 10;
    typedef struct A{int x;}typeA;
    int func(int x){return x;}

    namespace second
    {
        int a = 20;
        typedef struct A{ int x;}typeA;
        int func(int x){return x;}
    }
}

int main()
{
    cout<<first::a<<endl;

    using first::a;
    a = 20;
    cout<<a<<" "<<first::a<<endl;

    using namespace first;
    second::a = 30;
    cout<<a<<" "<<second::a<<endl;

    return 0;
}

结果为:

10
20 20
20 30

使用 using namespace spacename 的形式,相当于直接将命名空间 spacename 中的变量全部解压到当前。

范围限定

上边的形式可能会覆盖之前定义过的同名变量,因此如果我们想要使用不同命名空间中的同名变量时,需要加 {} 来限定变量的作用域。

#include <iostream>
#include <string>

using namespace std;

namespace first
{
int a = 10;
}

namespace second
{
int a = 20;
}

int main()
{
    int a = 100;
    cout<<a<<endl;
    {
        using first::a;
        cout<<a<<endl;
    }
    cout<<a<<endl;
    {
        using second::a;
        cout<<a<<endl;
    }
    cout<<a<<endl;

    return 0;
}

 结果为:

100
10
100
20
100

但是不能这样:

#include <iostream>
#include <string>

using namespace std;

namespace first
{
int a = 10;
}

namespace second
{
int a = 20;
}

int main()
{
    int a = 100;
    cout<<a<<endl;
    {
        using namespace first;
        cout<<a<<endl;
    }
    cout<<a<<endl;
    {
        using namespace second;
        cout<<a<<endl;
    }
    cout<<a<<endl;
    using namespace first;
    cout<<a<<endl;

    return 0;
}

结果为:

100
100
100
100
100
100

原因在于 using namespace spacename 和 using namespace::var 的含义不同。

支持嵌套

在之前的例子中可以看出一个命名空间中可以嵌套另一个命名空间。

协同开发

有时候大型项目的开发需要多个协作共同完成一个命名空间中所有属性的创建,但是属性却分布在不同的文件中。C++ 对这种情况做出了规定:

  • 同名命名空间自动合并
  • 对于一个命名空间中的类,要同时包含声明和实现

更多关于 namespace 的说明和注意事项在这篇文章

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章