从C向C++21——C++11(2)

一.构造函数

1.委托构造函数

委托构造函数允许使用同一个类中的一个构造函数调用其它的构造函数,从而简化相关变量的初始化。

class Test
{
public:
    Test() {};
    Test(int max)
    {
        this->m_max = max > 0 ? max : 100;
    }

    Test(int max, int min):Test(max)                  //委托构造函数
    {
        this->m_min = min > 0 && min < max ? min : 1;
    }

    Test(int max, int min, int mid):Test(max, min)         //委托构造函数
    {
        this->m_middle = mid < max && mid > min ? mid : 50;
    }

    int m_min;
    int m_max;
    int m_middle;
};
  • 这种链式的构造函数调用不能形成一个闭环(死循环),否则会在运行期抛异常。

  • 如果要进行多层构造函数的链式调用,建议将构造函数的调用的写在初始列表中而不是函数体内部,否则编译器会提示形参的重复定义。

参考链接

2.继承构造函数

我们知道,如果在基类写的函数比如func(),如果没有定义虚函数,那么派生类再定义一个同名函数(不一定同参),还是会造成基类函数的遮蔽问题,那么要想解决这个问题,使子类对象可以调用基类定义的函数,也就是继承了基类的同名函数,可以使用using关键字。

class Base
{
public:
    Base(int i) :m_i(i) {}
    Base(int i, double j) :m_i(i), m_j(j) {}
    Base(int i, double j, string k) :m_i(i), m_j(j), m_k(k) {}

    int m_i;
    double m_j;
    string m_k;
};

class Child : public Base
{
public:
    Child(int i) :Base(i) {}
    Child(int i, double j) :Base(i, j) {}
    Child(int i, double j, string k) :Base(i, j, k) {}
};

可以等价于:

class Base
{
public:
    Base(int i) :m_i(i) {}
    Base(int i, double j) :m_i(i), m_j(j) {}
    Base(int i, double j, string k) :m_i(i), m_j(j), m_k(k) {}

    int m_i;
    double m_j;
    string m_k;
};

class Child : public Base
{
public:
    using Base::Base;
};

继承构造函数的使用方法是这样的:通过使用using 类名::构造函数名(其实类名和构造函数名是一样的)来声明使用基类的构造函数,这样子类中就可以不定义相同的构造函数了,直接使用基类的构造函数来构造派生类对象。这里只是以构造函数为例,不一定非要是构造函数。

二.结构变化

1.基于范围的for循环

C++ 11标准之前(C++ 98/03 标准),如果要用 for 循环语句遍历一个数组或者容器,只能套用如下结构:

for(表达式 1; 表达式 2; 表达式 3){
  //循环体
}

而 C++ 11 标准中,除了可以沿用前面介绍的用法外,还为 for 循环添加了一种全新的语法格式,如下所示:

for (declaration : expression){
  //循环体
}

其中,两个参数各自的含义如下:

  • declaration:表示此处要定义一个变量,该变量的类型为要遍历序列中存储元素的类型。需要注意的是,C++ 11 标准中,declaration参数处定义的变量类型可以用 auto 关键字表示,该关键字可以使编译器自行推导该变量的数据类型。
  • expression:表示要遍历的序列,常见的可以为事先定义好的普通数组或者容器,还可以是用 {} 大括号初始化的序列。

同 C++ 98/03 中 for 循环的语法格式相比较,此格式并没有明确限定 for 循环的遍历范围,这是它们最大的区别,即旧格式的 for 循环可以指定循环的范围,而 C++11 标准增加的 for 循环,只会逐个遍历 expression 参数处指定序列中的每个元素。同时它也不支持反向遍历,只能顺序遍历。

注意:

  • 在使用新语法格式的 for 循环遍历某个序列时,如果需要遍历的同时修改序列中元素的值,实现方案是在 declaration 参数处定义引用形式的变量。
  • 对应set容器来说,内部元素都是只读的,这是由容器的特性决定的,因此在for循环中auto&会被视为const auto &
  • 对应基于范围的for循环来说,冒号后边的表达式只会被执行一次。在得到遍历对象之后会先确定好迭代的范围,基于这个范围直接进行遍历。

2.初始化列表

个人理解:初始化符号{},作用是可以在定义的时候就完成初始化。

以往的初始化:

// 数组的初始化
int array[] = { 1,3,5,7,9 };
double array1[3] = { 1.2, 1.3, 1.4 };

// 对象的初始化
struct Person
{
    int id;
    double salary;
}zhang3{ 1, 3000 };

