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

相关文章
|
3天前
|
设计模式 安全 编译器
【C++11】特殊类设计
【C++11】特殊类设计
22 10
|
8天前
|
C++
C++友元函数和友元类的使用
C++中的友元(friend)是一种机制,允许类或函数访问其他类的私有成员,以实现数据共享或特殊功能。友元分为两类:类友元和函数友元。类友元允许一个类访问另一个类的私有数据,而函数友元是非成员函数,可以直接访问类的私有成员。虽然提供了便利,但友元破坏了封装性,应谨慎使用。
39 9
|
3天前
|
存储 编译器 C语言
【C++基础 】类和对象(上)
【C++基础 】类和对象(上)
|
12天前
|
编译器 C++
【C++】string类的使用④(字符串操作String operations )
这篇博客探讨了C++ STL中`std::string`的几个关键操作,如`c_str()`和`data()`,它们分别返回指向字符串的const char*指针,前者保证以&#39;\0&#39;结尾,后者不保证。`get_allocator()`返回内存分配器,通常不直接使用。`copy()`函数用于将字符串部分复制到字符数组,不添加&#39;\0&#39;。`find()`和`rfind()`用于向前和向后搜索子串或字符。`npos`是string类中的一个常量,表示找不到匹配项时的返回值。博客通过实例展示了这些函数的用法。
|
12天前
|
存储 C++
【C++】string类的使用③(非成员函数重载Non-member function overloads)
这篇文章探讨了C++中`std::string`的`replace`和`swap`函数以及非成员函数重载。`replace`提供了多种方式替换字符串中的部分内容,包括使用字符串、子串、字符、字符数组和填充字符。`swap`函数用于交换两个`string`对象的内容,成员函数版本效率更高。非成员函数重载包括`operator+`实现字符串连接,关系运算符(如`==`, `&lt;`等)用于比较字符串,以及`swap`非成员函数。此外,还介绍了`getline`函数,用于按指定分隔符从输入流中读取字符串。文章强调了非成员函数在特定情况下的作用,并给出了多个示例代码。
|
12天前
|
C++
【C++】string类的使用④(常量成员Member constants)
C++ `std::string` 的 `find_first_of`, `find_last_of`, `find_first_not_of`, `find_last_not_of` 函数分别用于从不同方向查找目标字符或子串。它们都返回匹配位置,未找到则返回 `npos`。`substr` 用于提取子字符串,`compare` 则提供更灵活的字符串比较。`npos` 是一个表示最大值的常量,用于标记未找到匹配的情况。示例代码展示了这些函数的实际应用,如替换元音、分割路径、查找非字母字符等。
|
12天前
|
C++
C++】string类的使用③(修改器Modifiers)
这篇博客探讨了C++ STL中`string`类的修改器和非成员函数重载。文章介绍了`operator+=`用于在字符串末尾追加内容,并展示了不同重载形式。`append`函数提供了更多追加选项,包括子串、字符数组、单个字符等。`push_back`和`pop_back`分别用于在末尾添加和移除一个字符。`assign`用于替换字符串内容,而`insert`允许在任意位置插入字符串或字符。最后,`erase`函数用于删除字符串中的部分内容。每个函数都配以代码示例和说明。
|
12天前
|
安全 编译器 C++
【C++】string类的使用②(元素获取Element access)
```markdown 探索C++ `string`方法:`clear()`保持容量不变使字符串变空;`empty()`检查长度是否为0;C++11的`shrink_to_fit()`尝试减少容量。`operator[]`和`at()`安全访问元素,越界时`at()`抛异常。`back()`和`front()`分别访问首尾元素。了解这些,轻松操作字符串!💡 ```
|
12天前
|
存储 编译器 Linux
【C++】string类的使用②(容量接口Capacity )
这篇博客探讨了C++ STL中string的容量接口和元素访问方法。`size()`和`length()`函数等价,返回字符串的长度;`capacity()`提供已分配的字节数,可能大于长度;`max_size()`给出理论最大长度;`reserve()`预分配空间,不改变内容;`resize()`改变字符串长度,可指定填充字符。这些接口用于优化内存管理和适应字符串操作需求。
|
17天前
|
C++
【C++】日期类Date(详解)②
- `-=`通过复用`+=`实现,`Date operator-(int day)`则通过创建副本并调用`-=`。 - 前置`++`和后置`++`同样使用重载,类似地,前置`--`和后置`--`也复用了`+=`和`-=1`。 - 比较运算符重载如`&gt;`, `==`, `&lt;`, `&lt;=`, `!=`,通常只需实现两个,其他可通过复合逻辑得出。 - `Date`减`Date`返回天数,通过迭代较小日期直到与较大日期相等,记录步数和符号。 ``` 这是236个字符的摘要,符合240字符以内的要求,涵盖了日期类中运算符重载的主要实现。