从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月前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
104 5
|
2月前
|
编译器 C语言 C++
C++入门3——类与对象2-2(类的6个默认成员函数)
C++入门3——类与对象2-2(类的6个默认成员函数)
39 3
|
2月前
|
存储 编译器 C++
C++入门3——类与对象2-1(类的6个默认成员函数)
C++入门3——类与对象2-1(类的6个默认成员函数)
51 1
|
2月前
|
存储 编译器 数据安全/隐私保护
【C++篇】C++类与对象深度解析(四):初始化列表、类型转换与static成员详解2
【C++篇】C++类与对象深度解析(四):初始化列表、类型转换与static成员详解
44 3
|
1月前
|
存储 编译器 C语言
【c++丨STL】string类的使用
本文介绍了C++中`string`类的基本概念及其主要接口。`string`类在C++标准库中扮演着重要角色,它提供了比C语言中字符串处理函数更丰富、安全和便捷的功能。文章详细讲解了`string`类的构造函数、赋值运算符、容量管理接口、元素访问及遍历方法、字符串修改操作、字符串运算接口、常量成员和非成员函数等内容。通过实例演示了如何使用这些接口进行字符串的创建、修改、查找和比较等操作,帮助读者更好地理解和掌握`string`类的应用。
51 2
|
1月前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
93 4
|
1月前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
111 4
|
2月前
|
存储 编译器 对象存储
【C++打怪之路Lv5】-- 类和对象(下)
【C++打怪之路Lv5】-- 类和对象(下)
32 4
|
2月前
|
编译器 C语言 C++
【C++打怪之路Lv4】-- 类和对象(中)
【C++打怪之路Lv4】-- 类和对象(中)
32 4
|
2月前
|
存储 安全 C++
【C++打怪之路Lv8】-- string类
【C++打怪之路Lv8】-- string类
29 1