现在的初始化:

 	int a1 = { 1314 };
    int a2{ 1314 };
    int arr1[] = { 1, 2, 3 };
    int arr2[]{ 1, 2, 3 };

int * p = new int{520};
double b = double{52.134};
int * array = new int[3]{1,2,3};

3.std::initializer_list

个人理解:它是一个更轻量级的容器,类似于vector数组,只接受任意个同数据类型的容器。

  • 在std::initializer_list内部有三个成员接口:size(), begin(), end()。
  • std::initializer_list对象只能被整体初始化或者赋值。

使用方法:

void traversal(std::initializer_list<int> a)
{
    for (auto it = a.begin(); it != a.end(); ++it)
    {
        cout << *it << " ";
    }
    cout << endl;
}

	initializer_list<int> list;
 	list = { 1,2,3,4,5,6,7,8,9,0 };
    cout << "current list size: " << list.size() << endl;
    traversal(list);

4.lambda表达式

lambda表达式定义了一个匿名函数,并且可以捕获一定范围内的变量。lambda表达式的语法形式简单归纳如下:

[capture](params) opt -> ret {body;};

其中capture是捕获列表,params是参数列表,opt是函数选项,ret是返回值类型,body是函数体。

  • 捕获列表[]: 捕获一定范围内的变量

    lambda表达式的捕获列表可以捕获一定范围内的变量,具体使用方式如下:

    • [] - 不捕捉任何变量
    • [&] - 捕获外部作用域中所有变量, 并作为引用在函数体内使用 (按引用捕获)
    • [=] - 捕获外部作用域中所有变量, 并作为副本在函数体内使用 (按值捕获),拷贝的副本在匿名函数体内部是只读的
    • [=, &foo] - 按值捕获外部作用域中所有变量, 并按照引用捕获外部变量 foo
    • [bar] - 按值捕获 bar 变量, 同时不捕获其他变量
    • [&bar] - 按引用捕获 bar 变量, 同时不捕获其他变量
    • [this] - 捕获当前类中的this指针,只可以以用当前对象的成员
  • 参数列表(): 和普通函数的参数列表一样,如果没有参数参数列表可以省略不写。

  • opt 选项, 不需要可以省略

    • mutable: 可以修改按值传递进来的拷贝(注意是能修改拷贝,而不是值本身)
    • exception: 指定函数抛出的异常,如抛出整数类型的异常,可以使用throw();
  • 返回值类型:在C++11中,lambda表达式的返回值是通过返回值后置语法来定义的。

  • 函数体:函数的实现,这部分不能省略,但函数体可以为空。

注意:

  • 捕获列表中[=]表示以值传递(拷贝、副本)的方式传入外部参数,这些参数默认是只可读的;要想改变副本的值,后面的选项需要选择mutable;但是即使添加mutable后,你修改的也只是副本的值,在匿名函数外面的原来参数还是原值。
  • 一般匿名函数就在定义时调用,调用就是在{}后面添加一个()即可。

三.可调用对象

1.union联合体

在 C/C++ 中,联合体(Union)是一种构造数据类型。在一个联合体内,我们可以定义多个不同类型的成员,这些成员将会共享同一块内存空间

union 联合体名
{
    //成员变量
};

因为所有成员共用一块内存,所以同一时刻只有一个成员的值是有效的。

四.智能指针

1.函数指针

个人理解:一个指针(它指向某个函数)

函数指针的声明格式:

返回值类型 (*指针名)(参数1,参数2)

理解:上述定义了一个执行传入参数1、参数2返回值为ret类型函数的指针,注意里面有一个*

函数指针赋值和调用:

函数指针名 = 函数名;         //赋值

函数指针名(传入参数);      //调用

2.智能指针

智能指针是存储指向动态分配(堆)对象指针的类,用于生存期的控制,能够确保在离开指针所在作用域时,自动地销毁动态分配的对象,防止内存泄露。智能指针的核心实现技术是引用计数,每使用它一次,内部引用计数加1,每析构一次内部的引用计数减1,减为0时,删除所指向的堆内存。

C++11中提供了三种智能指针,使用这些智能指针时需要引用头文件<memory>

  • std::shared_ptr:共享的智能指针
  • std::unique_ptr:独占的智能指针
  • std::weak_ptr:弱引用的智能指针,它不共享指针,不能操作资源,是用来监视shared_ptr的。

3.共享智能指针

