第5课 - 新的关键字
一.动态内存分配和释放(C语言是通过标准C库里面的malloc,free函数分配,不只属于C语言)
1.1 C++中通过new关键字进行动态内存申请
1.2 C++中的动态内存申请时基于类型进行的
1.3 delete关键字用于内存释放
变量申请:
Type* pointer = new Type;
......
delete pointer;
数组申请:
Type* pointer = new Type[N];
......
delete[] pointer;
Source Example 1.3:
#include <iostream>
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
int main(int argc, char** argv) {
int *p = new int;
*p = 5;
printf ("p = %p\n", p);
printf ("*p = %d\n", *p);
delete p;
p = new int[10];
for (int i = 0; i < 10 ; i++)
{
p[i] = i + 1;
printf ("p[%d] = %d\n", i, p[i]);
}
delete[] p;
return 0;
}
输出结果如下
特别要注意如果申请的是一个数组,在释放的时候必须delete[] p,否则如果delete p,那么就只会释放一个字节,造成内存泄漏!
1.4. new关键字和malloc关键字的区别
1.4.1 new关键字是C++的一部分,malloc是C库提供的函数
1.4.2 new以具体l类型为单位进行内存分配,malloc只能以字节为单位进行内存分配
1.4.3 new在申请单个类型变量是可进行初始化,malloc不具备内存初始化的特性,具体初始化方式见如下代码
Source Example 1.4.3
#include <iostream>
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
int main(int argc, char** argv) {
int *pi = new int(1);
float *pf = new float(2.0);
char *pc = new char('c');
printf ("*pi = %d\n", *pi);
printf ("*pf = %f\n", *pf);
printf ("*pc = %c\n", *pc);
delete pi;
delete pf;
delete pc;
return 0;
}
输出结果如下
二.C++中的命名空间
2.1 在C语言中只有一个全局作用域(标识符之间可能发生冲突)
2.2 C++中提出了命名空间的概念
2.2.1 命名空间将全局作用域分成不同的部分
2.2.2 不同命名空间的标识符可以同名而不会发生冲突
2.2.3 命名空间可以相互嵌套
2.2.4 全局作用域也叫默认命名空间
2.2.5 C++命名空间的定义
namespace name {/* */}
Source Example 2.2.5:
#include <iostream>
#include <stdio.h>
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
namespace First
{
int i = 0;
}
namespace Second
{
int i = 1;
namespace Internal
{
struct P
{
int x;
int y;
};
}
}
int main(int argc, char** argv) {
return 0;
}
编译通过,两个同名变量i位于不同的命名空间内,因此可以编译通过。
2.2.6 C++命名空间的使用
a.使用整个命名空间 using namespace name;
b.使用命名空间中的变量 using name :: variable
c.使用默认命名空间的变量 ::vaiable
(默认情况下可以使用默认命名空间的所有标识符)
Source Example 2.2.6:
#include <iostream>
#include <stdio.h>
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
/* 定义First命名空间 */
namespace First
{
int i = 0;
}
/* Second */
namespace Second
{
int i = 1;
namespace Internal /* 嵌套定义Internal命名空间 */
{
struct P
{
int x;
int y;
};
}
}
int main(int argc, char** argv) {
/* 使用整个First命名空间的标识符 */
using namespace First;
/* 使用Second命名空间里面的Internal命名空间的P */
using Second::Internal::P;
printf ("i = %d\n", i);
printf ("i = %d\n", Second::i);
P p = {2,3};
printf ("p.x = %d\n", p.x);
printf ("p.y = %d\n", p.y);
return 0;
}
输出结果如下
三.强制类型转换
3.1 C语言中的强制类型转化
(Type)(Expression) or Type(Expression)
Source Example 3.1:
#include <iostream>
#include <stdio.h>
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
typedef void(PF)(int);
struct Point {
int x;
int y;
};
/* 下面的强制类型转换完全是乱来,但是编译通过! */
int main(int argc, char** argv) {
int v = 0x12345;
PF* pf = (PF *)v;
char c = char(v); /* 等同于char c = (char)v; */
pf(v);
Point* p = (Point*)v;
printf ("p->x = %d\n", p->x);
printf ("p->y = %d\n", p->y);
return 0;
}
结论: C方式的强制类型转换存在问题,强制类型转换是不被推荐的,与goto一样,应尽量避免。
a.过于粗暴
任意类型之间都可以进行转换,编译器很难判断其正确性
b.难于定位
在源码中无法快速定位所有使用强制类型转换的语句
现代软件三大难找的BUG:
1. 运算符优先级,位运算,逻辑运算,数学运算混合时
2. 多线程编程时各个线程之间的交互
3. 强制类型转换
3.2 C++将强制类型转换分为4种不同的类型
static_cast,const_cast,dynamic_cast,reinterpret_cast
用法:
xxx_cast<Type>(Expression)
Type是最终要转换目标的类型
Expression是要转换的表达式
3.2.1 static_cast强制类型转换
a.用于基本类型间的转换,但不能用于基本类型指针间的转换
b.用于有继承关系类对象之间的转换和类指针之间的转换
Source Example 3.2.1:
#include <iostream>
#include <stdio.h>
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
int main(int argc, char** argv) {
int i = 0;
char c = 'c';
int *pi = &i;
char *pc = &c;
c = static_cast<char>(i);/* It is Ok! */
pc = static_cast<char *>(pi);/* Error! */
return 0;
上面的例子表明static_cast不能用于基本类型指针间的转换。
注意"static_cast是编译器进行转换的,无法在运行时检测类型,所以类类型之间的转换可能存在风险。
3.2.2 const_cast强制类型转换
用于去除变量的const属性
Source Example 3.2.2:
#include <iostream>
#include <stdio.h>
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
int main(int argc, char** argv) {
const int& j = 1; /* 定义一个只读变量j */
int &k = const_cast<int &>(j); /* 去掉只读变量的const属性,j的只读变量属性就没有了 */
const int x = 2; /* 定义一个真正的常量 */
/* 去掉常量的const属性,即将常量强制转换为引用 */
/* 引用的本质就是指针,需要取址,因此会给x常量分配空间*/
/* 同时y这个引用就是x空间的别名 */
int &y = const_cast<int &>(x); /
k = 5;
printf ("k = %d\n", k);
printf ("j = %d\n", j);
y = 8;
/* x为常量,被放入了符号表里面,因此x的值不会改变,但是x所在内存空间的值变为了8 */
printf ("x = %d\n", x);
printf ("y = %d\n", y);
/* x,y处于同一块内存空间,地址相同 */
printf ("&x = %p\n", &x);
printf ("&y = %p\n", &y);
return 0;
}
输出结果如下
3.2.3 reinterpret_cast强制类型转换
a.用于指针类型间的强制类型转换
b.用于整数和指针类型间的强制类型转换
#include <iostream>
#include <stdio.h>
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
typedef void(PF)(int);
int main(int argc, char** argv) {
int i = 0;
char c = 'c';
int *pi = reinterpret_cast<int *>(&c);
char *pc = reinterpret_cast<char *>(&i);
PF* pf = reinterpret_cast<PF*>(0x123456);
c = reinterpret_cast<char>(i); /* Error, static_cast should be used here */
return 0;
}
注意:
reinterpret_cast直接从二进制位进行复制,是一种极其不安全的行为。
3.2.4 dynamic_cast强制类型转换
a.主要用于类层次间的转换,还可以用于类之间的交叉转换
b.dynamic_cast具有类型检查功能,比static_cast更安全
四.小结
4.1 C++中内置了动态内存分配的专用关键字
4.2 C++中的动态内存分配是基于类型进行的
4.3 C++中命名空间概念结局了名称冲突的问题
4.4 C++细化了C语言的强制类型转换
4.4.1 C++不推荐在程序中使用强制类型转换
4.4.2 C++建议在强制类型转换时考虑一下究竟希望什么样的转换