类和对象(下)

简介: 类和对象(下)

一、再谈构造函数


1.1 构造函数体内赋值



1.2 构造函数的初始化列表


初始化列表:是以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个括号,括号中放该成员变量的初始值或表达式。初始化列表是成员变量定义的地方,在调用构造函数进入函数体前一定会先走初始化列表,尽管程序员没有显式地写出初始化列表,程序也会自动走初始化列表这个步骤,可以说初始化列表是调用构造函数必须走的一条必经之路。



值得注意的是:


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


2、如果出现以下几种类型的成员变量,必须要在初始化列表中进行初始化:

(1) const修饰的成员变量。


(2) 引用成员变量。


(3) 没有默认构造函数的自定义类型的成员变量。


为什么有这三种类型的成员变量就一定要在初始化列表进行初始化呢?


const成员变量:因为const修饰的变量是常变量,只有一次初始化的机会,就是创建变量的同时就初始化,这种变量在创建出来之后是不能再进行赋值的,所以创建的时候必须给一个初始值,而由于初始化列表是成员变量初始化的地方,在调用构造函数时进入函数体前会先走初始化列表,所以这种变量必须要在初始化列表初始化。


引用成员变量:引用变量在创建的同时也必须要进行初始化,创建之后就不能再对它进行赋值了,初始化列表是成员变量初始化的地方,所以引用成员变量也必须要在初始化列表进行初始化。


无默认构造函数的自定义类型的成员变量:由于该成员变量没有默认构造函数,所以当我们没有在初始化列表进行初始化该成员变量时,虽然初始化列表依然会走,但是编译器自己没办法调用它的构造函数对该变量(对象)进行初始化,所以就不要要在初始化列表显式地对该对象进行初始化。



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


4、成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与其在初始化列表中的先后次序无关。(切记,这里好坑)



5、成员变量声明时可以给一个默认值,这个默认值是给初始化列表的,当我们没有显式地使用初始化列表初始化这个成员变量时,这个成员变量就用这个默认值初始化,如果我们显式地使用初始化列表初始化这个成员变量时,这个默认值就不会被使用。




下面来看一个程序题:


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();
}
A. 输出1 1
B.程序崩溃
C.编译不通过
D.输出1 随机值


这题的答案是D,为什么呢?原因正是第4点需要注意的地方,成员变量的定义顺序是按照成员变量声明的顺序来初始化的,与它在初始化列表出现的先后顺序没有任何关系。


1.3 explicit关键字


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


explicit关键字的作用是禁止隐式类型转换。


我们看一下以下代码:


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 d = 2023;
  return 0;
}
//这段代码是能够编译通过的,有人可能会有疑问,用Date d=2023
//怎么就构造出来了一个日期类对象了呢?我们看下面图片的解释



上面这个是允许隐式类型转换的条件下才可以的,但是在构造函数前加上explicit关键字意思就是禁止隐式类型转换,这段代码就会报错了。如下:



因为explicit修饰构造函数,禁止了单参构造函数类型转换的作用。


二、static成员


2.1 什么是static成员?


声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化。(原因:静态成员变量是属于整个类的,不属于某一个对象,所以在构造函数的地方静态成员变量不会走初始化列表,即在创建对象的时候不会进行初始化,所以必须要在类外面初始化)


题目:实现一个类,计算程序中创建出了多少个类对象。


代码实现如下:


class A
{
public:
  A(int x = 10)
    :_a(x)
  {
    _count++;
    cout << "A()" << endl;
  }
  A(const A& a)
  {
    _count++;
    cout << "A(const A& a)" << endl;
  }
  static int GetCount()
  {
    return _count;
  }
private:
  int _a;
  static int _count;//注意这里需要一个静态的成员变量统计次数,它属于整个类
};
int A::_count = 0;
int main()
{
  A a;
  cout << A::GetCount() << endl;
  A aa;
  A ab;
  A x(a);
  cout << A::GetCount() << endl;
  return 0;
}


2.2 静态成员的特性


静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区。


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


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


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


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

问题:


1、静态成员函数可以调用非静态成员函数吗?


不能,因为静态成员没有this指针,而调用非静态成员函数需要传递this指针,所以静态成员函数不能调用非静态成员函数。

2、非静态成员函数可以调用类的静态成员函数吗?


可以,类的静态成员函数可以被任何类型的成员函数所调用,因为它没有this指针,所以调用静态成员函数也就不需要传递this指针。


三、友元


友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。


友元可以分为友元函数和友元类。


3.1 友元函数


下面就是一个友元函数的典型的应用场景。