共享智能指针是指多个智能指针可以同时管理同一块有效的内存,共享智能指针shared_ptr 是一个模板类,如果要进行初始化有三种方式:通过构造函数、std::make_shared辅助函数以及reset方法。共享智能指针对象初始化完毕之后就指向了要管理的那块堆内存,如果想要查看当前有多少个智能指针同时管理着这块内存可以使用共享智能指针提供的一个成员函数use_count

  • 通过构造函数初始化:

    // shared_ptr<T> 类模板中,提供了多种实用的构造函数, 语法格式如下:
    std::shared_ptr<T> 智能指针名字(创建堆内存);
    
    
    // 使用智能指针管理一块 int 型的堆内存
        shared_ptr<int> ptr1(new int(520));
        cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl;
    
  • 通过拷贝构造或移动构造

    // 使用智能指针管理一块 int 型的堆内存, 内部引用计数为 1
        shared_ptr<int> ptr1(new int(520));
        cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl;
        //调用拷贝构造函数
        shared_ptr<int> ptr2(ptr1);
        cout << "ptr2管理的内存引用计数: " << ptr2.use_count() << endl;
        shared_ptr<int> ptr3 = ptr1;
        cout << "ptr3管理的内存引用计数: " << ptr3.use_count() << endl;
        //调用移动构造函数
        shared_ptr<int> ptr4(std::move(ptr1));
        cout << "ptr4管理的内存引用计数: " << ptr4.use_count() << endl;
        std::shared_ptr<int> ptr5 = std::move(ptr2);
        cout << "ptr5管理的内存引用计数: " << ptr5.use_count() << endl;
    
  • 通过std::make_shared初始化

// 使用智能指针管理一块 int 型的堆内存, 内部引用计数为 1
    shared_ptr<int> ptr1 = make_shared<int>(520);
    cout << "ptr1管理的内存引用计数: " << ptr1.use_count() << endl;
  • 通过 reset方法初始化
	shared_ptr<int> ptr5;
    ptr5.reset(new int(250));
    cout << "ptr5管理的内存引用计数: " << ptr5.use_count() << endl;

当智能指针管理的内存对应的引用计数变为0的时候,这块内存就会被智能指针析构掉了。另外,我们在初始化智能指针的时候也可以自己指定删除动作,这个删除操作对应的函数被称之为删除器,这个删除器函数本质是一个回调函数,我们只需要进行实现,其调用是由智能指针完成的。

#include <iostream>
#include <memory>
using namespace std;

// 自定义删除器函数,释放int型内存
void deleteIntPtr(int* p)
{
    delete p;
    cout << "int 型内存被释放了...";
}

int main()
{
    shared_ptr<int> ptr(new int(250), deleteIntPtr);
    return 0;
}

注意:在C++11中使用shared_ptr管理动态数组时,需要指定删除器,因为std::shared_ptr的默认删除器不支持数组对象。在删除数组内存时,除了自己编写删除器,也可以使用C++提供的std::default_delete<T>()函数作为删除器,这个函数内部的删除功能也是通过调用delete来实现的,要释放什么类型的内存就将模板类型T指定为什么类型即可。

4.独占智能指针

std::unique_ptr是一个独占型的智能指针,它不允许其他的智能指针共享其内部的指针,可以通过它的构造函数初始化一个独占智能指针对象,但是不允许通过赋值将一个unique_ptr赋值给另一个unique_ptr

std::unique_ptr不允许复制,但是可以通过函数返回给其他的std::unique_ptr,还可以通过std::move来转译给其他的std::unique_ptr,这样原始指针的所有权就被转移了,这个原始指针还是被独占的。

使用reset方法可以让unique_ptr解除对原始内存的管理,也可以用来初始化一个独占的智能指针。

5.弱引用智能指针

弱引用智能指针std::weak_ptr可以看做是shared_ptr的助手,它不管理shared_ptr内部的指针。std::weak_ptr没有重载操作符*和->,因为它不共享指针,不能操作资源,所以它的构造不会增加引用计数,析构也不会减少引用计数,它的主要作用就是作为一个旁观者监视shared_ptr中管理的资源是否存在。

#include <iostream>
#include <memory>
using namespace std;

int main()
{
    std::shared_ptr<int> sp1(new int(10));
    std::shared_ptr<int> sp2(sp1);
    std::weak_ptr<int> wp(sp2);
    //输出和 wp 同指向的 shared_ptr 类型指针的数量
    cout << wp.use_count() << endl;
    //释放 sp2
    sp2.reset();
    cout << wp.use_count() << endl;
    //借助 lock() 函数,返回一个和 wp 同指向的 shared_ptr 类型指针,获取其存储的数据
    cout << *(wp.lock()) << endl;
    return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

墨城烟柳ベ旧人殇

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值