版权声明:转载需注明出处,若有不足,欢迎指正。 https://blog.csdn.net/qq_28992301/article/details/53575026
c++的动态内存(堆)分配
c语言中依赖库函数malloc进行动态内存分配,而c++中则自带了new/delete关键字
- new/delete最大的特点是以类型为单位分配内存,并且分配单个变量时(数组不行)可进行初始化。唯一要注意的就是整个数组的delete要添加[]
int* p = new int;
delete p;
float* pf = new float(2.0f);//分配单个变量时可进行初始化
delete pf;
p = new int[10];
delete[] p;//尤其注意,整个数组的释放要添加[]
struct mystruct
{
int a;
char b;
}
mystruct *pMySrt=new mystruct;
delete pMySrt;
- new最大的优点是,可以new对象,并自动进行初始化。而malloc就做不到这一点,malloc仅仅能申请到内存而已,并不能赋予其对象的特性。这也是为什么c++要引入new的原因
class Test
{
public:
Test()
{ }
};
Test* pn = new Test;//new一个对象的时候,会自动触发构造函数
Test* pm = (Test*)malloc(sizeof(Test));//malloc仅仅能申请内存,而不能赋予其对象的特性
delete pn;
c++的命名空间
所谓的命名空间,就是为全局变量增加一个标识,用于解决重名问题。全局变量的作用域不会改变
- 其实不同命名空间里的同名变量,本质是两个不同的变量
namespace First
{
int i = 0;
}
namespace Second
{
int i = 1;
namespace Internal
{
struct P
{
int x;
int y;
};
}
}
void func(void)
{
using namespace First; //使用整个命名空间
using Second::Internal::P; //使用某命名空间中的一个变量
printf("Second::i = %d\n", Second::i);//即使函数头部未声明过Second,使用变量时,可以临时声明命名空间
/*之后,函数中的全局变量就会优先采用命名空间中的同名变量*/
}
- 在函数中,我们可以使用using来选择命名空间,函数中的全局变量就会优先采用该命名空间中的同名变量
::var
意味着采用默认命名空间(就是普通全局变量……)的var
c++的强制类型转换
c中的强制类型转换使用不当的话,不仅容易出错,还难以定位错误。而c++中使用了一些新的关键字来实现
- c++用4种关键字来修饰强制类型转换,用法为
xxx_cast< Type >(Expression)
,我们应该尽可能替代c风格的强制类型转换 - static_cast用于基本类型之间、有继承关系类对象之间、类指针之间转换,不可用于基本类型指针之间的转换
int i = 0x12345;
char c = static_cast<char>(i);
- const_cast用于去除变量的只读属性,只能用于指针之间或引用之间
const int& j = 1;
int& k = const_cast<int&>(j);
- reinterpret_cast用于指针之间、整数和指针类型间的转换
char* pc = &c;
int i = 0;
int *pi = reinterpret_cast<int*>(pc);
int *pi = reinterpret_cast<int*>(i);
- dynamic_cast是与继承相关的专用关键字,用于有继承关系的类指针(引用)之间、有交叉关系的类指针之间的转换
- dynamic_cast具有类型检查功能,若是违法操作,则返回NULL
- dynamic_cast要求类中必须有虚函数支持,所以一般都将父类的析构函数设为虚函数
class Base
{
public:
virtual ~Base()
{
}
};
class Derived : public Base
{
};
int main()
{
Base* p1 = new Base;
Base* p2 = new Derived;
Derived* pd = dynamic_cast<Derived*>(p1);//将指向父类对象的父类指针转换为子类指针,违法操作,dynamic_cast返回的指针值为NULL
Derived* pd = dynamic_cast<Derived*>(p2);//将指向子类对象的父类指针转换为子类指针,合法操作,dynamic_cast可以返回正确的指针
return 0;
}
c++的动态类型识别
- 面向对象中,继承关系会带来动态类型识别的问题:程序通过指针/引用只能得到静态类型Father,而不能判断出指针/引用实际指向的是什么类型,即得不到动态类型
QObject *p = new QLabel();//父类指针可以合法的指向子类对象
QObject &r = *(new QPushButton());//父类引用可以合法的指向子类对象
- typeid关键字可以获取变量的静态类型,若要获取动态类型,父类中必须有虚函数,typeid可以根据虚函数表来查询实际动态类型。typeid返回一个type_info对象,在每个编译器下type_info的内容略有不同
/*获取静态类型的简单用法*/
int i = 0;
const type_info &tiv = typeid(i);//通过变量得到静态类型
const type_info &tii = typeid(int);//通过类型得到静态类型
cout << (tiv == tii) << endl;//判断i是否是int类型
/*获取动态类型的用法*/
Father *p1 = new Father();
Father *p2 = new Child();
const type_info &t1 = typeid(*p1);
const type_info &t2 = typeid(*p2);
cout << t1.name() <<endl;//也可以这样直接获取动态类型名
cout << t2.name() <<endl;//也可以这样直接获取动态类型名
c++的结构体
- 在c和c++中,结构体类型有一些区别。比如在c和c++中定义一个结构体类型:
struct Student
{
const char *name;
int age;
};
- c中Student并不能直接作为类型,必须要加上struct;而c++中可直接将Student作为一种类型,极为便利。c中为了达到c++的这种效果,则要使用typedef才行…
/*c++中的结构体变量定义*/
Student s1;
/*c中允许的结构体变量定义*/
struct Student s2;
111111111111111111111111111111111111111111111111111111111111111111
c++中的const
基本性质
- 而c++中,const修饰的变量有基本两种情况
- 当const修饰一个初始化为常值的变量时,会让该变量变成真正的常量,即在编译时,编译器会为这个变量分配内存,但是却不会使用该内存,编译时将它替换成常数,类似于宏定义
- 当const修饰的是一个非显式的值(比如某个变量的地址),那么该变量的性质和c中一样,只是编译时的道德约束,可以用指针强行修改
const 与类的纠葛
- 在类中,const修饰的成员变量,只能由初始列表赋初始化值,构造函数无法对其初始化赋值
- 实例化类时,也可用const修饰对象,使得对象内的成员变量只读,但这只是编译期间的道德约束。const对象只能调用自己内部const修饰的成员函数,不能调用正常的成员函数
- const可以修饰成员函数,具体方法是在成员函数的括号后面加const,千万别再函数前面加const
int getMi()const;
- 由const修饰的成员函数,有如下性质
- const成员函数只能调用其他const成员函数,不可调用普通成员函数
- const成员函数中,不可改写任何成员变量的值
1111111111111111111111111111111111111111111111111111111111111
c++的bool类型
- 在c中,没有原生的bool类型,一般给int值赋1和0来实现bool
- 在c++中,bool类型是原生的,并且有专门的关键字true和false为其赋值,在编译器内部实际以1和0表示,如果将其他值赋给了bool类型,则任何非0数(如5、-3)都认为是true
111111111111111111111111111111111111111111111111111111111111111111
c++的引用机制
c中的指针很强大,但是用起来很晦涩,因此c++中引入了引用,来在很多场景中代替指针
引用简介
- 定义一个引用,相当于给某个变量创建了一个“快捷方式”。不难想象,c++的引用机制,其实是指针的语法糖,c++中多用引用来代替c语言中的指针,从而实现良好的可读性
int a = 4;
int& b = a; //定义b为别名,作为变量a的别名,有点类似于快捷方式
b = 5; //操作b就是操作a
使用引用尤其要注意:
- 引用在定义时必须被初始化,之后无法代表其他变量(无法更改)
- 不能定义引用的引用
定义指针时,有很多种风格:
int &a; //正确,推荐的写法,&靠近变量,定义多个引用时不容易弄错
int& a; //正确,不推荐的写法
int & a; //正确,不推荐的写法
int &a, &b, &c; //正确
int& a, b, c //错误,只有a定义成了引用,b和c都是整形变量。这也是推荐&要靠近变量的原因
- 当我们不关心变量具体的地址值时(比如输出型参数),用引用就比较方便。但当我们要知道具体地址值时(比如内存/寄存器操作),还是必须用指针
- 下面就是引用代替指针的一个场景,c利用指针来实现输出型参数,而在c++中通常利用引用来实现输出型参数
/*c风格*/
void swap(int *a, int *b)
{
int t = *a;
*a = *b;
*b = t;
}
swap(x, y);
/*c++风格*/
void swap(int &a, int &b)
{
int t = a;
a = b;
b = t;
}
swap(x, y);
引用的本质
引用和指针很类似,其实引用的在编译器内部的实现就是指针,只不过c++编译器会对编程者隐瞒这个真相罢了(可以认为引用是指针的语法糖)
- 编译器会向编程者隐瞒引用的本质,比如引用本身所占的空间。如下sizeof得出的结论所示,编译器将引用的本质隐瞒的很好,唯有结构体大小这里露出了马脚,显示出引用就是指针
/*下面定义的变量都和引用有关*/
struct TRef
{
char &r;
};
char c = 'c';
char &rc = c;
TRef ref = { c };
/*
sizeof(char &)为1字节
sizeof(rc)为1字节
sizeof(TRef)为4字节
sizeof(ref.r)为1字节
*/
智能指针
c中的指针经常会因为忘记释放,而引起堆内存泄漏,c++中的智能指针便由此而诞生
- 智能指针有如下一些特性:
- 指针的生命周期结束时,会主动释放堆内存(避免内存泄漏)
- 一片堆内存只能由一个指针标识(避免重复释放)
- 杜绝指针运算和比较(避免越界、野指针)
- 智能指针的的本质是类模板,以STL库实现的智能指针为例。不难发现,它的模板叫auto_ptr,
<Test>
是在向模板指定泛指类型,pt1是模板类实例化的名字,(new Test)
是pt1构造函数的参数。
auto_ptr<Test> pt1(new Test);//new了一个对象test,并定义智能指针pt1指向它
pt1->add();//可以像普通指针那样使用,调用对象中的函数
auto_ptr<Test> pt2(pt1);//pt1将堆内存的所有权转交给pt2,pt1值变为NULL,因为一片堆内存只能由一个指针标识
- 千万注意,智能指针只能用来指向堆内存,并不能在其他领域代替指针。在正确的情景下使用智能指针,可以最大程度上避免内存问题