c++语言基础(一)

一、构造函数

不同的语法使用场景会自动调用功能不同的构造函数。c++的构造函数主要有如下几种类型:无参(多参)构造函数、拷贝构造函数、移动构造函数、赋值构造函数(赋值语句)、移动赋值构造函数(移动赋值语句)、类型转换构造函数;

  • 无参构造函数:无参构造函数称为默认构造函数;如果未定义任何构造函数,编译器默认会生成一个

如下情况会调用无参构造函数(多个参数的构造函数也一样)

class MyClass
{
public:
  MyClass():myInt_val(0),myString(""){}
  MyClass(int val,string str):myInt_val(val),myString(str){}
private:
  int myInt_val;
  string myString;
};

MyClass myObj1;
MyClass myObj11(3,"str");
MyClass *myObj2 = new MyClass;
MyClass *myObj3 = new MyClass();
MyClass *myObj33 = new MyClass(3,"str");
MyClass *myObj4 = new MyClass[2];
  • 拷贝构造函数/移动构造函数:=进行初始化、作为函数参数、函数返回值等等时调用

拷贝构造函数,参数为自身类型的引用类型或者常引用类型称为拷贝构造函数。如果未定义,编译器会默认生成一个拷贝构造函数,且为浅拷贝;

移动构造函数,参数为类的右值引用类型

class MyClass
{
public:
  MyClass(const MyClass& obj):myInt_val(obj.myInt_val),
        myString(string(obj.myString)){
        cout<< "这里是拷贝构造函数" <<endl;
  }
  MyClass(MyClass&& obj):myInt_val(obj.myInt_val){
        myString = std::move(obj.myString);
        cout<< "这里是移动构造函数" <<endl;
  }
private:
  int myInt_val;
  string myString;

  static MyClass funcArg(MyClass obj,MyClass& obj2) {
    MyClass obj3 = obj;
    return obj3;
  }
  static MyClass& funcArg2(MyClass* obj) {
    MyClass *retObj = new MyClass;
    *retObj = *obj;
    return *retObj;
  }
};

MyClass myObj1;
// 拷贝构造函数
MyClass myObj5 = myObj1;
// 参数为类类型时:会调用拷贝构造函数。
// 参数或者返回值为类引用类型时:不调用构造函数
// 返回值为类类型时:添加-fno-elide-constructors,调用拷贝或者移动构造函数;不添加,编译器会优化,不调用任何构造函数
funcArg(myObj1, myObj1);
// 参数为指针、返回值引用或者指针:不调用任何构造函数
funcArg2(&myObj1);
  • 赋值构造函数/移动赋值构造函数: 主要当使用=进行赋值时调用

赋值构造函数:即进行对象赋值时调用的构造函数。如果未定义,编译器会默认生成一个,简单进行遍历的赋值

1、参数必须为引用类型或者常引用类型

2、返回值必须是引用类型

3、必须return *this; 保证前后为一个对象

移动赋值构造函数,与赋值构造函数区别是参数为右值类型,其它一样

如果都未定义,编译器都会默认各生成一个

class MyClass
{
public:
  MyClass& operator =(const MyClass & obj){
        cout<< "这里是赋值构造函数" <<endl;
        myInt_val = obj.myInt_val;
        myString = obj.myString;

        return *this;
  }
  MyClass& operator =(const MyClass && obj){
        cout<< "这里是移动赋值构造函数" <<endl;
        myInt_val = obj.myInt_val;
        myString = std::move(obj.myString);
        return *this;
    }
private:
  int myInt_val;
  string myString;
};

MyClass myObj1;
// 当未定义移动构造函数或者移动赋值语句,调用赋值语句
// 否则调用移动赋值语句
myObj1 = global_func();
  • 类型转换构造函数:类型转换构造函数,参数只有一个并且非自身类型,不常用
class MyClass
{
public:
    MyClass(int val) {
        myInt_val = val;
        cout<< "这里类型转换构造函数" <<endl;
    }
private:
  int myInt_val;
  string myString;
};

