1、c++中的内存管理
C语言提供了malloc和free两个函数(库函数)实现,完成对内存的释放和申请,而c++中提供了两个关键字new和delete(关键字)。
能用malloc和free实现的都能用new和delete实现,而new和delete实现的用malloc和free不一定能实现
new/new[]用法:
1.开辟单变量地址空间
int *p = new int; //开辟大小为 sizeof(int)空间
int *a = new int(5); //开辟大小为 sizeof(int)空间,并初始化为 5
2.开辟数组空间
一维:
int *a = new int[100]{0};开辟一个大小为 100 的整型数组空间
int **p = new int*[5]{NULL}
二维: int (*a)[6] = new int[5][6]
三维: int (*a)[5][6] = new int[3][5][6]
四维维及其以上:依此类推.
delete /delete[]用法:
1. int *a = new int;
delete a; //释放单个 int 的空间
2.int *a = new int[5];
delete []a; //释放 int 数组空间
不管几维空间,都是delete []a;只有一个【】
关于返回值
int main()
{
//c语言版本
char *ps = (char*)malloc(100);
if(ps == NULL)
return -1;
//C++内存申请失败会抛出异常
try{
int *p = new int[10];
}catch(const std::bad_alloc e) {
return -1;
}
//C++内存申请失败不抛出异常版本
int *q = new (std::nothrow)int[10];
if(q == NULL)
return -1;
return 0;
}
注意事项
1,new/delete 是关键字,效率高于 malloc 和 free.
2,配对使用,避免内存泄漏和多重释放。
3,避免,交叉使用。比如 malloc 申请的空间去 delete,new 出的空间被 free更进一步
如果只是上两步的功能,c 中的 malloc 和 free 完全可以胜任,C++就没有必要更进一
步,引入这两个关键字。
此两关键字,重点用在
类对像的申请与释放。申请的时候会调用构造器完成初始化,释
放的时候,会调用析构器完成内存的清理。以后我们会重点讲
二、内联函数(inline function)
内联
c 语言中有宏函数的概念。宏函数的特点是内嵌到调用代码中去,避免了函数调用的开销。但是由于宏函数的处理发生在预处理阶段,缺失了语法检测和有可能带来的语意差错。
优点:避免调用时的额外开销(入栈与出栈操作)
代价:由于内联函数的函数体在代码段中会出现多个“副本”,因此会增加代码段的空间。
本质:以牺牲代码段空间为代价,提高程序的运行时间的效率。
适用场景:函数体很“小”,且被“频繁”调用。
适用于函数只有几行的代码量来使用
宏定义是在预处理阶段进行展开的,而内联函数是在编译阶段进行展开
三、类型强转(type cast)
类型转换有 c 风格的,当然还有 c++风格的。c 风格的转换的格式很简单(TYPE EXPRESSION),但是 c 风格的类型转换有不少的缺点,有的时候用 c 风格的转换是不合适的,因为它可以在任意类型之间转换,比如你可以把一个指向 const 对象的指针转换成指向非const 对象的指针,把一个指向基类对象的指针转换成指向一个派生类对象的指针,这两种转换之间的差别是巨大的,但是传统的 c 语言风格的类型转换没有区分这些。还有一个缺点就是,c 风格的转换不容易查找,他由一个括号加上一个标识符组成,而这样的东西在 c++程序里一大堆。所以 c++为了克服这些缺点,引进了 4 新的类型转换操作符。
1.静态类型转换:
语法格式:
static_cast<目标类型> (标识符)
转化规则:
在一个方向上可以作隐式转换,在另外一个方向上就可以作静态转换。
int a = 10;
int b = 3;
cout<<(a)/b<<endl; //float = int int = float
return 0;
int *p; void *q;
p = static_cast(q);
char *p = static_cast(malloc(100));
重解释类型转换:
语法格式:
reinterpret_cast<目标类型> (标识符)
转化规则
“通常为操作数的位模式提供较低层的重新解释”也就是说将数据以二进制存在形式
的重新解释,在双方向上都不可以隐式类型转换的,则需要重解释类型转换。
char *p; int *q;
p = q;
q = p;
int main()
{
int x = 0x12345648;
char *p = reinterpret_cast(&x);
//char*p = static_cast(&x);
printf("%x\n",*p);
int a[5] = {1,2,3,4,5};
int *q = reinterpret_cast((reinterpret_cast(a) +1));
printf("%x\n",*q);
return 0;
}
(脱)常类型转换:
语法格式:
const_cast<目标类型> (标识符) //目标类类型只能是指针或引用。
语法规则
用来移除对象的常量性(cast away the constness)使用 const_cast 去除 const 限定的
目的不是为了修改它的内容,使用 const_cast 去除 const 限定,通常是为了函数能够接受
这个实际参数。
应用场景 1:
#include
using namespace std;
void func(int & ref) //
别人己经写好的程序或类库
{
cout<<ref<<endl;
}
int main(void)
{
const int m = 4444;
func(const_cast(m));
return 0;
}
脱掉 const 后的引用或指针可以改吗?
#include
using namespace std;
int main()
{
const int x = 200;
int & a =const_cast(x); // int &a = x;
a = 300;
cout<<a<<x<<endl;
cout<<&a<<"---"<<&x<<endl;
int *p =const_cast(&x); // int *p = &x;
*p = 400;
cout<<a<<*p<<endl;
cout<<p<<"---"<<&x<<endl;
struct A
{
int data;
};
const A xx = {1111};
A &a1 = const_cast< A&>(xx);
a1.data = 222;
cout<<a1.data<<xx.data<<endl;
A *p1 = const_cast(&xx);
p1->data = 333;
cout<<p1->data<<xx.data<<endl;
return 0;
}
结论:
不要去试图改变const的值
可以改变 const 自定义类的成员变量,但是对于内置数据类型,却表现未定义行为.
Depending on the type of the referenced object, a write operation throu
gh the resulting pointer, reference, or pointer to data member might pr
oduce undefined behavior.
const 常变量(补充):
C++中 const 定义的变量称为常变量。变量的形式,常量的作用,用作常量,常用于
取代#define 宏常量。
#include
using namespace std;
#define N 200
int main()
{
const int a = 200;
int b = 300;
int c = a +b; //int c = N + b;
return 0;
}
动态类型转换:
语法格式:
dynamic_cast<目标类型> (标识符)
用于多态中的父子类之间的强制转化,以后再讲。
四、命名空间(namespace scope)
为什么要引入namespace命名空间为了大型项目开发,而引入的一种避免命名冲突的一种机制。比如说,在一个大型项目中,要用到多家软件开发商提供的类库。在事先没有约定的情况下,两套类库可能在存在同名的函数或是全局变量而产生冲突。项目越大,用到的类库越多,开发人员越多, 这种冲突就会越明显。
语法规则
NameSpace 是对全局(Global scope)区域的再次划分。
声明
命令空间的声明及 namespace 中可以包含的内容
namespace NAMESPACE
{
全局变量 int a;
数据类型 struct Stu{};
函数 void func();
其它命名空间 namespace
}
使用方法
1.直接指定 命名空间: Space::a = 5;
2.使用 using+命名空间+空间元素:using Space::a; a = 2000;
3.使用 using +namespace+命名空间: using namespace Space;
无可辟免的冲突
#include
using namespace std;
namespace Space
{
int x;
}
namespace Other
{
int x;
}
int main()
{
// Space::x = 4;
// cout<<Space::x<<endl;
// Other::x = 5;
//cout<<Other::x<<endl;
{
using Space::x;
x = 5;
cout<<x<<endl;
}
{
using Other::x;
x = 7;
cout<<x<<endl;
}
{
using namespace Space;
x = 5;
}
{
using namespace Other;
x = 8;
}
return 0;
}
命名空间还支持嵌套
#include
using namespace std;
namespace MySpace
{
int x = 1;
int y = 2;
namespace Other {
int m = 3;
int n = 4;
}
}
int main()
{
using namespace MySpace::Other;
cout<<m<<n<<endl;
return 0;
两个相同的命名空间会自动合并成一个命名空间,std是标准命名空间
协作开发
同名命名空间自动合并,对于一个命名空间中的类,要包含声明和实现。
a.h
#ifndef A_H
#define A_H
namespace XX {
class A
{
public:
A();
~A();
};
}
#endif // A_H
a.cpp
#include "a.h"
using namespace XXX
{
A::A()
{}
A::~A()
{}
}
b.h
#ifndef B_H
#define B_H
namespace XX
{
class B
{
public:
B();
~B();
};
}
#endif // B_H
b.cpp
#include "b.h"
namespace XX {
B::B()
{}
B::~B()
{} }
main.cpp
#include
#include "a.h"
#include "b.h"
using namespace std;
using namespace XX;
int main()
{
A a;
B b;
return 0;
}
五、系统 string 类
头文件在程序中使用string类型,必须包含头文件 <string>。 #include <string> //注意这里不是string.h,string.h是C字符串头文件。
定义及初始化
int main()
{
string str;
str = "china";
string str2 = " is great ";
string str3 = str2;
cout<<str<<str2<<endl<<str3<<endl;
return 0;
}
类型大小
cout<<"sizeof(string) = "<<sizeof(string)<<endl;
cout<<"sizeof(str) = "<<sizeof(str)<<endl;
常用运算
赋值
string str3 = str2;
加法
string combine = str + str2;
cout<<combine<<endl;
关系
string s1 = "abcdeg";
string s2 = "12345";
if(s1>s2)
cout<<"s1>s2"<<endl;
else
cout<<"s1<s2"<<endl;
string s3 = s1-s2;
cout<<s3<<endl;
string 类型数组
string sArray[10] = {
"0",
"1",
"22",
"333",
"4444",
"55555",
"666666",
"7777777",
"88888888",
"999999999",
};
for(int i=0; i<10; i++)
{
cout<<sArray[i]<<endl;
}
string 数组是高效的,如果用二维数组来存入字符串数组的话,则容易浪费空间,此时列数是由最长的字符串决定。如果用二级指针申请堆空间,依据大小申请相应的空间,虽然解决了内存浪费的问题,但是操作麻烦。用 string 数组存储,字符串数组的话,效率即高又灵活。
网友评论