C++:类和对象(下)---对类和对象深入一些的理解

简介: C++:类和对象(下)---对类和对象深入一些的理解

构造函数

初始化列表

前面已然介绍过构造函数,但并未完全结束,构造函数有很多种写法,有带缺省参数的,有全缺省的,不带缺省参数的…但用前面的方法,都是对里面成员变量的一种赋值,也就是说,这是给类中每一个成员变量一个初始值

但这并不是初始化,这里要分清楚什么是初始化,什么是赋初值

简单来说,它们的一个区别就是初始化只能初始化一次,但是赋值可以多次赋值

因此构造函数中就引入了初始化列表的概念,初始化列表可以做到给类内的成员函数初始化,下面写一个具体的例子来表示

#include <iostream>
using namespace std;
class Date
{
public:
  Date(int year = 1900, int month = 1, int day = 1)
    : _year(year)
    , _month(month)
    , _day(day){}
  void Print()
  {
    cout << _year << "/" << _month << "/" << _day << endl;
  }
private:
  int _year;
  int _month;
  int _day;
};

上面就是对初始化列表的概述,通过这样的写法可以对参数进行初始化

这里就体现了初始化和赋值的区别:

  1. 如果采用的是初始化,那么每个成员变量在初始化列表中只能初始化一次
  2. 类内如果有引用成员函数,const成员变量和自定义成员且没有默认构造函数时,就要写在初始化列表的位置
  3. 尽量使用初始化列表进行初始化,不管是否用初始化列表,对于自定义类型的成员变量都会先使用初始化列表进行初始化
  4. 成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关

下面来对第二条进行解读,同时也能更好的理解上面的原理

  1. 有const成员变量
#include <iostream>
using namespace std;
class Date
{
public:
  //Date(int year = 1900, int month = 1, int day = 1)
  //  : _year(year)
  //  , _month(month)
  //  , _day(day){}
  Date(int year = 1900, int month = 1, int day = 1, int tmp = 1)
  {
    _year = year;
    _month = month;
    _day = day;
    _tmp = tmp;
  }
  void Print()
  {
    cout << _year << "/" << _month << "/" << _day << endl;
  }
private:
  int _year;
  int _month;
  int _day;
  const int _tmp;
};

这里的构造函数对吗?很显然是不能编译通过的,原因也很简单,这里的_tmp是const修饰的变量,怎么能给它赋值?解决方法有两个,第一个是可以给它在声明的时候就给它一个值,这是可以的,但使用初始化列表可以完美解决这个问题

#include <iostream>
using namespace std;
class Date
{
public:
  Date(int year = 1900, int month = 1, int day = 1, int tmp = 1)
    : _year(year)
    , _month(month)
    , _day(day)
    , _tmp(tmp) {}
  //Date(int year = 1900, int month = 1, int day = 1, int tmp = 1)
  //{
  //  _year = year;
  //  _month = month;
  //  _day = day;
  //  //_tmp = tmp;
  //}
  void Print()
  {
    cout << _year << "/" << _month << "/" << _day << endl;
    cout << "const tmp  " << _tmp << endl;;
  }
private:
  int _year;
  int _month;
  int _day;
  const int _tmp = 1;
};

这里也能更好理解赋值和初始化的区别,从const上就可以很好的体现出来

  1. 引用

还有一大使用场景就是在引用中可以体现,这里就不再写错误的示范了

class Date
{
public:
  Date(int year = 1900, int month = 1, int day = 1, int tmp = 1)
    : _year(year)
    , _month(month)
    , _day(day)
    , _tmp(tmp) {}
  //Date(int year = 1900, int month = 1, int day = 1, int tmp = 1)
  //{
  //  _year = year;
  //  _month = month;
  //  _day = day;
  //  //_tmp = tmp;
  //}
  void Print()
  {
    cout << _year << "/" << _month << "/" << _day << endl;
    cout << "const tmp  " << _tmp << endl;;
  }
private:
  int _year;
  int _month;
  int _day;
  int& _tmp;
};

explicit关键字

C++提供了关键字explicit,可以阻止不应该允许的经过转换构造函数进行的隐式转换的发生,声明为explicit的构造函数不能在隐式转换中使用

在了解explicit前,就必须先知道隐式转换是什么了

显式转换一定不陌生,就是强行转换,因此隐式转换就是编译器自动发生转换

