c++学习笔记6

版权声明:转载需注明出处,若有不足,欢迎指正。 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,因为一片堆内存只能由一个指针标识
  • 千万注意,智能指针只能用来指向堆内存,并不能在其他领域代替指针。在正确的情景下使用智能指针,可以最大程度上避免内存问题

猜你喜欢

转载自blog.csdn.net/qq_28992301/article/details/53575026