// 显示调用转换构造函数MyClass(int val)
MyClass myObj1(3);
  • 个人思考:这么多构造函数,其实大部分情况下使用编译器默认的即可。对于频率调用较高的拷贝构造函数,当类中成员使用智能指针后,拷贝构造函数也只是指针的赋值,不存在效率问题,所以也无需定义移动构造函数了。就像oc的拷贝一样,大部分情况下也是这样。

二、智能指针

c++中有四个智能指针,auto_ptr,shared_ptr,weak_ptr,unique_ptr,其中后三个是c++11支持的,第一个已经被弃用,属于c++98的标准

为什么要用智能指针?用来自动的实现对象的内存管理,不需要手写delete语句

实现原理:智能指针实际上是一个模板类,魔板类通过引用计数管理对应的指针内存,当类在离开作用域时会自动执行析构函数,这个模板类在析构函数中判断如果引用计数为0,则释放该指针对应的内存。

1、shared_ptr:多个智能指针可以指向同一个指针对象,每次引用计数都会+1

2、weak_ptr:仅仅指向指针对象,引用计数不变,用来解决shared_ptr导致的循环引用问题

3、unique_ptr: 每一个智能指针只能同时指向一个指针对象,也就是它的引用计数最多为1

使用例子:

class SmartTestClass {
public:
    SmartTestClass(){
        cout<<"这里是构造函数 SmartTestClass \n";
    }

    ~SmartTestClass(){
        cout<<"这里是析构函数 SmartTestClass \n";
    }
};
class Smart_ptr_Test
{
public:
    Smart_ptr_Test(string s)
    {
        str = s;
        sharedClass = std::make_shared<SmartTestClass>();
        cout << str<<" Test creat\n";
    }
    ~Smart_ptr_Test()
    {
        cout<<"Test delete:"<<str<<" buffer "<<buffer<<endl;
    }
    string& getStr()
    {
        return str;
    }
    void setStr(string s)
    {
        str = s;
    }
    void print()
    {
        cout<<str<<" buffer "<<endl;
    }

    std::shared_ptr<SmartTestClass> getPtr(){
        return sharedClass;
    }

    string buffer;
private:
    string str;

    std::shared_ptr<SmartTestClass> sharedClass;
};

unique_ptr<Smart_ptr_Test> fun()
{
    return unique_ptr<Smart_ptr_Test>(new Smart_ptr_Test("unique_ptr_789"));
}
unique_ptr<Smart_ptr_Test> utest(new Smart_ptr_Test("unique_ptr 123"));
unique_ptr<Smart_ptr_Test> utest2 = make_unique<Smart_ptr_Test>("unique_ptr 456");

ptest->print();
if(ptest == NULL)cout<<"ptest = NULL\n";
Smart_ptr_Test* p = ptest2.release();
p->print();
ptest.reset(p);
ptest->print();

ptest2 = std::move(ptest);
//这里可以用 =,因为unique_ptr实现了移动构造函数
ptest2 = fun();
// 编译报错,因为unique_ptr同时智能指向一个指针对象
ptest2 = ptest;
  • 个人思考:智能指针确实可以解放对于动态内存的释放操作,但是也会造成循环引用得问题。不过c++循环引用只有相互引用才会产生,这种情况应该比较少见,不管怎样,极力推荐

三、lamda表达式

即匿名函数,闭包表达式

  • 语法参数:

[捕获列表 ] ( 参数 列表) [可选值] -> 返回值 { 函数体 }

其中捕获列表的含义如下:

[] 不捕获任何变量。

[&] 捕获外部作用域中所有变量,并作为引用在函数体中使用(按引用捕获)。

[=] 捕获外部作用域中所有变量,并作为副本在函数体中使用(按值捕获)。

[=,&foo] 按值捕获外部作用域中所有变量,并按引用捕获 foo 变量。

[bar] 按值捕获 bar 变量,同时不捕获其他变量。