class Date
{
public:
  static int GetMonthDay(int year, int month)
  {
    static int DayArr[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
    if ((month == 2) && (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0)))
    {
      return 29;
    }
    return DayArr[month];
  }
  Date(int year = 1900, int month = 1, int day = 1)
  {
    _year = year;
    _month = month;
    _day = day;
  }
  //如果重载成成员函数,那么第一个参数必然被this指针占用,
  //那么cout对象只能是作为第二个参数
  ostream& operator<<(ostream& out)
  {
    out << _year << "/" << _month << "/" << _day << endl;
    return out;
  }
  //如果重载成成员函数,那么第一个参数必然被this指针占用,
  //那么cin对象只能是作为第二个参数
  istream& operator>>(istream& in)
  {
    int year = 0;
    int month = 0;
    int day = 0;
    cin >> year >> month >> day;
    if (month > 0 && month < 13
      && day>0 && day <= Date::GetMonthDay(year, month))
    {
      _year = year;
      _month = month;
      _day = day;
    }
    else
    {
      cout << "输入的是非法日期" << endl;
      assert(false);
    }
    return in;
  }
private:
  int _year;
  int _month;
  int _day;
};
int main()
{
  Date d;
  //由于第一个参数必须是this指针,所以必须要像以下d>>cin和d<<cout这样输入
  //输出,但是这样子写显然不符合我们平时的cin>>d和cout<<d的习惯。
  d >> cin;
  d << cout << endl;
  return 0;
}


由于成员函数才一定需要传递this指针,那么这里就可以利用友元函数把cout和cin定义在类外面,这样就可以按照我们平时的使用习惯重载cout和cin了。


如下:


