C++类和对象下(初始化列表,静态成员,explicit关键字,友元)(上)

简介: C++类和对象下(初始化列表,静态成员,explicit关键字,友元)

一.初始化列表

1.为什么会有初始化列表

我们在Date中添加了两种成员变量:

分别是引用类型和const类型

为什么编译器会报错呢?

是不是因为编译器默认生成的构造函数不行呢?

那我们自己去实现一下怎么样?

还是不行:它说引用和const类型的对象定义时必须初始化

对啊,因为引用不能改变指向,所以必须在初始化引用的时候就要指定好对象

const类型的变量的值是不能修改的,因此初始化时也必须设好值

那么我们应该怎么办呢?

针对于这个问题C++创始人规定了初始化列表这一语法:

为了解决有些成员变量在定义的时候必须初始化!!!

2.初始化列表的语法形式

Date(int year = 1, int month = 2, int day = 3)
  :_year(year)
  , _month(month)
  , _day(day)
  , _ref(_year)
  , _cint(_year)
  {
    //函数体内可以继续进行代码的书写
  };
第一个是冒号
后面是逗号

这样我们的程序就可以通过了

其实初始化列表还解决了下面这个问题

3.没有默认构造函数的自定义成员变量

C++类和对象中(构造函数,析构函数,拷贝构造函数)详解

那么问题来了:

如果这个Stack类没有默认构造函数呢?

会发生编译错误

那么怎么办呢?

其实我们仔细想一想:

这个MyQueue类中的Stack类没有了默认构造函数,不就意味着这个Stack类在我们这个MyQueue类定义的时候必须初始化吗?

所以这个时候初始化列表就派上用场了

MyQueue(int capacity1, int capacity2,int size)
  :_st1(capacity1)
  ,_st2(capacity2)
  ,_size(size)
  {
  };

这样就可以解决这个问题了

其实就算是你这个Stack类有默认构造函数,但是如果我就是想自己去传参调用你的这个默认构造函数.也是可以这样做的

如果没有初始化列表这一语法,我们在声明的时候去调用Stack类的传参构造函数呢?

会直接报错

4.初始化列表是成员变量定义的地方

那么初始化列表到底是什么身份呢?

这么强大

初始化列表是成员变量定义的地方

我们之前提到过:

也就是说只要你这个类有成员变量,就一定会走构造函数,只要你走构造函数,就一定会走构造函数当中的初始化列表

而成员变量就是在初始化列表中定义的,也就是在初始化列表中分配的空间!!

5.初始化列表可以跟函数体内定义搭配使用

那么既然初始化列表这么强大,可不可以不要函数体了呢?

构造函数只留一个初始化列表不就行了吗?

当然不可以

比如:下面的Stack类

Stack(int capacity = 1)
    :_a((int*)malloc(sizeof(int)*capacity))
    ,_capacity(capacity)
    ,_top(0)
  {};

你这不是也可以吗,这是好的情况

万一我申请空间太大,申请失败了呢?

wzs::Stack st(1000000000000000000);

我们在这里直接申请这么大的空间

那么该怎么办呢?

在初始化列表里面去检查吗?

初始化列表是定义成员变量给成员变量开辟空间的地方,

你在里面检查_a是否等于空指针

你这不是大材小用吗

直接让_a在函数体内初始化它不香吗?

也就是这样

Stack(long long capacity = 1)
  :_capacity(capacity)
  ,_top(0)
{
  _a = ((int*)malloc(sizeof(int) * capacity));
  if (_a == nullptr)
  {
    perror("malloc fail");
    exit(-1);
  }
};
wzs::Stack st(1000000000000000000);
• 1

开辟空间失败:

开辟空间成功:

wzs::Stack st(10);

也就是说初始列表和构造函数的函数体是相辅相成的,

类似的关系:引用和指针

所以日常中:对于成员变量来说

大多数情况下我们都是用初始化列表搞定

剩下那些只能用函数体去初始化的就用函数体去搞定

6.初始化列表执行的顺序

成员变量在类中声明次序就是其在初始化列表中的初始化顺序,

与其在初始化列表中的先后次序无关

大家可以看一下这个题,这是《剑指offer》上的一道题目

答案:

n1:随机值

n2:0

因为成员变量在类中声明次序就是其在初始化列表中的初始化顺序,

所以n1先被初始化,然后n2才被初始化

也就是说

它们在初始化列表中的执行次序如下:

n1 = n2+2;
n2 = 0;

而n1和n2在还没有被初始化时都是随机值