[this] 捕获当前类中的 this 指针,让 lambda 表达式拥有和当前类成员函数同样的访问权限。如果已经使用了 & 或者 =,就默认添加此选项。捕获 this 的目的是可以在 lamda 中使用当前类的成员函数和成员变量。

可选值:默认没有,可以为mutable 代表可以在函数体内修改外部补货变量的值,和OC的__block类似

  • 使用例子:
//全局变量
int all_num = 0;

/** lambda表达式的实现原理:在编译期间,编译器生成一个局部类,其中封装了捕获的变量以及要执行的代码。
 */
void lambda_main()
{    
    //局部变量
    int a = 1;
    int b = 2;
    cout << "lambda1:\n";
    auto lambda1 = [=] () mutable {
        //全局变量可以访问甚至修改
        all_num = 10;
        a = 20;
        //加入mutable后则可以对外部变量进行修改;否则编译器会报错
        cout << a << " "
        << b << " "
        << endl;
    };
    lambda1();
    cout << all_num << " " << num_1 <<endl;
    cout << "lambda2:\n";
    auto lambda2 = [&]{
        all_num = 100;
        a = 10;
        b = 20;
        cout << a << " "
        << b << " "
        << endl;
    };
    lambda2();
    cout << all_num << endl;
}
  • 原理:

在编译期间,编译器生成一个局部类,其中封装了捕获的变量以及要执行的代码。

假设lamda表达式捕捉了两个整数变量a和b、编译器的实现主要分为如下几个步骤:

1、创建 lambda 类,实现构造函数,使用 lambda 表达式的函数体重载 operator()(所以 lambda 表达式 也叫匿名函数对象;

2、创建 lambda 对象

3、通过对象调用 operator()

伪代码如下:

class lambda_xxxx
{
private:
    int a;
    int b;
public:
    lambda_xxxx(int _a, int _b) :a(_a), b(_b)
    {
    }
    bool operator()(int x, int y) throw()
    {
        函数体......
    }
};

void LambdaDemo()
{
  int a = 1;
  int b = 2;
    auto lambda1 = [=] () {
      ......
        return true
    };
    lambda1();
    // 转化为如下:
    int a = 1;
    int b = 2;
    lambda_xxxx lambda = lambda_xxxx(a, b);
    bool ret = lambda.operator()(3, 4);
}
  • 个人思考:lamada表达式会像oc/Swift那样造成循环引用吗?

不会,首先c++中造成循环引用得满足两个条件1、使用shared_ptr指针;2、相互引用;对于第2点,lamda表达式需要在捕获this的同时还得作为this所在类的成员变量,但是c++的静态特性决定了对于捕获了this的lamda表达式无法作为this所在类的成员变量,所以无法相互引用,即不会构成循环引用

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,948评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,371评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,490评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,521评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,627评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,842评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,997评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,741评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,203评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,534评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,673评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,339评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,955评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,770评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,000评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,394评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,562评论 2 349

推荐阅读更多精彩内容

  • 1.指针和引用的区别 1.指针有自己的一块空间,而引用只是一个别名;2.使用sizeof看一个指针的大小是4,而引...
    微糖去冰_阅读 756评论 0 6
  • C++兼容C语言,所以基础部分可以 参考C语言基础快查[https://www.jianshu.com/p/7bd...
    薛定喵的鹅阅读 432评论 0 0
  • 相关章节 「C++类的特殊成员函数(1):构造函数」中“3 隐式类类型转换” 考虑下面的例子: 在计算ival的值...
    徐不缓阅读 1,043评论 0 0
  • C++ 语言基础 1. 局部变量和全局变量能否重名? 局部变量是定义在函数内部的变量。全局变量是定义在函数之外的变...
    MinoyJet阅读 664评论 0 2
  • C++语言的八股文 C++面向对象的特性 封装——隐藏对象的属性和实现细节,仅对外公开接口和对象进行交互,将数据和...
    wolfaherd阅读 816评论 1 3