C++类与对象(四):再谈构造函数(详解初始化列表)、Static成员

简介: C++类与对象(四):再谈构造函数(详解初始化列表)、Static成员

上次把默认的成员函数部分梳理完毕了

今天接着讲下面的内容:


1.再谈构造函数


1.1构造函数体赋值


根据之前介绍的内容:在创建对象时,编译器通过调用构造函数,给对象中各个成员变量一个合适的初始值,我们之前使用的构造函数都叫做函数体内赋初值

class Date
{
public:
  Date(int year = 2024, int month = 1, int day = 1)//使用全缺省,也是默认构造函数
  {
    //函数体内初始化,在函数体内进行赋值
    _year = year;
    _month = month;
    _day = day;
  }
private:
  int _year;//变量声明
  int _month;
  int _day;
};

虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称为对对象中成员变量的初始化,构造函数体中的语句只能将其称为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内可以多次赋值。


初始化与赋值区别:


初始化是在创建变量时为其赋予一个初始值。在构造函数中,初始化通常是在对象创建时对成员变量进行赋值。初始化可以在变量声明时进行,也可以在构造函数的初始化列表中进行(下面就介绍)。

赋值是在变量已经存在的情况下改变变量的值。赋值操作符=用于将一个值赋给一个已经存在的变量

初始化是在变量创建时进行的,而赋值是在变量已经存在的情况下进行的

初始化可以只进行一次,而赋值可以进行多次

在一些情况下,初始化可能比赋值更加高效,因为它可以在对象创建时直接将初始值传递给对象,而不需要额外的操作


1.2初始化列表


1.2.1格式和概念

初始化列表:成员变量定义处

初始化列表:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个成员变量后面跟一个放在括号中的初始值或表达式

class Date
{
public:
  Date(int year = 2024, int month = 1, int day = 1)//使用全缺省,也是默认构造函数
    :_year(year)
    ,_month(month)
    ,_day(day)  //初始化列表
  {
  }
private:
  int _year;
  int _month;
  int _day;
};

那大家可能有疑问了? 之前函数体内赋值不是用的好好的嘛,来这个干嘛? 现在就来解释:


1.2.2由来


情况1

class Date
{
public:
  Date(int year = 2024, int month = 1, int day = 1)
    :_year(year)
    ,_month(month)
    ,_day(day)  //这三个可以在这,也可以在函数体内
    ,_ref(year)
    ,_n(1)  //这两个必须在这里
  {
  }
    //两个地方可以混着用,这样也行  
  //Date(int year = 2024, int month = 1, int day = 1)
  //  : _ref(year)
  //  , _n(1)  //这两个必须在这里
  //{
  //  _year = year;//没有在初始化列表显示出来定义,但是也会定义和初始化,不过内置类型随机值。
                                            //自定义类型调用自己的默认构造函数
  //  _month = month;
  //  _day = day;//这三个可以在这,也可以在初始化列表内
  //}
private:
  int _year;//这些都是声明,还没有空间
  int _month;
  int _day;
  int& _ref;//引用,必须在定义的时候初始化
  const int _n;//常量,必须在定义的时候初始化
};
int main()
{
  //实例化对象
  Date d1;//此时才定义,但是对象整体定义; 那每个成员在哪里定义呢?——就在初始化列表
  return 0;
}

可以知道的是:在进去函数体之前,定义和初始化都已经完成了,函数体进行的只是单纯的赋值操作。

所有的初始化行为都是在初始化列表内完成的。如果在初始化列表里没有出现的话一般是会在初始化列表给他初始化为默认值(随机值或自己给的缺省值)

之前我们也用过缺省值

class Date
{
public:
  //两个地方可以混着用,这样也行
  Date(int year = 2024, int month =1, int day = 1)
    : _ref(year)
    , _n(1)  //这里给1,看结果
  {
    _year = year;
    _month = month;
    _day = day;//这三个可以进行赋值
  }
private:
  int _year=1;//只给_year缺省值
  int _month;
  int _day;
  int& _ref;//引用,必须在定义的时候初始化
  const int _n=2;//这里给2
};

如果在初始化列表里进行了显示地初始化,那就按照列表里进行(最优先); 没有那才会用缺省值;连缺省值都没有那就随机值了。

