从C语言到C++⑦(第二章_类和对象_下篇)初始化列表+explicit+static成员+友元+内部类+匿名对象(上)

简介: 从C语言到C++⑦(第二章_类和对象_下篇)初始化列表+explicit+static成员+友元+内部类+匿名对象

1. 构造函数的初始化列表

我们知道,引用在定义时必须初始化,常量也必须在定义时初始化,

因为常量只有一次初始化的机会,就是在定义的时候。

类里面哪里是初始化的地方?

我们之前学习创建对象时,编译器通过调用构造函数,给对象赋初值。

class Date
{
public:
  Date(int year = 1, int month = 1, int day = 1)
  {
    _year = year;
    _month = month;
    _day = day;
  }
private:
  int _year;
  int _month;
  int _day;
};

虽然上述构造函数调用之后,对象中已经有了一个初始值,

但是不能将其称为对对象中成员变量 的初始化,构造函数体中的语句只能将其称为赋初值

而不能称作初始化。因为初始化只能初始 化一次,而构造函数体内可以多次赋值。

类里面给成员变量提供了一个初始化的地方:初始化列表

1.1 初始化列表概念

初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,

每个"成员变量"后面跟一个放在括号中的初始值或表达式。

#include <iostream>
using namespace std;
 
class Date
{
public:
  Date(int year = 1, 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;
};
 
int main()
{
  Date d(2023, 5, 7);
  d.Print();
  return 0;
}

1.2 初始化列表注意事项

每个成员变量再初始化列表中只能出现一次,即 初始化只能初始化一次。

类中包含以下成员,必须放在初始化列表位置进行初始化:

(编译器会把前两个在其它位置的“初始化”当做声明,即给初始化列表缺省值,


前面提到的C++11打的补丁时给内置类型的缺省值也是给初始化列表的)


1.  const成员变量                       const int _N;

2.  引用成员变量                         int& ref;

3.  没有默认构造函数的自定义类型成员变量     A _aa;  

③ 尽量显示使用初始化列表初始化,因为不管你是否使用初始化列表,编译器都会默认生成。

使用示例:

class Time
{
public:
  Time(int hour = 0)
  {
    _hour = hour;
  }
private:
  int _hour;
};
 
class Date
{
public:
 
  Date(int year, int hour, int& x)
    :_year(year)
    ,_t(hour)
    , _N(10)
    , _ref(x)
  {
 
  }
private:
  int _year;
  Time _t;
  const int _N;
  int& _ref;
};

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


与其在初始化列表中出现的顺序无关。


下面的程序输出什么?


A . 输出 1   1

B . 程序崩溃

C . 编译不通过

D . 输出 1   随机值

class A
{
public:
  A(int a)
    :_a1(a)
    , _a2(_a1)
  {
 
  }
 
  void Print() 
  {
    cout << _a1 << " " << _a2 << endl;
  }
private:
  int _a2;
  int _a1;
};
int main() 
{
  A aa(1);
  aa.Print();
}

因为先声明的是 _a2,所以在初始化列表里我们先初始化的是 _a2,

因为这里是 _a2(_a1), _a1 此时还是没有得到传过去的1,

此时还是随机值,所以 _a2 就被初始化成随机值了。

按照声明顺序然后是 _a1, _a1 接收到了1,自然会初始化成1。

最后按顺序打印:1 和 随机值。

如果先声明_a1的话就会打印两个1。

2. 构造函数的explicit关键字

构造函数不仅可以构造与初始化对象,

对于单个参数或者除第一个参数无默认值其余均有默认值的构造函数,还具有类型转换的作用

2.1 C语言的隐式类型转换

这里只是报了一个警告,为什么会支持隐式类型转换呢?

因为他们是意义相同的类型,比如 char、int、double 这些类型都是可以互相转,

因为它们都是表示数据大小的。这里 d 也不是直接转给 i,我们之前讲过,中间会生成一个临时变量。我们在讲引用的时候详细讲过这一点。

2.2 explicit 关键字使用

explicit 关键字只能用于类内部的构造函数声明上。

看一段代码:

class Date 
{
public:
  Date(int year = 1)
    : _year(year)
  {
    
  }
 