因此n1就是随机值,而n2被初始化为了0

7.总结+建议

这个注意当中的第一条和这个初始化列表跟函数体内定义的区别这一条:

也就是说如果有人这么写:

也就是说初始化列表中,成员变量只能初始化一次,因此叫做初始化列表

而函数体中,成员变量可以赋值很多次,所以在函数体中只能被称为赋值初值,而不能被称为初始化!!!

也就是说对于我们的这个构造函数来说

Date(int year = 1, int month = 2, int day = 3)
  : _ref(_year)
  , _cint(_year)
  {
    _year=year;
    _month=month;
    _day=day;
  };
private:
  int _year;
  int _month;
  int _day=26;
  int& _ref;
  const int _cint;

本质上编译器是这样进行的:

Date(int year = 1, int month = 2, int day = 3)
  :_year(随机值)
  ,_month(随机值)
  ,_day(缺省值26)
  ,_ref(_year)
  , _cint(_year)
  {
    //下面是重新对_year,_month,_day进行再次赋值
    _year=year;
    _month=month;
    _day=day;
  };
private:
  int _year;
  int _month;
  int _day=26;
  int& _ref;
  const int _cint;

也就是说构造函数中:

初始化列表是成员变量定义的地方,每一个成员变量都需要走初始化列表

如果在初始化列表当中我们没有显式定义该成员变量,就会使用该成员变量声明时给的缺省值,如果没有缺省值,那么就会使用编译器给的随机值

函数体内定义其实本质上是给成员变量进行第二次赋值

也就是像这样

int var = 随机值; //初始化列表
var=1;//函数体内定义

其实翻来覆去就只有一句话:初始化列表是成员变量定义的地方

函数体内是成员变量可以进行二次赋值的地方

二.静态成员变量和静态成员函数

我们出现了一个需求:计算一个类实例化出了多少个对象

1.static成员变量的引出

其实方法很简单:只需要定义一个全局变量count=0,然后在这个类的所有的构造函数当中都让这个count++

最后count的数值就是该类实例化出的对象的个数

这样是可行的,但是并不好

为什么呢?

因为这个变量count是一个全局变量,也就是说这个count可以在程序的任意位置访问并修改

所以有可能会出现这种情况:

这个func函数中它成功地把我count这个全局变量给修改了

导致最后得出的答案少了1

也就是我们无法避免这种极端情况的发生

那怎么办呢?

而且把这个count定义为成员变量也是不行的

因为每一个对象都有自己的count,都是++的自己的count

那么有没有一种成员变量是为我这个类实例化出的所有的对象所共享的呢?

static成员变量就出现了

2.static成员变量的特性

既然我们了解了static成员变量的特性

3.对于解决以上需求: static成员变量的不足

那么我们该怎么解决上面那个需求呢?

但是还是有一个问题

我在这里是把_count这个静态成员变量的访问属性设置为公有了,但是也防不住会出现下面这种情况:

是,你是把_count这个全局变量放到了你A这个类的内部

但是你把它的访问属性设置为了公有,

因此我func想改你这个_count,我只需要加上你A这个类域,我依然能改,你拦不住我

那怎么办呢?

把_count放到私有属性下:

相关文章
|
11天前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
40 4
|
12天前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
35 4
|
1月前
|
存储 编译器 对象存储
【C++打怪之路Lv5】-- 类和对象(下)
【C++打怪之路Lv5】-- 类和对象(下)
27 4
|
4天前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
24 5
|
1月前
|
编译器 C语言 C++
【C++打怪之路Lv4】-- 类和对象(中)
【C++打怪之路Lv4】-- 类和对象(中)
24 4
|
1月前
|
存储 安全 C++
【C++打怪之路Lv8】-- string类
【C++打怪之路Lv8】-- string类
21 1
|
1月前
|
存储 编译器 C++
【C++类和对象(下)】——我与C++的不解之缘(五)
【C++类和对象(下)】——我与C++的不解之缘(五)
|
1月前
|
编译器 C++
【C++类和对象(中)】—— 我与C++的不解之缘(四)
【C++类和对象(中)】—— 我与C++的不解之缘(四)
|
1月前
|
C++
C++番外篇——对于继承中子类与父类对象同时定义其析构顺序的探究
C++番外篇——对于继承中子类与父类对象同时定义其析构顺序的探究
53 1
|
1月前
|
编译器 C语言 C++
C++入门4——类与对象3-1(构造函数的类型转换和友元详解)
C++入门4——类与对象3-1(构造函数的类型转换和友元详解)
20 1