从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

目录
打赏
0
2
1
1
47
分享
相关文章
|
2月前
|
【C语言】static 关键字详解
`static` 关键字在C语言中用于控制变量和函数的作用域和生命周期。它可以用于局部变量、全局变量和函数,具有不同的效果。理解 `static` 关键字的用法有助于封装和管理代码,提高代码的可维护性和可靠性。
118 3
C 语言的关键字 static 和 C++ 的关键字 static 有什么区别
在C语言中,`static`关键字主要用于变量声明,使得该变量的作用域被限制在其被声明的函数内部,且在整个程序运行期间保留其值。而在C++中,除了继承了C的特性外,`static`还可以用于类成员,使该成员被所有类实例共享,同时在类外进行初始化。这使得C++中的`static`具有更广泛的应用场景,不仅限于控制变量的作用域和生存期。
115 10
【C++篇】C++类与对象深度解析(四):初始化列表、类型转换与static成员详解2
【C++篇】C++类与对象深度解析(四):初始化列表、类型转换与static成员详解
72 3
|
4月前
|
【C++篇】C++类与对象深度解析(四):初始化列表、类型转换与static成员详解1
【C++篇】C++类与对象深度解析(四):初始化列表、类型转换与static成员详解
75 3
|
4月前
|
C++
C++构造函数初始化类对象
C++构造函数初始化类对象
34 0
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
【C++篇】深度解析类与对象(中)
在上一篇博客中,我们学习了C++类与对象的基础内容。这一次,我们将深入探讨C++类的关键特性,包括构造函数、析构函数、拷贝构造函数、赋值运算符重载、以及取地址运算符的重载。这些内容是理解面向对象编程的关键,也帮助我们更好地掌握C++内存管理的细节和编码的高级技巧。
【C++篇】深度解析类与对象(上)
在C++中,类和对象是面向对象编程的基础组成部分。通过类,程序员可以对现实世界的实体进行模拟和抽象。类的基本概念包括成员变量、成员函数、访问控制等。本篇博客将介绍C++类与对象的基础知识,为后续学习打下良好的基础。
|
1月前
|
【C++面向对象——类与对象】Computer类(头歌实践教学平台习题)【合集】
声明一个简单的Computer类,含有数据成员芯片(cpu)、内存(ram)、光驱(cdrom)等等,以及两个公有成员函数run、stop。只能在类的内部访问。这是一种数据隐藏的机制,用于保护类的数据不被外部随意修改。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。成员可以在派生类(继承该类的子类)中访问。成员,在类的外部不能直接访问。可以在类的外部直接访问。为了完成本关任务,你需要掌握。
71 19

热门文章

最新文章

AI助理

你好,我是AI助理

可以解答问题、推荐解决方案等