目录
1、动态内存分配
C++中的动态内存分配已经和C语言有了明显区别:
- C++中通过new关键字进行动态内存申请
- C++中的动态内存申请是基于类型进行的
- delete关键字用于内存释放
变量申请和释放:
Type* pointer = new Type;
// ……
delete pointer;
数组申请和释放:
Type* pointer = new Type[N];
// ……
delete[] pointer;
new关键字与malloc函数的区别:
- new关键字是C++的一部分,malloc是由C库提供的函数
- new以具体类型为单位进行内存分配,malloc只能以字节为单位进行内存分配
- new在申请单个类型变量时可进行初始化,malloc不具备内存初始化的特性
- new对基本类型申请空间后默认初始化为0
- 对于类类型new和delete还负责构造函数和析构函数的调用
示例:
exp-1.cpp
#include <iostream>
using namespace std;
int main()
{
//申请数组,并默认初始化为0
int **p = new int*[10];
for(int i = 0; i < 10; i++)
{
cout << p[i] << '\t';
}
cout << endl;
//申请单个变量,并指定初始值
int *a = new int(5);
cout << "*a = " << *a << endl;
//申请单个变量,并默认初始化为0
double *d = new double;
cout << "*d = " << *d << endl;
delete [] p;
delete a;
delete d;
return 0;
}
运行结果:
2、命名空间
在C语言中只有一个全局作用域
- C语言中所有的全局标识符共享同一个作用域
- 标识符之间可能发生冲突
C++中提出了命名空间的概念
- 命名空间将全局作用域分成不同的部分
- 不同命名空间中的标识符可以同名而不会发生冲突
- 命名空间可以相互嵌套
- 全局作用域也叫默认命名空间
C++命名空间的定义:
namespace name { /* … */ }
C++命名空间的使用:
- 使用整个命名空间:using namespace name;
- 使用命名空间中的变量:using name::variable;
- 使用默认命名空间中的变量:::variable;
- 默认情况下可以直接使用默认命名空间中的所有标识符:variable;
示例:
exp-2.cpp
#include <iostream>
using namespace std;
namespace First
{
int i = 0;
}
namespace Second
{
int i = 10;
namespace Internal
{
struct Student
{
const char *name;
int age;
};
}
}
int main()
{
using Second::Internal::Student;
First::i = 5;
Student stu;
stu.name = "Tom";
stu.age = First::i + Second::i;
cout << "Name:" << stu.name << "\n" << "Age:" << stu.age << endl;
return 0;
}
3、强制类型转换
C方式的强制类型转换:
- ( Type )( Expression )
- Type( Expression )
示例:
exp-3.cpp
#include <stdio.h>
typedef void(PF) (int);
struct Point
{
int x;
int y;
};
int main()
{
//定义一个整数
int v = 0x12345;
//下面就开始乱来了!想怎么搞就怎么搞!
PF *pf = (PF*)v;
char c = char(v);
//我都不知道我在写什么....
pf(c);
Point *p = (Point*)v;
printf("Point::x = %d\n", p->x);
printf("Point::y = %d\n", p->y);
return 0;
}
上面的程序,编译能过,最多也就给个警告,但是运行肯定出错!
C方式强制类型转换存在的问题
- 过于粗暴
- 任意类型之间都可以进行转换,编译器很难判断其正确性
- 难于定位
- 在源码中无法快速定位所有使用强制类型转换的语句
如何进行更加安全可靠的转换呢?
C++强制类型转换
- C++将强制类型转换分为4种不同的类型
static_cast强制类型转换
- 用于任何具有明确定义的类型之间的转换,只要不包含底层const,都可以使用!
- const char *cp = “C++”; char *q = static_cast< char* >(cp);
- 上面这句就是错误的,static_cast不能去掉底层const
- 用于基本类型间的转换,但不能用于基本类型指针间的转换!
- 用于有继承关系类对象之间的转换和类指针之间的转换!
Tip:
static_cast是编译期进行转换的,无法在运行时检测类型,所以类类型之间的转换可能存在风险。
示例:
exp-4.cpp
#include <iostream>
using namespace std;
int main()
{
int i = 97;
char c;
c = static_cast<char>(i); //OK!
i = static_cast<int>(c); //OK!
char *pa = static_cast<char*>(&i); Error!!
cout << "char c = '" << c << "'"<<endl;
cout << "int i = " << i <<endl;
const char *cp = "C++";
char *q = static_cast< char* >(cp); //Error!!
return 0;
}
const_cast强制类型转换
- 用于去除变量的const属性!
- 只能改变运算对象的底层const!
示例:
exp-5.cpp
#include <stdio.h>
int main()
{
//这样声明,相当于得到了一个只读变量,而非常量
const int& j = 1; //相当于: int temp = 1; const int&j = temp; //temp来自符号表
int& k = const_cast<int&>(j); //对j这个只读变量取引用,并强制去掉了它的const属性:const int* const ==> int* const,得到了一个别名为k的普通变量
k = 5;
printf("k = %d\n", k); //普通变量,来自存储空间
printf("j = %d\n", j); //只读变量,来自存储空间
printf("&k = %p\n", &k);
printf("&j = %p\n", &j);
//这里才是声明了一个常量,符号表中会有它
const int x = 2;
int& y = const_cast<int&>(x);
//这里对x这个常量取了引用,引用的本质是指针常量,所以导致编译器为常量x分配了存储空间
//然后,这里的const_cast相当于转换了:const int* const类型的指针为int *const类型
//而且,这里不能这些做:int& y = x; 会报错!!因为如果没有const_cast<int&>(x);那么x始终都还是一个常量,没有存储空间,不是只读变量!
y = 8;
printf("x = %d\n", x); //这里用的是常量,来自符号表
printf("y = %d\n", y); //这里用的是只读变量,来自存储空间
printf("&x = %p\n", &x);
printf("&y = %p\n", &y);
return 0;
}
运行结果:
Tip:
关于以上程序运行结果的本质原因,在前面的文章中说过,请看这里!
当volatile关键字作用于“常量”
- volatile关键字,告诉编译器不做任何的优化,每次直接到内存中存取值!
- volatile关键字的详细介绍,请看这里!
思考?
经过volatile关键字后,const int i = 1; 还是一个常量么?
验证方法:
我们取i的地址并去掉它的底层const属性,假设它还是一个常量,那么通过指针修改i的内存值后,i的输出值不会改变,因为常量的值来自于符号表;如果值改变了,说明i已经成为了一个只读变量,它的值来自于存储空间!
示例:
exp-6.cpp
#include <stdio.h>
int main()
{
volatile const int i = 1;
int *p = const_cast<int*>(i);
*p = 100;
printf("i = %d\n", i);
printf("*p = %d\n", *p);
return 0;
}
运行结果:
Tip:
从结果上表明,volatile 关键字作用于常量时,常量会变成只读变量,不再使用符号表,而直接使用编译器为“常量”分配的内存空间中的值!
reinterpret_cast强制类型转换
- 用于指针类型间的强制转换
- 用于整数和指针类型间的强制转换
- 通常为运算对象的位模式提供较低层次上的重新解释,直接复制二进制位来实现类型转换,是一种极其不安全的转换!
示例:
exp-6.cpp
#include <stdio.h>
int main()
{
int i = 0;
char c = 'c';
int* pi = &i;
char* pc = &c;
pc = reinterpret_cast<char*>(pi);
pi = reinterpret_cast<int*>(pc);
pi = reinterpret_cast<int*>(i);
c = reinterpret_cast<char>(i); // Error!
return 0;
}
dynamic_cast强制类型转换
- 主要用于类层次间的转换,还可以用于类之间的交叉转换
- dynamic_cast具有类型检查的功能,比static_cast更安全