类和对象(跑路人笔记)<完>(4)

简介: 类和对象(跑路人笔记)<完>

const成员

将const修饰的类成员函数称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this 指针,表明在该成员函数中不能对类的任何成员进行修改

image.png

其实很简单,就是在类内函数()后加上const就可以把原来类型为Date*的this指针变成const Date*类型.


const修饰的变量是不能传给没有被const修饰的引用和指针的,因为我们不能const修饰的变量被修改.


被const修饰的变量传给没有被const修饰的指针和引用时被称为权限放大是不被允许的.


而没被const修饰的变量,传给被const修饰的指针和引用时被称为权限的缩小是被允许的.


如我们在const修饰的this指针函数中无法调用没有被const修饰的.



image.png


原因也很简单,我们的函数在传参的时候会默认把this指针也传给调用的函数,我们上述的例子就是把Print函数的this指针传给了GetMonthDay函数,但是Print的this指针是const而GetMonthDay函数的不是const,这种传参属于是权限的放大.


但是非const就可以传给const这是权限的缩小.


初始化列表

我们使用构造函数函数,其实并不是对其进行初始化,而是对其进行赋值


有点像我们的构造函数和下面类似


int a;

a = 10;



但是我们有的时候必须是要对变量进行初始化,而不是赋值.


所以我们就需要到初始化列表了.


经过初始化列表进行初始化的就是直接类似:int a = 10;


初始化列表的格式如下:


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


class Date
{
  Date(int year = 2002, int month = 8, int day = 26)
  :_year(year)
  , _month(month)
  , _day(day)
  {
  ;
  }
private:
  int _year;
  int _month;
  int _day;
};



如下


image.png


注意:


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


类中包含以下成员,必须放在初始化列表位置进行初始化


引用成员变量


const成员变量


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


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


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


例子如下:


class test
{
public:
  test()
  :_a1(1)
  , _a2(_a1)
  {
  //cout << "test()" << endl;
  }
  void Print()
  {
  cout << "_a1:" << _a1 << endl;
  cout << "_a2:" << _a2 << endl;
  }
private:
  int _a2;
  int _a1;
};
int main()
{
  test a;
  a.Print();
  return 0;
}




运行结果如下:

image.png


而如果我们先初始化_a2在通过_a2 传给_a1就是正常情况,如下:


image.png


explicit关键字

介绍explicit之前让我们先看一串代码吧.


class test
{
public:
  test(int a = 1, int b = 1)
  {
  _a = a;
  _b = b;
  }
  void Print()
  {
  cout << "_a>:" << _a << endl << "_b>:" << _b << endl;
  }
private:
  int _a;
  int _b;
};
int main()
{
  test a;
  a = { 10,20 };//特殊的赋值,对代码的可读性不好
  a.Print();
  return 0;
}