上述赋值结果:


情况2

class Stack
{
public:
  Stack(int capacity)
  {
    //.......
  }
  //没有默认构造函数了
private:
  int* _a;
  int _top;
  int _capacity;
};
class MyQueue
{
public:
  //此时都是自定义类型,但是又没有默认构造函数;或者有但是不想用。就要自己初始化
  MyQueue()
    :_s1(4)//自己显示地初始化
    , _s2(5)
  {
  }
private:
  Stack _s1;
  Stack _s2;
};

如果自定义类型没有默认构造函数。解决方案:

  1. 写出来默认构造
  2. 在初始化列表处显示地写出来

         

1.2.3特性


每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)

类中包含以下成员,必须放在初始化列表位置进行初始化:(在由来里讲了)

引用成员变量

const成员变量

自定义类型成员(且该类没有默认构造函数时)

尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化

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

解决的问题:

必须在定义的地方显示地初始化:引用 const

没有默认构造函数的自定义成员

有些自定义成员想要自己控制自己的初始化


1.2.4特殊情况

class Stack
{
public:
  Stack(int capacity=3)
  {
    cout << "调用了Stack的默认构造函数";
    //.......
  }
private:
  int* _a;
  int _top;
  int _capacity;
};
class MyQueue
{
public:
private:
  Stack _s1;
  Stack _s2;//这俩可调用Stack类的默认构造
  int _size = -1;//给了缺省值
  //此时可以简单点理解:该类的默认构造函数对于自定义就去调用他们的,对于内置使用缺省或随机值
};
class Stack
{
public:
  Stack(int capacity=3)
  {
    cout << "调用了Stack的默认构造函数";
    //.......
  }
  //没有默认构造函数了
private:
  int* _a;
  int _top;
  int _capacity;
};
class MyQueue
{
public:
  MyQueue()//有没有效果一样,没写就按照默认构造函数那老一套
  { }  //写了初始化列表一定会走,但没有显示的写那也是老一套
private:
  Stack _s1;
  Stack _s2;//这俩可调用Stack类的默认构造
  int _size = -1;//给了缺省值
};


1.3explicit关键字


构造函数不仅可以构造与初始化对象,对于单个参数或者除第一个参数无默认值其余均有默认值的构造函数,还具有类型转换的作用

用explicit修饰构造函数,将会禁止构造函数的隐式转换

  1. 构造函数是单参数
class A
{
public:
  A(int a = 0)
    :_a(a)
  { }
private:
  int _a;
};
int main()
{
  A a1(1);//这样创建对象大家都知道
  A a2 = 2;//这样也可以:内置类型对象隐式转换为自定义类型对象
           //能这样做,是A的int单参数构造函数支持的
       //其实隐式转换中间产生一个临时变量,临时变量是A类型的
  const A& ra = 3;//给临时变量起别名,这时临时变量会在引用的作用域结束时销毁
  return 0;
}


除第一个参数无默认值其余均有默认值的多参构造函数


class Date
{
public:
   Date(int year, int month = 1, int day = 1)
    :_year(year)
    ,_month(month)
    ,_day(day)
  { }
private:
  int _year;
  int _month;
  int _day;
};
int main()
{
  Date d1(2024, 1, 6);
    Date d2=2024;//
    //Date d2 = { 2024,1,1 };这样也行
  Date d3 = (2024, 1, 1);//这样是逗号表达式子<==> Date d3=1 <==> Date d3=(Date)1 <==> Date d3(1)
  return 0;
}

  1. 全缺省构造函数
class Date
{
public:
  // explicit修饰构造函数,禁止类型转换
   Date(int year=1, int month = 1, int day = 1)
    :_year(year)
    ,_month(month)
    ,_day(day)
  { }
private:
  int _year;
  int _month;
  int _day;
};
int main()
{
  Date d1;
  Date d2 = { 2024,2 };//从左到右依次赋值
  Date d3 = { 2024,2,2 }; //这样也行
  Date d4 = 2024;
  return 0;
}


2. static成员


2.1概念与引入


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


用static修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化(不走初始化列表,不属于单个成员。类里声明,类外定义)