  void Print()
  {
    cout << _year << endl;
  }
 
private:
  int _year;
};
 
int main()
{
  Date d1(2022);
  Date d2 = 2023;    // 隐式类型转换
  d1.Print();
  d2.Print();
 
  return 0;
}

这里是隐式类型的转换,为什么支持一个整型转换成日期类相关的类型呢?

整型和日期类本来是没有关系的,但是你支持一个单参数的构造函数后,

整型就可以去构造一个日期类的对象,这个日期类的对象自然可以赋值给他了。

本来用 2023 构造成一个临时对象 Date(2023) ,在用这个对象拷贝构造 d2,


但是 C++ 编译器在连续的一个过程中,编译器为了提高效率,多个构造会被优化,合二为一。


所以这里被优化成,直接就是一个构造了。并不是所有的编译器都会这么做,


C++标准并没有规定,但是新一点的编译器一般都会这么做。


如果你不想让这种 "转换" 发生,C++提供了一种关键字:explicit


构造函数不仅可以构造和初始化对象,对于单个参数的构造函数,还具有类型转换的作用。

用 explicit 关键字修饰构造函数,可以禁止单参构造函数的隐式类型转换:

3. static成员

如果我们要计算一个类中创建了多少个类对象,我们可以用全局变量计算一下。

int n = 0;  // 全局变量
 
class A 
{
public:
  A(int a = 0)
    : _a(a) 
  {
    n++;
  }
  A(const A& aa)
    : _a(aa._a)
  {
    n++;
  }
 
private:
  int _a;
};
 
void f(A a) 
{
  ;
}
 
int main()
{
  A a1;
  A a2 = 1;
  f(a1);
 
  cout << n << endl;
 
  return 0;
}

输出了3,如果我不想让这个 n 可以被人在外面随便改呢?

有没有办法可以把 n 和类贴合起来呢?让这个 n 专门用来计算我 A 这个类的。

我们先试着把它定义成 —— 成员变量:

class A
{
public:
  A(int a = 0)
    : _a(a) 
  {
    _n++;
  }
  A(const A& aa)
    : _a(aa._a)
  {
    _n++;
  }
 
private:
  int _a;
  int _n = 0;  // 定义成成员变量
};

是这样还是不行!这样的话每个对象里面都有一个 n,

我们是希望的是每个对象创建的时候去++的是同一个变量,而不是每个对象里面都有一个。

那该怎么办呢?类里面可以定义静态成员,在成员变量前面加一个 static,就是静态成员。

3.1 static的概念

声明为 static 的类成员称为类的静态成员,用 static 修饰的成员变量,称为静态成员变量。

用 static 修饰的成员函数,称为静态成员函数,静态的成员变量一定要在类外进行初始化。

class A 
{
public:
  A(int a = 0)
    : _a(a) 
  {
    _sn++;
  }
  A(const A& aa)
    : _a(aa._a)
  {
    _sn++;
  }
 
private:
  int _a;
 
  // 静态成员变量属于整个类,所有对象,生命周期在整个程序运行期间。
  static int _sn;   // 这里以 _s 为前缀,是为了一眼就看出它是静态成员变量。
};
 
int A::_sn = 0;//静态的成员变量一定要在类外进行初始化。

3.2 static成员特性

① 静态成员为所有类对象所共享,不属于某个具体的实例。

② 静态成员变量必须在类外定义,定义时不添加 static 关键字。

③ 类静态成员即可用类名 :: 静态成员变量或者对象 . 来访问。

如果它是公有的,我们就可以在类外对它进行访问:

class A 
{
public:
  A(int a = 0)
    : _a(a) 
  {
    _sn++;
  }
  A(const A& aa)
    : _a(aa._a) 
  {
    _sn++;
  }
 