浅提一下我们这种特殊赋值的实现是通过先建立一个临时的类再通过拷贝构造传给我们的对象a.我们新的编译器会对这个过程进行优化具体请看[小拓展—编译器优化](# 小拓展—编译器优化)


我们的explicit关键字就可以避免这个问题.


image.png

在构造函数前面加上explicit就将那种赋值给避免了.


小拓展

image.png


我们的这个赋值方式的成功是我们的编译器在赋值之前创建了一个临时变量,然后通过临时变量与类型a通过拷贝构造将10,20的值给到a中.


static成员

声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用static修饰的 成员函数,称之为静态成员函数。静态的成员变量一定要在类外进行初始化.


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


#include<iostream>
using namespace std;
class A
{
public:
  A()
  {
  ++_scount;
  }
  A(const A& t)
  {
  ++_scount;
  }
  static int GetACount()//静态修饰的成员函数没有this指针
  {
  return _scount;
  }
private:
  static int _scount;
};
int A::_scount = 0;//对类内进行初始化,不能在类内初始化
void TestA()
{
  cout << A::GetACount() << endl;//静态成员函数可以这样调用.
  A a1, a2;
  A a3(a1);
  cout << a1.GetACount() << endl;
}



上述代码中是因为static修饰的成员变量是整个类共享的不会因为换了个对象而改变自身值.


特性

静态成员为所有类对象所共享,不属于某个具体的实例


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


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


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


静态成员和类的普通成员一样,也有public、protected、private3种访问级别,也可以具有返回值


而非静态的成员函数可以调用静态的成员函数.



image.png

C++11成员初始化更新

C++11支持非静态成员变量在声明时进行初始化赋值,但是要注意这里不是初始化,这里是给声明的成员变 量缺省值。


image.png


他的作用类似于给初始化列表一个缺省值.


友元

友元可以分为1. 友元类 2.友元函数


使用友元我们可以突破封装,但是也会增加耦合性,所以友元要尽量少用


友元函数

我们在重载操作符的时候有两个操作符<< >>无法重载因为我们的重载调用的时候会把左边的当做this指针,而我们需要把cout放在左边所以我们就需要用的我们的友元函数了.


注: cout只是我们的一个调制好的ostream类的一个全局对象.内置类型的打印已经写好了,所以我们只需要把类里需要打印的通过内置类型打印好即可.


还是以日期类为例子吧:


我们实现一个日期类的<< 来看看吧.


class Date
{
public:
  Date(int year = 2002, int month = 8, int day = 26)
  {
  _year = year;
  _month = month;
  _day = day;
  }
  ostream& operator<<(ostream& out)
  {
  out << _year << "_" << _month << "_" << _day << endl;
  return out;
  }
private:
  int _year;
  int _month;
  int _day;
};




这种实现我们要使用的话十分别扭.


int main()
{
  Date a1;
  a1 << cout;//日期类的<<的使用
  return 0;
}




我们就可以用到友元来实现


实现如下:


class Date
{
public:
  friend ostream& operator<<(ostream& out, Date& d);
  Date(int year = 2002, int month = 8, int day = 26)
  {
  _year = year;
  _month = month;
  _day = day;
  }
private:
  int _year;
  int _month;
  int _day;
};
ostream& operator<<(ostream& out, Date& d)//返回这样的类型是因为<<的结合性是从左到右
{
  out << d._year << "_" << d._month << "_" << d._day << endl;
  return out;
}
int main()
{
  Date a1;
  cout << a1;//正确的使用
  return 0;
}



所以友元的使用也是很容易看出来的.


友元函数的使用就是在类里将函数声明前加上friend 即可.


当我们的函数是类的友元的时候我们就可以访问类的私有空间.


所以友元在函数方面的作用其实就是让一个普通函数具有访问一个类私用空间的权利


注意


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

友元函数不能用const修饰

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

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

友元函数的调用与普通函数的调用和原理相同

友元类

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


举例如下:


class Date
{
public:
  friend class test;
  Date(int year = 2002, int month = 8, int day = 26)
  {
  _year = year;
  _month = month;
  _day = day;
  }
private:
  int _year;
  int _month;
  int _day;
};
class test
{
public:
  void Print()
  {
  cout << a._year << endl;
  }
private:
  Date a;
};





上面的例子我们的test就是Date的友元类,我们就可以通过在test里创建Date的对象来访问Date类里的私有成员.


注:


友元关系不能传递.


如A是B的友元 B是C的友元 但是A不是C的友元


友元不具有交换性.


比如我们test是Date的友元类所以我们的test可以访问Date的私有成员但是我们的Date不能访问test的私有成员.


内部类

概念: 一个类定义在另一个类的内部就叫内部类.


注意: 内部类是外部类的友元类但是外部类对内部类没有任何优先和权力,对于外部类来说内部类就跟普通的类一样,外部类没有内部类的任何特权.


并且内部类可以不通过对象等做到直接访问外部类的成员(包括:枚举成员(就是C的枚举),static)


特性:


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

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

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

class A
{
public:
  class B
  {
  void Print(const A& a)
  {
    cout << a.h << endl;
    cout << _a << endl;
            //上述代码均可以通过
  }
  };
private:
  int h;
  static int _a;
};
int A::_a = 10;



我们想要访问到类B的话就需要通过A::B() 的方式进行访问了.


小拓展(关于类名+()这个匿名对象)

我们还是弄个日期类吧


class Date
{
public:
  Date(int year = 2002, int month = 8, int day = 26)
  {
  _year = year;
  _month = month;
  _day = day;
  }
  void Print()
  {
  cout << _year << "_" << _month << "_" << _day << endl;
  }
private:
  int _year;
  int _month;
  int _day;
};




ok,现在我们讲一下匿名对象.


创建方式其实很简单类名+()即可如我们的日期类就可以写成Date()这就是个匿名对象.


匿名对象特性


生命周期只有一行,运行完就没了.(除非用常引用来接收才可以)

我们一般用它来调用类里的函数之类的.


int main()
{
  Date().Print();
  return 0;
}



具体功能咱也不知道.等后续遇见了再补.


小拓展—编译器优化

我们的新的胆大的编译器会把一下需要构造再拷贝构造的或者双次拷贝构造的操作简化成一次拷贝构造或一次构造.(不同编译器不同)


举例来看


注: 例子都是通过以下代码进行的测验


#include<iostream>
using namespace std;
class test
{
public:
  test(int a1 = -1, int a2 = -1)
  {
  cout << "test()" << endl;
  _a1 = a1;
  _a2 = a2;
  }
  test(const test& a)
  {
  cout << "test(const test& a)" << endl;
  }
  void Print()
  {
  cout << "_a1: " << _a1 << endl;
  cout << "_a2: " << _a2 << endl;
  }
private:
  int _a1;
  int _a2;
};
test fun(test a)
{
  return a;
}





先构造再拷贝构造

int main()
{
  test a1 = 1;//特殊赋值
  return 0;
}



这个特殊赋值我们在讲述[explicit关键字](# explicit关键字)的时候浅提了一下


具体过程我用下面的图片来解释:


image.png

我们的编译器优化后就会省略很多步骤,如下图:


image.png


而我们在VS2022也是符合的

image.png


image.png

相关文章
|
SQL 开发框架 缓存
分享143个ASP源码,总有一款适合您
分享143个ASP源码,总有一款适合您
200 3
|
小程序
TDesign电商小程序模板解析02-首页功能(一)
TDesign电商小程序模板解析02-首页功能(一)
|
安全 网络安全 数据库
Web安全防护的必要性与漏洞扫描技术
随着互联网的发展,Web应用程序的使用越来越广泛,但也带来了越来越多的安全威胁。因此,Web安全防护变得越来越重要。本文将介绍Web安全防护的必要性,并详细介绍各种漏洞扫描技术,以帮助您保护Web应用程序的安全。
369 2
|
人工智能 自然语言处理 算法
国产新型AI编程助手—DevChat AI插件在VSCode中的应用
国产新型AI编程助手—DevChat AI插件在VSCode中的应用
511 0
|
移动开发 Dart 前端开发
AliFlutter - 面向阿里集团的Flutter体系化建设
阿里巴巴集团移动技术委员会联合淘系技术部重磅推出「AliFlutter系列直播」,文中可以报名哦!
7318 0
AliFlutter - 面向阿里集团的Flutter体系化建设
|
应用服务中间件 Shell Docker
Docker 容器与主机时间同步
宿主机时间 [root@slave-1 ~]# date Fri May 12 11:20:30 CST 2017 容器时间 [root@slave-1 ~]# docker exec -ti 87986863838b /bin/bash root@...
1600 0
|
11月前
|
人工智能 缓存 搜索推荐
OPENAI DevDay 2024:推动AI技术的新边界
在今年的OPENAI DevDay活动中,尽管形式更为低调,但OpenAI依然带来了四项令人瞩目的技术创新,展示了其在推动人工智能开发者生态方面的持续努力,以及向更高效、用户友好的AI工具转型的决心。我将为大家详细介绍这些新产品
397 10
|
存储 JavaScript 前端开发
展开运算符的介绍使用(...),实际应用this.tableData.push({...})
这篇文章介绍了ES6中引入的展开运算符`(...)`的多种用途,包括数组合并与复制、对象合并与复制、函数参数的展开以及字符串处理,并强调了它在简化代码、提高开发效率方面的重要性,同时通过实际代码示例展示了其在项目中的应用。
|
机器学习/深度学习 人工智能 自动驾驶
人工智能的伦理困境:机器的自主性与人类的责任
【8月更文挑战第8天】在人工智能技术飞速发展的今天,一个日益凸显的问题是关于AI的伦理困境。随着机器学习和深度学习技术的进步,AI系统展现出越来越高的自主性,这引发了关于人类责任和控制的哲学讨论。本文将探讨AI自主性的提升如何影响人类的伦理责任,以及我们应如何平衡技术进步与道德考量。
|
消息中间件 Java Kafka
说说RabbitMQ延迟队列实现原理?
说说RabbitMQ延迟队列实现原理?
310 0
说说RabbitMQ延迟队列实现原理?