static静态成员变量:属于整个类,属于这个类所有对象。受访问限定符限制

实际上:静态成员函数和静态成员变量,本质上是受限制的全局变量和全局函数(专属这个类,受类域和访问限定符的限制)

#include<iostream>
using namespace std;
class A
{
public:
  A()//无参构造
  {
    count++;
  }
  A(A& a)//拷贝构造
  {
    count++;
  }
  static int count;//类内声明,属于整体(公有)
};
int A::count = 0;//类外定义,就类似于成员函数声明和定义分离
int main()
{
  A aa;
  cout << A::count << endl;//正常大家会想到这样访问
  cout << aa.count << endl;//这样也可以,类比调用成员函数:告诉编译器去那个类里找
}

此时是公有,那如果是私有。要怎么访问呢???

对于count都在类外定义了,为什么不能直接访问呢? 这样就直接以成员函数类比就行

using namespace std;
class A
{
public:
  A()//无参构造
  {
    count++;
  }
  A(A& a)//拷贝构造
  {
    count++;
  }
  int getCount()
  {
    return count;
  }
private:
  static int count;//类内声明,属于整体(私有)
};
int A::count = 0;//类外定义
int main()
{
  A aa;
  cout << aa.getCount()-1 << endl;//因为为了得到count而特地创建了一个对象来调用get函数,要-1;
}

现在count是私有了,就定义了一个getCount函数来得到。但是:为了得到count而特地创建了一个对象来调用get函数(还是有点不合适)


对于对象调用成员函数意义:1. 是告诉编译器getCount在A类里 2. 另一个是传this指针


而编译器在编译阶段遇到变量或者函数,都会去找出处,向上找和全局找(也是命名空间和类域起作用原因)

class A
{
public:
  A()//无参构造
  {
    count++;
  }
  A(A& a)//拷贝构造
  {
    count++;
  }
  static int getCount()//静态成员函数,没有this指针,所以不能访问非静态成员变量
  {
    return count;
  }
private:
  static int count;//类内声明,属于整体(公有)
};
int A::count = 0;//类外定义
int main()
{
  A aa;
  cout << A::getCount()-1 << endl;//可以直接用类名调用
    cout << aa.getCount << endl;//这样也行
} 


2.2特性


根据上面也可总结出一些特性:


静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区。它们在类的所有实例之间是唯一的。因此,静态成员函数可以直接访问静态成员变量,因为它们不依赖于特定的对象实例,而是与整个类相关联的

静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明

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

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

静态成员也是类的成员,受public、protected、private 访问限定符的限制

静态成员函数,没有this指针,所以不能访问非静态成员变量

实际上:静态成员函数和静态成员变量,本质上是受限制的全局变量和全局函数(专属这个类,受类域和访问限定符的限制)


这次就先到这里啦,下次类与对象的内容也要告一段落了,感谢大家支持!!!