   //private:
  int _a;
  static int _sn;
};
 
int A::_sn = 0;//静态的成员变量一定要在类外进行初始化。
 
void f(A a) 
{
  ;
}
 
int main()
{
  A a1;
  A a2 = 1;
  f(a1);
 
  cout << A::_sn << endl;  // 使用类域对它进行访问
 
  // 这里不是说是在 a1 里面找,这里只是帮助他突破类域
  cout << a1._sn << endl;
  cout << a2._sn << endl;
 
  return 0;
}

但是如果它是私有的,我们可以提供一个公有的成员函数。

我们写一个公有的 Get_sn成员函数,让它返回 _sn 的值,

这样我们就可以在类外调用该函数,就可以访问到它了。

还有没有更好的方式?让我不用对象就可以访问到它呢?静态成员函数:

class A 
{
public:
  A(int a = 0)
    : _a(a) 
  {
    _sn++;
  }
  A(const A& aa)
    : _a(aa._a) 
  {
    _sn++;
  }
  static int Get_sn() 
  {
    return _sn;
  }
private:
  int _a;
  static int _sn;
};
 
int A::_sn = 0;//静态的成员变量一定要在类外进行初始化。
 
void f(A a) 
{
  ;
}
 
int main()
{
  A a1;
  A a2 = 1;
  f(a1);
 
  cout << A::Get_sn() << endl;  // 使用类域对它进行访问
 
  // 这里不是说是在 a1 里面找,这里只是帮助他突破类域
  cout << a1.Get_sn() << endl;
  cout << a2.Get_sn() << endl;
 
  return 0;
}

④ 静态成员函数没有隐藏的 this 指针,不能访问任何非静态成员。

⑤ 静态成员和类的普通成员一样,也有 public、protected、private 三种访问级别,

静态成员函数也可以具有返回值。

从C语言到C++⑦(第二章_类和对象_下篇)初始化列表+explicit+static成员+友元+内部类+匿名对象(中):https://developer.aliyun.com/article/1513653

目录
相关文章
|
1天前
|
算法 Java 程序员
面向对象编程(OOP)通过对象组合构建软件,C语言虽是过程式语言,但可通过结构体、函数指针模拟OOP特性
【6月更文挑战第15天】面向对象编程(OOP)通过对象组合构建软件,C语言虽是过程式语言,但可通过结构体、函数指针模拟OOP特性。封装可使用结构体封装数据和方法,如模拟矩形对象。继承则通过结构体嵌套实现静态继承。多态可通过函数指针模拟,但C不支持虚函数表,实现复杂。C语言能体现OOP思想,但不如C++、Java等语言原生支持。
12 7
|
18天前
|
安全 程序员 C语言
从C语言到C++_37(特殊类设计和C++类型转换)单例模式(下)
从C语言到C++_37(特殊类设计和C++类型转换)单例模式
23 5
|
18天前
|
设计模式 编译器 Linux
从C语言到C++_37(特殊类设计和C++类型转换)单例模式(中)
从C语言到C++_37(特殊类设计和C++类型转换)单例模式
18 0
|
18天前
|
安全 编译器 C语言
从C语言到C++_37(特殊类设计和C++类型转换)单例模式(上)
从C语言到C++_37(特殊类设计和C++类型转换)单例模式
22 0
|
26天前
|
编译器 测试技术 C语言
从C语言到C++_11(string类的常用函数)力扣58和415(下)
从C语言到C++_11(string类的常用函数)力扣58和415
11 0
|
26天前
|
存储 编译器 C语言
从C语言到C++_11(string类的常用函数)力扣58和415(中)
从C语言到C++_11(string类的常用函数)力扣58和415
14 0
|
26天前
|
存储 C语言 C++
从C语言到C++_11(string类的常用函数)力扣58和415(上)
从C语言到C++_11(string类的常用函数)力扣58和415
16 0
|
26天前
|
C语言 C++
从C语言到C++⑧(第二章_类和对象_下篇_续)笔试选择题和OJ题
从C语言到C++⑧(第二章_类和对象_下篇_续)笔试选择题和OJ题
21 0
|
26天前
|
Java 编译器 C语言
从C语言到C++⑦(第二章_类和对象_下篇)初始化列表+explicit+static成员+友元+内部类+匿名对象(下)
从C语言到C++⑦(第二章_类和对象_下篇)初始化列表+explicit+static成员+友元+内部类+匿名对象
11 0
|
26天前
|
C语言 C++
从C语言到C++⑦(第二章_类和对象_下篇)初始化列表+explicit+static成员+友元+内部类+匿名对象(中)
从C语言到C++⑦(第二章_类和对象_下篇)初始化列表+explicit+static成员+友元+内部类+匿名对象
28 0

热门文章

最新文章