第2章 C到C++的扩展

2.1命名空间

2.1.2 命名空间的使用

1.命名空间的定义

namespace是C++中的关键字,用来定义一个命名空间,语法格式如下:

namespace name

{

// 变量、函数、类等

}

2.命名空间的使用

:: 是一个新符号,叫做域解析操作符,在C++中用来指明要使用的命名空间。

还可以使用using来声明;

2.1.3 命名空间完整示例代码

#include<stdio.h>

namespace NameSpace_A
{
    int a;
    int add(int a,int b)
    {
        return a+b;
    }
}

namespace NameSpace_B
{
    int a;
   
    namespace NameSpce_C
    {
        struct teacher
        {
            int id;
            char name[20];
        };
    }
}

// 默认的命名空间的add
int add(int a,int b)
{
    return a+b+10;
}

int main()
{
    {
        int ret = NameSpace_A::add(2,4);
        printf("ret = %d\n",ret);
    }

    {

        using namespace NameSpace_A;
        // 必须加**::,否则和默认的命名空间的add冲突
        int ret = NameSpace_A::add(2,4);
        printf("ret = %d\n",ret);

        ret = ::add(2,3); // 使用默认的命名空间的add
        printf("ret = %d\n",ret);

     }

     {

        using namespace NameSpace_A;
        a = 10;
        printf("a = %d\n",NameSpace_A::a);
     }

     {

        // 定义一个teacher结构体变量
        struct NameSpace_B::NameSpace_C::teacher t1;
        t1.id = 10;
        return 10;
     }

      return 0;

}

站在编译和链接的角度,代码中出现的变量名,函数名,类名等都是一种符号(Symbol)。

有的符号可以指代一个内存位置,如变量名,函数名;有的符号仅仅是一个新的名称,如tydedef定义的类型别名。

2.1.4 C++标准库和std命名空间

..... 略

2.2.2 C++的输入和输出

在编写C++程序时,如果需要使用输入/输出时,需要包含头文件iostream,该头文件中包含了用于输入/输出的对象:

cin 表示标准输入;

cout 表示标准输出;

cerr 表示标准错误;

cout和cin 都是C++的内置对象,而不是关键字。

C++库中定义了大量的类,程序员可以使用他们来创建对象;

cin 是 istream类的对象;

cout 是ostream类的对象;

它们是由标准库的开发者提前创建好的,可以直接拿来使用。

在C++中提前创建好的对象称为内置对象。

2.3 变量定义的位置

ANSI C 规定,所有的局部变量都必须定义在函数开头,在定义变量之前不能有其他的执行语句。

C99标准取消了这个限制。

取消该限制的一个好处是,可以在for循环控制语句中定义变量,如下:

#include <iostream>
using namespace std;

int sum(int n)
{
    int total = 0;
    for(int i = 0; i < n; i++)
    {
        total += i;
    }
    return total;
}

int main()
{
    cout << "Input a interge: ";
    int n;
    cin >> n;

    std::cout << "Total: " << sum(n) << std::endl;

    return 0;
}

在for循环内部定义变量i,代码看起来更加紧凑,使 i 的作用域被限制在for循环内部(循环条件个循环体内)从而减少了命名冲突的概率,在以后的编码过程中,推荐使用。

2.4 register 关键字的变化

register 关键字的作用是请求编译器让变量直接放在寄存器里面,来提升变量的访问速度。

用register关键字修饰的变量,在C语言中是不可以使用&操作符取地址的。内存管理的最小单位是字节,我们对内存的每个字节进行编号,而这个编号就是我们说的地址,寄存器并不在内存中,所以寄存器变量不存在内存地址。

例如:

#include<stdio.h>

int main()
{
    register int a = 10;

    printf("&a = %p\n",&a);

    return 0;
}

使用gcc 编译器编译报错。使用g++ 编译器编译没问题,可以输出相应的地址值。

         

出现这样现象的原因是:C++中,如果对一个寄存器变量进行取地址操作,register对变量的声明变得无效,被定义的变量将会强制存放在内存中。

2.6 三目运算符的加强

C语言中三目运算符返回的是变量的值,而在C++中三目运算符返回的是变量本身。

这样的差异导致了使用方式上的一些差异,如在C++中的三目运算符可以作为左值来使用,如下所示:

#include<iostream>

int main()
{
    int a = 10;
    int b = 20;

    (a > b ? a:b) = 40;
    printf("b = %d\n",b);

    return 0;
}

实验结果如下:

2.6.2 如何在C语言中实现C++的特性

C++中的三目运算符返回的是变量本身,而变量本身实际上代表的是一块内存空间,C语言要想支持C++的这种特性,我们只要通过返回值来找到变量所在的空间即可。

具体的实现方法是让三目运算符返回变量的地址而不是变量的值。

再通过*来获取变量所在的空间修改如下:

*(a > b ? &a:&b) = 40;

这样一来就可以让三目运算符作为左值来使用,实际上C++的三目运算符在内部也是如此操作的。

2.7 bool 类型

C语言并没有彻底从语法上支持真和假,只是用0和1来代表。这点在C++中的到了改善,C++新增了bool类型,它一般占用一个字节的长度,如果多个bool变量定义在一起,可能会各占1位,这取决于编译器的实现。bool类型只有2个值,true和false。

2.8 C/C++中的const

C语言中的const修饰的是只读变量,本质上是变量,有自己的存储空间。

const的含义是不能通过被修饰的变量名来改变这块存储空间的值,并不是说这块存储空间的值不能修改的。如下代码所示:

#include <stdio.h>

int main(int argc,char *argv[])
{
    const int a = 10;
    int *p = (int *)&a;
    *p = 20;

    printf("a = %d\n",a);
    return 0;
}

测试结果如下:

const修饰了变量a;a就变成了只读变量,我们无法通过变量名 a 来修改它所代表的内存空间的值,但是可以通过一个指针指向a所代表的内存空间,进而间接地修改了a代表地那块内存空间地值。如下图所示,直接通过a 来修改变量地值,编译直击报错:

另外,因为const修饰地变量依然是一个变量,所以使用const修饰的变量作为数组长度去定义一个数组也是不允许的。但是我在GCC上编译通过了。

  

2.8.2 C++中的const

C++中的const和C中的const有本质的区别。在C++中,const修饰的是一个真正的常量,而不是C中的只读变量。

const常量会被编译器放到符号表中,符号表中存储的是一系列键值对,所以一般情况下,编译器不会为const常量分配空间。

但是,当我们要对一个const常量进行取地址或者extern操作时,编译器就会为该常量分配存储空间。需要注意的是,当我们使用const常量时,值是从符号表中获取的,而不是使用分配的存储空间的值。例子如下:

#include <stdio.h>
int main(int argc,char*argv[])
{
    const int a = 10;
    int *p = (int *)&a;
    *p = 20;

    printf("&a = %p, p= %p\n",&a,p);
    printf("a = %d, *p = %d\n",a,*p);
    
    return 0;
}

 分别使用gcc和g++对上面的代码进行编译和执行,结果如下:

指针变量p和变量a的地址值都是相等的;

在C中,通过指针变量修改了const int a变量的值,输出为20;

在C++中,直接使用const修饰的值时,是直接从符号表中的值,是10;

a有自己的存储空间,但是在使用a的时候,并不是使用这个存储空间的值,而是从符号表中取值的,符号表原理如下图所示:

2.8.3 const 与define

1.const 与define的相同之处

在C++中const 和 define都可以用来定义常量;

2.const与define的不同之处

const 常量是由编译器处理的,提供类型检查和作用域检查;宏定义是由预处理器处理,单纯的文本替换。

2.9 C++中的引用

C++ 提供了给变量定义别名的机制,那就是引用(Reference)。引用是C++相对于C语言的又一个扩充。

type &name = data

type是被引用的数据的类型,name是引用的名称,data是被引用的数据。

引用必须在定义的同时被初始化,并且以后不能再引用其他数据。

引用的示例代码如下;

#include <iostream>
using namespace std;
int main(int argc, char*argv[])
{
    int a = 10;
    int &b = a;

    // 打印 a, b变量的值
    std::cout << "a = "<< a << "b = "<< b <<std::endl;

    // 打印 a, b变量的地址的值
    std::cout << "&a = " << &a << "&b = "<< &b << std::endl;

    b = 20;
    std::cout << "a = "<< a << "b = "<< b <<std::endl;
}

 程序运行结果如下:

 注意:引用在定义时加&,在使用时不能添加&,使用时添加&表示取地址

2.9.2 引用作为函数参数

在C语言中,函数参数传递可以分为两种:值传递和地址传递。

在定义或声明函数时,可以将函数的形参指定为引用的形式,这样在调用函数时就会将实参和形参绑定在一起,它们指代同一份数据。

如果在函数体中修改了形参的数据,那么实参的数据也会被修改,从而达到了“在函数内部影响函数外部数据”的效果。实例如下:

#include <iostream>
using namespace std;

// 值传递
void swap(int a,int b)
{
    int tmp = a;
    a = b;
    b = tmp;
}

// 地址传递
void swap_p(int *pa, int *pb)
{
    int tmp = *pa;
    *pa = *pb;
    *pb = tmp;
}

// 引用传递
void swap_r(int &a, int &b)
{
    int tmp = a;
    a = b;
    b = tmp;
}

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

    swap(a,b);
    cout<<" a = "<< a << " b = " << b << endl;

    swap_p(&a,&b);
    cout<<" a = "<< a << " b = " << b << endl;

    swap_r(a,b);
    cout<<" a = "<< a << " b = " << b << endl;

    return 0;
}

程序运行如下:

说明,值传递无法改变函数体外变量的值,地址传递和引用传递可以实现。

总结:在一些场合可以代替指针,而且引用相对于指针来说可读性和实用性更好。在以后的C++编程中,应该尽可能地使用引用,尤其对于一些复合数据类型地变量进行参数传递时地引用,可以极大地节省开销。

2.9.3 引用作为函数返回值

引用除了可以作为函数形参,还可以作为函数返回值。在将引用作为函数的返回值时,应该注意一个问题,就是不能返回局部数据(如局部变量、局部对象、局部数组等)的引用,因为当函数调用完成后局部数据就会被销毁掉,有可能在下次使用时就已经不存在了。

C++编译器在检测到该行为时也会给出警告。我们可以返回一个全局变量或者静态变量的引用

对于将引用作为函数返回值的函数,有以下4种处理方式:

1.不接收函数返回值;

2.用一个普通变量接收函数返回值,这时接收的是变量的值而不是变量的引用

3.用一个引用接收函数的返回值,接收的是一个引用

4.当成左值来使用;

实列代码如下:

#include <stdio.h>

int& func()
{
    static int a = 0;
    a++;

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

    return a;
}

int main(int argc, char*argv[])
{
    func(); // 不接收返回值
    
    int a = func();    // 使用一个普通变量去接收,接收到的是一个值
    printf("main a = %d\n",a);
    
    a = 20;
    func();

    int &b = func();// 用一个引用去接收,接收到的是一个引用
    printf("b = %d\n",b);
    b = 30;
    func();

    func() = 100; // 一个函数返回引用,可以当成左值来使用
    printf("b = %d\n",b);

    return 0;
} 

程序运行结果如下:

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值