class Date
{
public:
  //友元函数的写法,类里面声明某个函数是这个类的友元函数,在函数前加关键字friend
  friend ostream& operator<<(ostream& out, const Date& d);
  friend istream& operator>>(istream& in, Date& d);
  static int GetMonthDay(int year, int month)
  {
    static int DayArr[13] = { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
    if ((month == 2) && (((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0)))
    {
      return 29;
    }
    return DayArr[month];
  }
  Date(int year = 1900, int month = 1, int day = 1)
  {
    _year = year;
    _month = month;
    _day = day;
  }
private:
  int _year;
  int _month;
  int _day;
};
ostream& operator<<(ostream& out, const Date& d)
{
  out << d._year << "/" << d._month << "/" << d._day << endl;
  return out;
}
istream& operator>>(istream& in, Date& d)
{
  int year = 0;
  int month = 0;
  int day = 0;
  cin >> year >> month >> day;
  if (month > 0 && month < 13
    && day>0 && day <= Date::GetMonthDay(year, month))
  {
    d._year = year;
    d._month = month;
    d._day = day;
  }
  else
  {
    cout << "输入的是非法日期" << endl;
    assert(false);
  }
  return in;
}
int main()
{
  Date d;
  //这样子定义就符合我们平时使用cin和cout的使用习惯了
  cin >> d;
  cout << d << endl;
  return 0;
}


注意点:

1、友元函数可访问类的私有成员和保护成员,但不是类的成员函数。

2、友元函数不能用const修饰。

3、友元函数可以在类定义的任何地方声明,不受类访问限定符限制。

4、一个函数可以是多个类的友元函数。

5、友元函数的调用与普通函数的调用原理相同。


3.2 友元类


友元类的几种特性:


1、友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。


2、友元关系是单向的,不具有交换性。A类是B类的友元,A的对象可以访问B类的所有成员。但是B类的对象则不能访问A的私有成员。

比如下面的Time类和Date类,在Time类中声明Date类为其友元类,那么可以在Date类中直接访问Time类的私有成员变量,但想在Time类中访问Date类中私有的成员变量则不行。


3、友元关系不能传递,如果B是A的友元,C是B的友元,则不能说明C是A的友元。


4、友元关系不能继承。


class Time
{
  //声明Date是友元类,那么Date类型的对象就能够访问我的所有成员
  friend class Date;
public:
  Time(int hour = 0, int minute = 0, int second = 0)
    :_hour(hour)
    , _minute(minute)
    , _second(second)
  {}
public:
  int _hour;
  int _minute;
  int _second;
};
class Date
{
public:
  Date(int year = 1, int month = 1, int day = 1)
    :_year(year)
    , _month(month)
    , _day(day)
  {}
  void Print()
  {
    //可以访问Time类型的所有成员
    cout << _t._hour << ":" << _t._minute << ":" << _t._second << endl;
  }
private:
  int _year;
  int _month;
  int _day;
  Time _t;
};
int main()
{
  Date d;
  d.Print();
  return 0;
}


四、内部类


4.1 什么是内部类?


概念:如果一个类定义在另一个类的内部,那么这个类就叫做内部类。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类不能访问内部类的私有成员。


但是内部类就是外部类的友元类,参见友元类的定义,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。


4.2 内部类的特性


1、内部类定义在外部类的public、protected、private都是可以的。


2、注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。


3、sizeof(外部类)=外部类,和内部类没有任何关系。


class A
{
public:
  A(int x = 10)
    :_a(x)
  {}
  //B是A的内部类,内部类天生是友元类,所以B内可以访问A的任何成员
  class B
  {
  public:
    B(int y = 20)
      :_b(y)
    {}
    void Print(const A& ab)
    {
      //访问A的私有成员
      //可以直接访问静态成员,不需要类名和对象名
      cout << ab._a << " " << _k << endl;
    }
  private:
    int _b;
  };
private:
  int _a;
  static int _k;
};
int A::_k = 5;
int main()
{
  A::B b;
  b.Print(A());
  return 0;
}
相关文章
|
存储 测试技术 Apache
Apache Hudi 元数据字段揭秘
Apache Hudi 元数据字段揭秘
403 1
|
2月前
|
机器学习/深度学习 人工智能 自然语言处理
智启未来:AI 科技的发展、应用与时代变革
人工智能(Artificial Intelligence,简称 AI),作为计算机科学的核心分支,是一门旨在使机器模拟、延伸和扩展人类智能的技术科学。它让机器具备感知、推理、学习、决策等类人能力,打破了 “机器仅能执行固定指令” 的传统认知,成为推动新一轮科技革命和产业变革的核心驱动力。
983 1
|
2月前
|
人工智能 API 数据安全/隐私保护
效率翻倍!OpenClaw 核心功能实战手册(阿里云/本地部署+百炼API配置+3大场景落地)
“部署完OpenClaw只用来聊天?那就太浪费了!”——2026年,这款开源AI框架的核心价值早已超越“对话工具”,凭借AI对话、任务自动化、插件扩展、知识库、自定义智能体五大核心功能,成为真正的生产力利器。参考文章从界面概览到实战案例,系统拆解了OpenClaw的使用逻辑,让用户从“会用”升级为“用好”。
1292 0
|
3月前
|
人工智能 自然语言处理 机器人
多格式兼容+批量导入:AI知识库内容管理高效技巧
在技术研发与团队协作的日常中,知识沉淀始终是绕不开的核心命题——碎片化的文档散落在不同平台、检索时陷入“关键词陷阱”、撰写技术文档耗时费力、跨平台协作效率低下,这些痛点困扰着无数开发者与团队。直到接触到一款由AI大模型驱动的开源知识库系统,我们才真正打破知识管理的壁垒,它并非单纯的“文档容器”,而是以轻量化开源架构为基础,将大模型能力与知识管理全流程深度融合的企业级工具,无论是个人私有化技术笔记的搭建,还是团队标准化产品文档、FAQ体系的构建,都能精准适配。结合长期实操经验,这篇文章将分享实打实的使用干货与心得,帮大家避开误区、高效落地。
|
3月前
|
JSON API 开发者
Ozon关键词搜索数据API接口技术指南
本文详解如何用Python调用Ozon关键词搜索API,涵盖账号注册、API密钥申请、请求参数配置、完整代码示例及错误处理,助您高效获取搜索量、排名与趋势数据,优化选品与SEO策略。(239字)
408 0
|
1月前
|
缓存 监控 NoSQL
MySQL分库分表缓存乱、命中率低还易不一致?ShardingSphere+Redis+监控,搭建高可用缓存管理体系
本文详解分库分表后缓存管理的四大痛点:路由混乱、数据不一致、穿透/击穿/雪崩、缺乏监控。提出ShardingSphere+Redis+Prometheus/Grafana组合方案,通过分片感知的Key设计、Cache-Aside一致性策略、多级防护机制及全链路监控,构建稳定高效、可落地的缓存管理体系。(239字)
|
1月前
|
数据采集 数据可视化 数据挖掘
Python数据分析实战——从数据加载到可视化,新手也能上手的全流程解析
本文以学生成绩分析为案例,手把手讲解Python数据分析全流程:从用Pandas加载CSV/Excel数据,到用NumPy+Pandas清洗缺失值、异常值与重复值;再到分组统计、相关性分析等数据处理;最后用Matplotlib实现直方图、柱状图、热力图等可视化。全程代码实操、口语化讲解,并推荐官方文档与Kaggle等优质学习资源。(239字)
|
1月前
|
JSON 前端开发 JavaScript
基于LangChain的简易智能旅游助手Agent
本文分享基于LangChain开发的智能旅游助手Agent,支持“查天气+荐景点”双功能,对比ReAct与FunctionCall两种实现模式,并详解工具封装、记忆管理、执行框架等LangChain核心优势。代码开源,含FastAPI后端与原生HTML/JS前端。
185 3
|
2月前
|
存储 弹性计算 小程序
2026阿里云轻量应用服务器详解:免费试用、费用价格、200M带宽优势及问题解答FAQ
2026阿里云轻量应用服务器全面升级:新用户享1个月免费试用(2核1G/4G+200M带宽),国内套餐38元起/年,全系标配200M峰值带宽、不限流量、一键镜像。适合个人建站、小程序后端与开发测试,免备案香港版可选。
1240 3
|
5月前
|
消息中间件 Java 数据安全/隐私保护
RabbitMQ集群部署
本文介绍RabbitMQ集群部署及高可用方案,涵盖普通集群搭建、镜像模式配置与仲裁队列使用。通过Docker部署三节点集群,配置Erlang Cookie与rabbitmq.conf实现节点通信;利用镜像模式实现数据冗余,支持主从切换;引入3.8版本后的仲裁队列,简化高可用配置,提升系统容错能力。

热门文章

最新文章