目录
相关文章
|
2月前
|
编译器 C++
类和对象(中 )C++
本文详细讲解了C++中的默认成员函数,包括构造函数、析构函数、拷贝构造函数、赋值运算符重载和取地址运算符重载等内容。重点分析了各函数的特点、使用场景及相互关系,如构造函数的主要任务是初始化对象,而非创建空间;析构函数用于清理资源;拷贝构造与赋值运算符的区别在于前者用于创建新对象,后者用于已存在的对象赋值。同时,文章还探讨了运算符重载的规则及其应用场景,并通过实例加深理解。最后强调,若类中存在资源管理,需显式定义拷贝构造和赋值运算符以避免浅拷贝问题。
|
2月前
|
存储 编译器 C++
类和对象(上)(C++)
本篇内容主要讲解了C++中类的相关知识,包括类的定义、实例化及this指针的作用。详细说明了类的定义格式、成员函数默认为inline、访问限定符(public、protected、private)的使用规则,以及class与struct的区别。同时分析了类实例化的概念,对象大小的计算规则和内存对齐原则。最后介绍了this指针的工作机制,解释了成员函数如何通过隐含的this指针区分不同对象的数据。这些知识点帮助我们更好地理解C++中类的封装性和对象的实现原理。
|
2月前
|
编译器 C++
类和对象(下)C++
本内容主要讲解C++中的初始化列表、类型转换、静态成员、友元、内部类、匿名对象及对象拷贝时的编译器优化。初始化列表用于成员变量定义初始化,尤其对引用、const及无默认构造函数的类类型变量至关重要。类型转换中,`explicit`可禁用隐式转换。静态成员属类而非对象,受访问限定符约束。内部类是独立类,可增强封装性。匿名对象生命周期短,常用于临时场景。编译器会优化对象拷贝以提高效率。最后,鼓励大家通过重复练习提升技能!
|
3月前
|
编译器 C++ 开发者
【C++篇】深度解析类与对象(下)
在上一篇博客中,我们学习了C++的基础类与对象概念,包括类的定义、对象的使用和构造函数的作用。在这一篇,我们将深入探讨C++类的一些重要特性,如构造函数的高级用法、类型转换、static成员、友元、内部类、匿名对象,以及对象拷贝优化等。这些内容可以帮助你更好地理解和应用面向对象编程的核心理念,提升代码的健壮性、灵活性和可维护性。
|
25天前
|
编译器 C++ 容器
【c++11】c++11新特性(上)(列表初始化、右值引用和移动语义、类的新默认成员函数、lambda表达式)
C++11为C++带来了革命性变化,引入了列表初始化、右值引用、移动语义、类的新默认成员函数和lambda表达式等特性。列表初始化统一了对象初始化方式,initializer_list简化了容器多元素初始化;右值引用和移动语义优化了资源管理,减少拷贝开销;类新增移动构造和移动赋值函数提升性能;lambda表达式提供匿名函数对象,增强代码简洁性和灵活性。这些特性共同推动了现代C++编程的发展,提升了开发效率与程序性能。
53 12
|
2月前
|
设计模式 安全 C++
【C++进阶】特殊类设计 && 单例模式
通过对特殊类设计和单例模式的深入探讨,我们可以更好地设计和实现复杂的C++程序。特殊类设计提高了代码的安全性和可维护性,而单例模式则确保类的唯一实例性和全局访问性。理解并掌握这些高级设计技巧,对于提升C++编程水平至关重要。
58 16
|
3月前
|
编译器 C语言 C++
类和对象的简述(c++篇)
类和对象的简述(c++篇)
|
2月前
|
安全 C++
【c++】继承(继承的定义格式、赋值兼容转换、多继承、派生类默认成员函数规则、继承与友元、继承与静态成员)
本文深入探讨了C++中的继承机制,作为面向对象编程(OOP)的核心特性之一。继承通过允许派生类扩展基类的属性和方法,极大促进了代码复用,增强了代码的可维护性和可扩展性。文章详细介绍了继承的基本概念、定义格式、继承方式(public、protected、private)、赋值兼容转换、作用域问题、默认成员函数规则、继承与友元、静态成员、多继承及菱形继承问题,并对比了继承与组合的优缺点。最后总结指出,虽然继承提高了代码灵活性和复用率,但也带来了耦合度高的问题,建议在“has-a”和“is-a”关系同时存在时优先使用组合。
143 6
|
4月前
|
C++ 芯片
【C++面向对象——类与对象】Computer类(头歌实践教学平台习题)【合集】
声明一个简单的Computer类,含有数据成员芯片(cpu)、内存(ram)、光驱(cdrom)等等,以及两个公有成员函数run、stop。只能在类的内部访问。这是一种数据隐藏的机制,用于保护类的数据不被外部随意修改。根据提示,在右侧编辑器补充代码,平台会对你编写的代码进行测试。成员可以在派生类(继承该类的子类)中访问。成员,在类的外部不能直接访问。可以在类的外部直接访问。为了完成本关任务,你需要掌握。
124 19
|
3月前
|
安全 编译器 C语言
【C++篇】深度解析类与对象(中)
在上一篇博客中,我们学习了C++类与对象的基础内容。这一次,我们将深入探讨C++类的关键特性,包括构造函数、析构函数、拷贝构造函数、赋值运算符重载、以及取地址运算符的重载。这些内容是理解面向对象编程的关键,也帮助我们更好地掌握C++内存管理的细节和编码的高级技巧。

热门文章

最新文章