比如说下面这样的例子:

int main()
{
  int i = 0;
  double d = 1.2;
  int& p = d;
  return 0;
}

这里编译是通不过的,原因是因为d是double类型因此编译不通过吗?其实不然

在编译器的处理中,会把d先生成一个临时变量,再进行赋值,而我们都知道临时变量是具有常性的,因此这里并不能把一个常属性的数交给引用来处理

因此这里只需要加上const,进行权限的缩小,就可以用常引用接纳常性变量了

const int& p = d;

其实,上述过程中把变量d从double类型转换到int类型就是一个隐式类型转换,我们并没有进行转换,但是编译器依旧自己转换了,并且生成了临时变量导赋值失败

而explicit的存在就是不能让隐式转换存在,因此我们不能把类型局限在int和double类型,要加入类的类型

class A
{
public:
  A(int a = 10) :_a(a) {}
private:
  int _a;
};
int main()
{
  A a;
  a = 2;
  return 0;
}

看看上面的代码发生了什么?把2赋给了一个类?如果你知道了隐式类型转换,那么就不难理解这段代码,这就是把2进行隐式类型转换,把它转换成了类A,里面成员变量_a的值是2,然后又进行了一次赋值

那么explicit就可以登场了,在初始化列表前面加上explicit:

class A
{
public:
  explicit A(int a = 10) :_a(a) {}
private:
  int _a;
};
int main()
{
  A a;
  a = 2;
  return 0;
}

此时,隐式转换就不存在了,因此这里的赋值就不复存在了,因为2在这里真的就是一个数字2

匿名对象

C++中引入了匿名对象的概念,简单来说就是没有名字的对象

class A
{
public:
  explicit A(int a = 10) :_a(a) {}
private:
  int _a;
};
int main()
{
  A(2);
  return 0;
}

这里看起来很奇怪,但这是可以通过的,C++内部是允许这样做的,它和有名对象的区别在于:

匿名对象的生命周期只在这一行,结束后就进行析构,而正常生成的对象需要在main函数结束后才会进行析构,普通生成的对象的生命周期是它的局部域

那匿名对象有什么用?

假设有这样的情景

class A
{
public:
  explicit A(int a = 10) :_a(a) {}
  void Print()
  {
    cout << "Print" << endl;
  }
private:
  int _a;
};
int main()
{
  A a1;
  a1.Print();
  A().Print(); // 匿名对象
  return 0;
}

假如这里我只需要调用类内的成员函数print,但如果正常来说我是需要创建一个对象,再通过对象去引用这个类内的成员函数,这是十分繁琐的,如果使用匿名对象,我不关注这个对象是谁,这个对象是多少,我只关心能不能引用类内的成员函数,因此就可以这样使用,相比起使用定义对象的方法来看,这样的方法的生命周期更短

相关文章
|
18天前
|
编译器 C++
C++之类与对象(完结撒花篇)(上)
C++之类与对象(完结撒花篇)(上)
31 0
|
13天前
|
存储 编译器 对象存储
【C++打怪之路Lv5】-- 类和对象(下)
【C++打怪之路Lv5】-- 类和对象(下)
19 4
|
13天前
|
编译器 C语言 C++
【C++打怪之路Lv4】-- 类和对象(中)
【C++打怪之路Lv4】-- 类和对象(中)
16 4
|
13天前
|
存储 安全 C++
【C++打怪之路Lv8】-- string类
【C++打怪之路Lv8】-- string类
14 1
|
14天前
|
存储 编译器 C语言
【C++打怪之路Lv3】-- 类和对象(上)
【C++打怪之路Lv3】-- 类和对象(上)
14 0
|
18天前
|
编译器 C++ 数据库管理
C++之类与对象(完结撒花篇)(下)
C++之类与对象(完结撒花篇)(下)
28 0
|
18天前
|
编译器 C++
C++之类与对象(3)(下)
C++之类与对象(3)(下)
30 0
|
18天前
|
编译器 C++
C++之类与对象(3)(上)
C++之类与对象(3)
15 0
|
18天前
|
编译器 C++
C++之类与对象(2)
C++之类与对象(2)
28 0
|
18天前
|
存储 编译器 C++
C++之类与对象(1)(下)
C++之类与对象(1)(下)
24 0