从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

目录
相关文章
|
29天前
|
编译器 C++ 容器
【c++11】c++11新特性(上)(列表初始化、右值引用和移动语义、类的新默认成员函数、lambda表达式)
C++11为C++带来了革命性变化,引入了列表初始化、右值引用、移动语义、类的新默认成员函数和lambda表达式等特性。列表初始化统一了对象初始化方式,initializer_list简化了容器多元素初始化;右值引用和移动语义优化了资源管理,减少拷贝开销;类新增移动构造和移动赋值函数提升性能;lambda表达式提供匿名函数对象,增强代码简洁性和灵活性。这些特性共同推动了现代C++编程的发展,提升了开发效率与程序性能。
54 12
|
7月前
|
安全 编译器 C语言
C++入门1——从C语言到C++的过渡
C++入门1——从C语言到C++的过渡
122 2
|
5月前
|
算法 编译器 C语言
【C语言】C++ 和 C 的优缺点是什么?
C 和 C++ 是两种强大的编程语言,各有其优缺点。C 语言以其高效性、底层控制和简洁性广泛应用于系统编程和嵌入式系统。C++ 在 C 语言的基础上引入了面向对象编程、模板编程和丰富的标准库,使其适合开发大型、复杂的软件系统。 在选择使用 C 还是 C++ 时,开发者需要根据项目的需求、语言的特性以及团队的技术栈来做出决策。无论是 C 语言还是 C++,了解其优缺点和适用场景能够帮助开发者在实际开发中做出更明智的选择,从而更好地应对挑战,实现项目目标。
224 0
|
7月前
|
C语言 C++
C 语言的关键字 static 和 C++ 的关键字 static 有什么区别
在C语言中,`static`关键字主要用于变量声明,使得该变量的作用域被限制在其被声明的函数内部,且在整个程序运行期间保留其值。而在C++中,除了继承了C的特性外,`static`还可以用于类成员,使该成员被所有类实例共享,同时在类外进行初始化。这使得C++中的`static`具有更广泛的应用场景,不仅限于控制变量的作用域和生存期。
165 10
|
7月前
|
C语言 C++
实现两个变量值的互换[C语言和C++的区别]
实现两个变量值的互换[C语言和C++的区别]
85 0
|
7月前
|
C++
C++构造函数初始化类对象
C++构造函数初始化类对象
51 0
|
4月前
|
存储 算法 C语言
【C语言程序设计——函数】素数判定(头歌实践教学平台习题)【合集】
本内容介绍了编写一个判断素数的子函数的任务,涵盖循环控制与跳转语句、算术运算符(%)、以及素数的概念。任务要求在主函数中输入整数并输出是否为素数的信息。相关知识包括 `for` 和 `while` 循环、`break` 和 `continue` 语句、取余运算符 `%` 的使用及素数定义、分布规律和应用场景。编程要求根据提示补充代码,测试说明提供了输入输出示例,最后给出通关代码和测试结果。 任务核心:编写判断素数的子函数并在主函数中调用,涉及循环结构和条件判断。
227 23
|
3月前
|
人工智能 Java 程序员
一文彻底搞清楚C语言的函数
本文介绍C语言函数:函数是程序模块化的工具,由函数头和函数体组成,涵盖定义、调用、参数传递及声明等内容。值传递确保实参不受影响,函数声明增强代码可读性。君志所向,一往无前!
47 1
一文彻底搞清楚C语言的函数
|
4月前
|
算法 C语言
【C语言程序设计——函数】利用函数求解最大公约数和最小公倍数(头歌实践教学平台习题)【合集】
本文档介绍了如何编写两个子函数,分别求任意两个整数的最大公约数和最小公倍数。内容涵盖循环控制与跳转语句的使用、最大公约数的求法(包括辗转相除法和更相减损术),以及基于最大公约数求最小公倍数的方法。通过示例代码和测试说明,帮助读者理解和实现相关算法。最终提供了完整的通关代码及测试结果,确保编程任务的成功完成。
200 15
【C语言程序设计——函数】利用函数求解最大公约数和最小公倍数(头歌实践教学平台习题)【合集】