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;
}
程序运行结果如下: