1. 命名空间
命名空间可作为附加信息来区分不同库中相同名称的函数、类、变量等。使用了命名空间即定义了上下文。本质上,命名空间就是定义了一个范围。
1.1 定义命名空间
声明一个名字为A
的命名空间:
namespace A {
int cnt;
void f(int x) { cnt = x; }
} // namespace A
声明之后,在这个命名空间外部,你可以通过 A::f(x)
来访问命名空间 A
内部的 f
函数。
命名空间的声明是可以嵌套使用的。
1.2 using指令
使用 using namespace
指令,这样在使用命名空间时就可以不用在前面加上命名空间的名称。这个指令会告诉编译器,后续的代码将使用指定的命名空间中的名称。
using
指令有如下两种形式:
using 命名空间::成员名;
:这条指令可以让我们省略某个成员名前的命名空间,直接通过成员名访问成员,相当于将这个成员导入了当前的作用域。using namespace 命名空间;
:这条指令可以直接通过成员名访问命名空间中的 任何成员,相当于将这个命名空间的所有成员导入了当前的作用域。
- 关于命名空间内变量和函数及全局变量的使用和作用域:
全局变量 a 表达为::a
,用于当有同名的局部变量时来区别两者。
#include <iostream>
using namespace std;
namespace A {
int a = 10;
namespace B //嵌套一个命名空间B
{
int a = 20;
}
}
int a = 200;//定义一个全局变量
int main(int argc, char *argv[]) {
cout << "A::a =" << A::a << endl; //A::a =10
cout << "A::B::a =" << A::B::a << endl; //A::B::a =20
cout << "a =" << a << endl; //a =200
cout << "::a =" << ::a << endl; //::a =200
using namespace A;
cout << "a =" << a << endl; // Reference to 'a' is ambiguous // 命名空间冲突,编译期错误
cout << "::a =" << ::a << endl; //::a =200
int a = 30; //局部变量
cout << "a =" << a << endl; //a =30
cout << "::a =" << ::a << endl; //::a =200
//即:全局变量 a 表达为 ::a,用于当有同名的局部变量时来区别两者。
using namespace A;
cout << "a =" << a << endl; // a =30 // 当有本地同名变量后,优先使用本地,冲突解除
cout << "::a =" << ::a << endl; //::a =200
return 0;
}
2. 动态内存
- 栈:在函数内部声明的所有变量都将占用栈内存。
- 堆:这是程序中未使用的内存,在程序运行时可用于动态分配内存。
new 和 delete 运算符
很多时候,无法提前预知需要多少内存来存储某个定义变量中的特定信息,所需内存的大小需要在运行时才能确定。
在 C++ 中,您可以使用特殊的运算符为给定类型的变量在运行时分配堆内的内存,这会返回所分配的空间地址。这种运算符即 new
运算符。
如果不再需要动态分配的内存空间,可以使用 delete
运算符,删除之前由 new 运算符分配的内存。
new data-type;
data-type 可以是包括数组在内的任意内置的数据类型,也可以是包括类或结构在内的用户自定义的任何数据类型。
double* pvalue = new double; // 为变量请求内存
...
delete pvalue; // 释放 pvalue 所指向的内存
如果自由存储区已被用完,可能无法成功分配内存。所以建议检查 new 运算符是否返回 NULL 指针,并采取以下适当的操作:
double* pvalue = NULL; // 范围指针类型
if( !(pvalue = new double ))
{
cout << "Error: out of memory." <<endl;
exit(1);
}
C++ new
与 C malloc()
new
与 C中的 malloc()
函数相比,其主要的优点是,new 不只是分配了内存,它还创建了对象(调用构造函数)。
void malloc()
:
void *malloc(size_t size) // 参数 size--内存块最小空间,以字节为单位
new
返回指定类型的指针,并且可以自动计算所需要的大小。
int *p;
p = new int;//返回类型为int* ,分配的大小是sizeof(int)
p = new int[100];//返回类型是int*类型,分配的大小为sizeof(int)*100
delete []p;
而malloc需要我们自己计算字节数,并且返回的时候要强转成指定类型的指针。
int *p = (int *)malloc(sizeof(int)*8); // int p[8];
...
free(p)
数组的动态内存分配
- 一维数组
// 动态分配,数组长度为 m
int *array=new int [m];
//释放内存
delete [] array;
- 二维数组
int **array
// 假定数组第一维长度为 m, 第二维长度为 n
// 动态分配空间
array = new int *[m]; // 指针数组
for( int i=0; i<m; i++ )
{
array[i] = new int [n] ;
}
//释放
for( int i=0; i<m; i++ )
{
delete [] array[i];
}
delete [] array;
对象的动态内存分配
#include <iostream>
using namespace std;
class Box
{
public:
Box() {
cout << "调用构造函数!" <<endl;
}
~Box() {
cout << "调用析构函数!" <<endl;
}
};
int main( )
{
Box* myBoxArray = new Box[4];
delete [] myBoxArray; // 删除数组
return 0;
}
调用构造函数!
调用构造函数!
调用构造函数!
调用构造函数!
调用析构函数!
调用析构函数!
调用析构函数!
调用析构函数!
3. C++预处理器
预处理器是一些指令,指示编译器在实际编译之前所需完成的预处理。
所有的预处理器指令都是以井号(#
)开头,只有空格字符可以出现在预处理指令之前。预处理指令不是 C++ 语句,所以它们不会以分号(;)结尾。
3.1 define预处理
#define
预处理指令用于创建符号常量。该符号常量通常称为宏,指令的一般形式是:
#define macro-name replacement-text
当这一行代码出现在一个文件中时,在该文件中后续出现的所有宏macro-name都将会在程序编译之前被替换为 replacement-text。
3.2 参数宏
使用 #define
来定义一个带有参数的宏,如下所示:
#define MIN(a,b) (a<b ? a : b)
在代码中会进行文本替换。
3.3 条件编译
有几个指令可以用来有选择地对部分程序源代码进行编译。这个过程被称为条件编译。
条件预处理器的结构与 if 选择结构很像,以下代码代表可以只在调试时进行编译,调试开关可以使用一个宏来实现,如下所示:
#ifdef DEBUG
cerr <<"Variable x = " << x << endl;
#endif
#ifndef SOMETHING_H
#define SOMETHING_H
//...
#endif
如果在指令 #ifdef DEBUG
之前已经定义了符号常量#define DEBUG
,则会对程序中的 cerr
语句进行编译。您可以使用 #if 0
语句注释掉程序的一部分,如下所示:
#if 0
不进行编译的代码
#endif
3.4 # 和 ## 运算符
3.5 C++ 中的预定义宏
宏 | 描述 |
---|---|
__LINE__ | 这会在程序编译时包含当前行号。 |
__FILE__ | 这会在程序编译时包含当前文件名。 |
__func__ | 这会在程序编译时包含当前函数名。 |
__DATE__ | 这会包含一个形式为 month/day/year 的字符串,它表示把源文件转换为目标代码的日期。 |
__TIME__ | 这会包含一个形式为 hour:minute:second 的字符串,它表示程序被编译的时间。 |