本节主要讲了动态内存分配new和delete关键字的使用方法以及和malloc的差别,命名空间主要讲了命名空间的使用法和C++支持的四种强制类型转换。
C++中的动态内存分配
基本概念和使用方法
在C语言中,malloc和free是C库提供的函数,其实它并不属于C语言的本身(毕竟是函数嘛),在C++中一样可以得到支持,但是C++中申请动态内存(new)和释放动态内存(delete)使用的是两个关键字,这两个关键是C++关键字。
无论是malloc和free函数还是new和delete关键字都是在堆上面获取内存空间的,也就是服从堆分配。
new关键字的使用方法主要包括两个方面:
①申请变量:Type* pointer = new Type;这里是创造一个指针,让这个指针指向新申请的一个Type
②申请数组:Type* pointer = new Type[N];这里创造一个指针,让这个指针指向一个新的申请的数组,这个数组有N个元素。
单个变量的申请和数组的申请是有差别的,数组的申请要注意我们准备申请的数组的大小。
释放内存也同时有两种情况:
①delete pointer 释放单个变量的内存
②delete[] pointer释放数组的内存
注意:在释放数组的时候必须加上[],否则编译器会认为释放的是一个变量的内存,造成内存泄露。例程如下:
#include <stdio.h>
int main()
{
int *p = new int;
*p = 2;
*p = *p + 3;
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;
printf ("p[%d] = %d\n",i, p[i]);
}
delete []p;
}
注意:这里的p是通过int *p = new int建立的指针,所以在释放之后可以直接建立指向10个int类型的数组,不需要再通知类型。p = new int[10]
下面的程序编译不会通过,编译器提示p重复定义,多次初始化
#include <stdio.h>
int main()
{
int *p = new int;
*p = 2;
*p = *p + 3;
printf ("p = %p\n",p);
printf ("*p = %d\n",*p);
delete p;
int *p = new int[10];
for (int i = 0; i < 10; i++)
{
p[i] = i;
printf ("p[%d] = %d\n",i, p[i]);
}
delete []p;
}
new关键字和malloc函数的差别
①new关键字是C++的一部分,malloc是由C库提供的。②new关键字能够以具体类型为单位进行内存分配,malloc只能够按照字节数进行内存分配。
③new在申请单个类型变量时可以进行初始化,malloc不能够进行初始化。
new关键字在申请单个变量时可以进行初始化的例程如下:
#include <stdio.h>
int main()
{
int *pa = new int(1);
char *pb = new char ('c');
float *pc = new float(3.4f);
printf ("*pa = %d\n", *pa);
printf ("*pb = %c\n", *pb);
printf ("*pb = %f\n", *pc);
printf ("*pa = %d\n", *pa);
delete pa;
delete pb;
delete pc;
}
同时不要忘记内存的初始化问题,或者哪一块内存是合法的,尽管是在C++中。下面的程序会发生崩溃。
#include <stdio.h>
int main()
{
int *pa = new int(1);
printf ("*pa = %d\n", *pa);
pa = pa + 4;
printf ("*pa = %d\n", *pa);
delete pa;
}
C++中的命名空间
命名空间的使用背景
在C语言中只有一个全局作用域,也就是我们定义的变量在程序运行的始末都可以进行调用,其他有固定的使用范围的变量都是在子函数中之类的。但是这样可能导致我们的变量或者其他标示符造成冲突,尤其是在比较大的程序中。
在C++中的命名空间有效的解决了上述问题,命名空间将全局作用域分为不同的部分,不同空间中的标示符可以同名而不会发生冲突,命名空间可以相互嵌套,全局作用域也叫默认命名空间。命名空间一定要定义在函数的外部,因为它本身就已经有效的控制了程序中变量的作用域,如果把它放在函数的内部,那么双重限制会导致程序发生混乱。
基本例程:
#include <stdio.h>
namespace first
{
int i = 0;
}
namespace second
{
int i = 1;
namespace t
{
struct P
{
int a;
int b;
};
}
}
int main()
{
printf ("1");
return 1;
}
命名空间的使用方法
①using namespace name 使用整个命名空间。②using name::variable 使用命名空间中的某一个变量(即使这个命名空间没有被完全打开,我们仍然可以通过这种方式来使用这个空间中的某个变量)
③::variable 使用默认命名空间中的变量,
④在默认情况下,可以直接使用命名空间中的所有标示符。
例程如下:
#include <stdio.h>
namespace first
{
int i = 0;
}
namespace second
{
int i = 1;
namespace t
{
struct P
{
int a;
int b;
};
}
}
int main()
{
using namespace first;
using second::t::P;
P p = {2,3};
printf ("%d",i);
printf ("%d",second::i);
printf ("%d",p.a);
}
在上述程序中,我们只打开了一部分second的空间,所以这里如果要使用second中的i,我们仍然要声明second::i,但是这里的P已经完全打开,所以可以直接使用P命名空间在大型的C++项目中意义巨大。
如果仅仅打开第一层命名空间,即使在程序的运行过程中再通过第一层命名空间去访问第二层命名空间也是无法实现的。例程如下:
#include <stdio.h>
namespace first
{
int b = 0;
namespace first
{
int i = 5;
}
}
int main()
{
using namespace first;
printf ("%d",first::first::i);
}
命名空间的使用注意事项
1.命名空间在嵌套的时候可以使用相同的名字,不过在打开命名空间的时候要按照顺序打开,同时作用域也是不相同的。例程如下:
#include <stdio.h>
namespace first
{
int i = 0;
namespace first
{
int i = 5;
}
}
int main()
{
using namespace first::first;
printf ("%d",first::i);
}
上述程序的编译结果是0,这里可以这样理解,我们
通过using namespace first::first打开了名字为first和first的嵌套命名空间(第一个first嵌套第二first),这个时候实际上我们把两个first都打开了,所以实际上我们可以使用两个命名空间了,上述程序最后的打印结果是0,也就是打印第一层命名空间中的i。
#include <stdio.h>
namespace first
{
int i = 0;
namespace first
{
int i = 5;
}
}
int main()
{
using namespace first::first;
printf ("%d",i);
}
上述程序的打印结果是5,我们在
程序开始运行的时候就已经确定直接打开第二层first的命名空间,所以这个时候打印的i是第二层命名空间对应的i.
2.如果 不是嵌套关系的两个命名空间同名,并 且命名空间中的变量也没有出现重名的现象,那么程序可以进行正常运行。例程如下:
#include <stdio.h>
namespace first
{
int i = 0;
}
namespace first
{
int b = 8;
}
int main()
{
using namespace first;
printf ("%d",i);
}
3.如果被嵌套的命名空间的名字和其他的并排命名空间的名字重名,要注意打开命名空间的顺序和到底是哪个打开了,这个时候程序可以运行。
4.一个命名空间下可以嵌套多个命名空间,这多个命名空间可以重名,不过重名的时候空间中的标示符不允许相同,否则程序运行报错。例程如下:
#include <stdio.h>
namespace first
{
int i = 0;
namespace first
{
int i = 0;
}
namespace first
{
int b = 0;
}
}
int main()
{
using namespace first;
printf ("%d",i);
}
C++中的强制类型转换
C语言的强制类型转换
C语言的强制类型转换主要有两种形式, (Type)(Expression)or Type(Expressiong),常用的是第一种形式的强制类型转换,第二种不是很常用。C语言抢孩子类型转换的弊端:①过于粗暴,任意类型之间都可以进行转换,编译器很难判断是否正确。②难于定位,在源码中无法快速定位所有使用强制类型转换的语句。
注意:在程序设计理论中强制类型转换不被推荐,与goto语句一样,应该进行避免,因为在程序设计过程中,定义变量就应该清楚变量的使用环境,直接定义成有效的类型,避免强制类型转换。现代编程中的三个BUG的主要来源:三个BUG的源泉:①运算符优先级(位运算,逻辑运算,数学运算混合使用)②多线程编程(线程之间的转换)③强制类型转换
C++中的强制类型转换方法
1.static_cast强制类型转换
这种强制类型转换的方式主要用于基本类型之间的转换,不能够用于基本类型之间的指针转换。用于有继承关系类对象之间的转换和类指针之间的转换。
基本例程如下:
#include <stdio.h>
int main()
{
int a = 1;
char b = 'a';
int *c = &a;
char *d = &b;
b = static_cast<char>(a);
printf ("%c", b);
return 0;
}
错误的例程(不能够用于基本类型指针之间的转换)
#include <stdio.h>
int main()
{
int a = 1;
char b = 'a';
int *c = &a;
char *d = &b;
c = static_cast<char*>(d);
return 0;
}
程序无法编译通过。
2.const_cast强制类型转换
这种方式主要用于去除变量的const属性。
这种方式去除变量的const属性主要有两种形式,①const int* p②const int& p例程如下:
#include <stdio.h>
int main()
{
const int& a = 1; //const与引用结合让一个变量变为了一个只读变量
int& b = const_cast<int&>(a); //这里通过const_cast来进行强制类型转换,这个时候a就由一个
//只读变量降级为一个普通变量被b引用。
b = 8;
//a = 9; 这条语句经过编译会报错,因为a虽然经过了强制类型转换,但是a仍然是一个只读变量,不够被赋值
printf ("%d\n",b);
printf ("%d\n",a);
}
#include <stdio.h>
int main()
{
const int c = 2; //这里的const在C++中起到的作用是c成为一个常量,在程序开始编译的时候就已经进入符号表
//将常量的const属性去掉之后,由于对c取了引用,也就是对一个常量取了地址,所以编译器会分配内存,
//这个时候d就是c这个常量的内存地址的别名。这个时候d完全可以当做一个普通变量来使用了。
int& d = const_cast<int&>(c);
d = 9;
printf ("%d\n",c);
printf ("%d\n",d);
}
程序打印结果是2,9
#include <stdio.h>
int main()
{
int i = 5;
const int* a = &i; //根据const的属性,此时的*a整体的值是不能够改变的
int* b = const_cast<int*>(a); //这里去掉了*a的const属性,赋给*b
*b = 9; //这里改变a的const属性已经被去掉,所以改变b指向的内存值,a的值也会发生改变。
printf ("%p\n", a);
printf ("%p\n", b);
printf ("*a = %d", *a);
printf ("*b = %d", *b);
}
程序打印结果如下图所示:
除了上面两种情况,还有一种情况是int const *p ,这个应该很熟悉吧,这是引用嘛。例程如下:
#include <stdio.h>
int main()
{
int i = 5;
int* const a = &i; //根据const的属性,此时的a的值是不能够改变的
int* b = const_cast<int*>(a); //这里去掉了*a的const属性,赋给*b
int cc = 8;
a = &cc;
//b = &cc;
printf ("%p\n", a);
printf ("%p\n", b);
printf ("*a = %d\n", *a);
printf ("*b = %d\n", *b);
}
这里程序编译失败,因为我们无法去除掉int* const a的const属性,这也是引用的实质,一旦指向就无法改变了。
3.reinterpret_cast强制类型转换
主要用于指针间的强制类型转换,用于整数和指针间的强制类型转换。这里的指针类型的强制类型转换,都是没有const属性去除的,如果有const属性去除的要求优先考虑去除const属性的要求。
#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);
c = reinterpret_cast<char>(i); // Oops!
printf("Press any key to continue...");
getchar();
return 0;
}
reinterpret_cast直接从二进制位进行赋值,所有会产生很多隐患。
4.dynamic_cast强制类型转换
主要用于类层次间的转换,还可以用于类之间的交叉转换。dynamic_cast具有类型检查的功能,比static_cast更安全。