c++ — 类和对象

2023-05-16

目录

一.面向对象的程序设计

1.1对象

1.2面向对象的三大特性

1.3类和对象的关系

二.类的声明和对象的定义

2.1声明类的类型

2.2对象的定义

三.类的成员函数

3.1类的作用域

3.2内置成员函数

3.3成员函数的存储方式

四.this指针

五.默认成员函数

5.1初始化和清理

5.1.1构造函数

5.1.2析构函数

5.2拷贝复制

5.2.1拷贝构造函数

5.2.2赋值重载

5.3取地址重载

5.3.1const对象取地址

 六.static成员

6.1静态数据成员

6.1.1静态数据成员形式

6.1.2静态数据成员的特点

6.2静态成员函数

七.友元

7.1友元函数

7.2友元类


我们在学习C语言时知道,C语言是面向过程的语言,他关系的是是完成事情的过程,以函数进行驱动,在对于比较规模的程序来说,我们可以直接编写出一个面向过程的程序,但是对于规模较大的程序,我们就不得不重新思考办法了。

一.面向对象的程序设计

1.1对象

客观世界中,任何一个事物都可以看成是对象。

我们在这里举个栗子:一个学生它具有的属性有姓名,学号,年龄,性别;它所具有的行为有上课,吃饭,睡觉,写作业。在这里我们可知属性静态特征,而此时的行为动态特征。而类就是对其重新抽象。任何一个对象都应当具有属性和行为这两个要素。

int main(){
	struct Student stu1; //在c中,stu1表示结构体变量
	student stu2;//在c++中stu2表示对象
}

1.2面向对象的三大特性

C++是基于面向对象的程序,面向对象有三大特性即:封装、继承、多态。

特性一:封装

将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行 交互所谓的封装就有两方面的含义,一是将数据与方法封装在一个对象中,形成一个基本的单位,各个对象至今互不影响;二是将对象中默写部分对外进行隐蔽,即就是隐蔽内部细节,只留下少量接口以便于与外部进行联系。这种对外界进行隐蔽的做法叫做信息隐蔽

特性二:继承

继承是一个进程,通过一个进程,一个对象可以获得另一个对象的属性(数据和函数),且可以想起加入属于自己的方法和属性。用这种方法可以自动的对一个类中提供另一个类的成员和数据结构。当A类被另B类继承,A类可以称之为基类或者父类;B类被称之为派生类或者子类

特性三:多态

如果几个相似而不完全相同的对象,当对他发出同一个消息时,他们的反映各不相同,执行不同的操作。这种现象叫做多态现象。在C++中多态是指由继承而产生的相关的不同的类,其对象会对同一消息做出不同的相应。他能增加程序的灵活性。

1.3类和对象的关系

在C++中对象的类型叫做类(class)。类描述了对象的共性和特征。类是对象的抽象,而对象是类的实例。

二.类的声明和对象的定义

2.1声明类的类型

class为定义类的关键字,ClassName为类的名字,{}中为类的主体,注意类定义结束时后面分号。类中的元素称为类的成员:类中的数据称为类的属性或者成员变量; 类中的函数称为类的方法或者成员函数。

class student{
	char name[100];
	int ID_number;
	int age;
	char gender;//类的成员变量
	void learning();
	void eating();
	void sleeping();
	void working();//类的成员函数
};
student stu1,stu2;//定义了两个student类的对象

如上现在封装在类中的stu1和stu2两个对象中的成员都对外是隐蔽的,在类外不能直接调用类的成员。但是现在缺少了对外的接口,因此我们要使用成员访问限定符public和private。private声明以下部分为私有,不可以再类的外部进行调用,而pubilc声明以下部分是公有的,可以在类外进行调用。而一般情况下,在类中的定义及不指定是private或者是public时,我们默认为时私有的。

2.2对象的定义

方法一:先声明类类型在定义对象

student stu1,stu2;//直接用类名定义了两个student类的对象

方法二:在声明类的同时定义对象

class student{
	char name[100];
	int ID_number;
	int age;
	char gender;
	void learning();
	void eating();
	void sleeping();
	void working();
}stu1,stu2;//定义了两个student类的对象

方法三:不出现类名,直接定义对象

class{
	char name[100];
	int ID_number;
	int age;
	char gender;
	void learning();
	void eating();
	void sleeping();
	void working();
}stu1,stu2;

直接定义对象在C++中是合法的,但是不常用,不建议使用。

问题:C++中struct和class的区别是什么?

解答:C++需要兼容C语言,所以C++中struct可以当成结构体去使用。另外C++中struct还可以用来定义类。 和class是定义类是一样的,区别是struct的成员默认访问方式是public,class是的成员默认访问方式是 private。

三.类的成员函数

类的成员函数是类的成员,出现在类内。他可以被定义为private,public,protected。成员函数可以访问本类中的所有成员,可以引用在本作用域中的所有数据。一般的做法是将需要被外界调用的成员函数指定为public,他们是类对外的接口。

3.1类的作用域

类定义了一个新的作用域,类的所有成员都在类的作用域中。在类体外定义成员,需要使用 :: 作用域解析符 指明成员属于哪个类域。

void student::prints()
{
 cout<<_name<<" "<<ID_number<<" "<<_gender<<" "<<_age<<endl;
}

如果函数在作用域符::的前面没有类名,或者函数名前面即无类名有无作用域符,如:

 ::函数名()
或
 函数名()

则表示函数不适于任何类,这个函数不是成员函数,而是全局函数,即使普通函数。

3.2内置成员函数

3.2.1内置函数与宏

在C中,宏定义是十分常用的,但是又有许多缺陷。"宏并不是函数,宏并不是语句,宏并不是定义类型。"他是直接嵌入的,代码量较多,容易出错,而且无类型,无法进行类型检测,对带参的宏来说,并不会检查参数的合法性。因此在C++中提供以inline修饰的函数即内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数压栈的开销, 内联函数提升程序运行的效率。

内联函数的特点:

  1. inline是一种以空间换时间的做法,省去调用函数额开销。所以代码很长或者有循环/递归的函数不适宜使用作为内联函数。
  2. inline对于编译器而言只是一个建议,编译器会自动优化,如果定义为inline的函数体内有循环/递归等 等,编译器优化时会忽略掉内联。
  3. inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。

3.2.2内置成员函数

在类体中定义的成员函数中不包括循环控制结构,C++就会自动对他们处理做内联函数,这样可以大大减少调用成员函数的时间。C++要求在对一般内置函数要用关键字inline声明,但对类定义的成员函数,可以省略,因为这些成员函数已经被隐含的指定为内置函数。

 class Date{
     public:
        ...
         inline void display();//声明此成员为内置函数
        ...
 };

 inline void Date::display(){//在类外定义display()函数为内置函数
     cout<<"the date is"<<"  year:"<<_year<<"  month:"<<_month<<"  day:"<<_day<<endl;
 }

3.3成员函数的存储方式

同一类的不同对象中的数据成员的值一般是不相同的,而不相同对象的函数的代码是相同的,无论调用哪一个对象函数的代码,其实都是调用同样内容的代码。这样做会大大节约内存空间。

在这里如何求类的大小呢?

一个类的大小,实际就是该类中”成员变量”之和,当然也要进行内存对齐,注意空类的大小,空类比 较特殊,编译器给了空类一个字节来唯一标识这个类。

内存对齐规则:

  1. 第一个成员在与结构体偏移量为0的地址处。
  2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。 VS中默认的对齐数为8
  3. 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
  4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是 所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

举个栗子:

class{
    public:
        int hour;
        int minute;
        int sec;
        void set(){
                cin>>a>>b>>c;
    }
};

在这里类所占的空间大小为12个字节,说明对象所占空间大小只取决于该对象中成员所占空间,而与成员函数无关。

四.this指针

在之前我们提到每个对象中的数据成员都分别占有存储空间,如果对同一个类定义了n个对象,即有n组同样大小的空间以存放n个对象中的数据成员。但是不同的对象都调用同一个函数的目标代码。在每一个成员函数中都包含一个特殊的指针,这个指针的名字是固定的,称之为this。他是指向本类对象的指针(他被作为隐藏参数传递给方法),他的值是当前被调用的成员函数所在的对象的起始地址,一般来说所有的类方法都将this指针作为条用它的对象的地址。this指针的空间在栈上,而this指针有可能为空,但是会崩溃。

例如:

int Date::get_date(){
    cin>>_year;
    cin>>_month;
    cin>>_day;
}

会被处理为:

int Date::get_date(Date *this){
    cin>>this->_year;
    cin>>this->_month;
    cin>>this->_day;
}

即在成员函数的形参列表中增加了一个this指针。在调用该成员函数时,实际上以t1.get_date(&t1);的方式调用的,将对象t1的地址传给形参this指针,然后按this指针的指向其引用其他成员。而这些都是编译系统自动实现的,我们不必自己动手在形参中添加this指针。

注意!!!!

每个成员函数(包括构造和析构函数)都有一个this指针,this指针指向调用对象。如果方法需要引用整个调用对象,则可使用表达式*this,在函数括号后面面使用const限定符将this进行限定,就不可以使用this来修改对象的值。然而,返回的并不是this,因为this是对象的地址,而不是对象本身,即*this(将解除引用运算符用到指针前,将得到指针指向的值)。现在可以将*this作为调用对象的别名完成前面的方法定义。

this指针的特性:

  1. this指针的类型:类类型* const(在C里面相当于常量引用)
  2. 只能在“成员函数”的内部使用
  3. this指针本质上其实是一个成员函数的形参,是对象调用成员函数时,将对象地址作为实参传递给this 形参。所以对象中不存储this指针。
  4. this指针是成员函数第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递

五.默认成员函数

如果一个类中什么成员都没有,简称为空类。任何一个类在我们不写的情况下,都会自动生成下面6个默认成员函数。我们将六个默认函数分成三大类:初始化和清理,拷贝复制,取地址重载。

5.1初始化和清理

5.1.1构造函数

在基于对象的程序中,在定义一个对象时,也需要对数据成员进行赋值,但是有一点特别重要,直接在声明类时对数据成员进行初始化是不正确的,因为类并非是一个实体,而是一种抽象类型,并不占存储空间。如果一个类中的所有成员都是公有的,则可以在定义对象是对数据成员进行初始化。但是如果数据成员是私有的呢,又该如何处理。这是我们就要用到C++为我们提供的构造函数来处理对象的初始化。构造函数是一中特殊的成员函数,与其他的成员函数不同,不需要用户来调用它,而是在建立对象是自动执行。

a.用构造函数实现数据成员的初始化

构造函数是特殊的成员函数,需要注意的是,构造函数的虽然名称叫构造,但是需要注意的是构造函数的主要任务并不是开空间创建对象,而是初始化对象。他的特征如下:

  1. 构造函数的名字必须与类名相同,而不能任意命名;
  2. 它不具有任何类型,不返回任何值;
  3. 编译器自定调用;
  4. 在整个对象的声明周期内只调用一次;
  5. 如果没有显示任何构造函数,则会生成一个默认的无参构造函数。

b.带参的构造函数

有时我们希望对不同的对象赋予不同的值,这是就无可以使用带参的构造函数,在调用不同对象的构造函数时从外面将不同的数据传递给构造函数,以实现不同的初始化。他的一般格式如下:

//构造函数首部的一般格式
构造参数名(类型1 形参1,类型2 形参2,...)

//定义对象的一般格式
类名 对象名(实参1,实参2,...);

在这里我们要注意,带参数的构造函数中的形参,其对应的实参实在建立对象时给定的。在建立对象的同时指定数据成员的初始值。 定义不同对象时使用的是参数不同的,他们反映了建立对象的属性,此方法可实现对不同对象的不同初始化。

c.用参数初始化表对数据成员初始化

虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能将其称作为类对象成员的初始化,构造函数体中的语句只能将其称作为赋初值,而不能称作初始化。因为初始化只能初始化一次,而构造函数体内 可以多次赋值。通过赋值语句可以对数据成员进行初始化,C++还提供了另一种初始化数据成员的方法--参数初始化列表来实现对数据成员的初始化

带有参数初始化表的构造函数一般形式:

类名::构造函数([参数列表])[:成员初始化表]{
    [构造函数体]
}

注意:

  1. 每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
  2.  类中包含以下成员,必须放在初始化列表位置进行初始化:(引用成员变量;const成员变量;自定义成员变量)

  3. 尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使 用初始化列表初始化。
  4. 成员变量在类中声明的次序就是其在初始化列表中初始化的次序,与其在初始化列表中的先后顺序无关。

构造函数初始化时必须采用初始化列表一共有三种情况:

  1. 需要初始化的数据成员是对象(继承时调用基类构造函数)
  2. 需要初始化const修饰的类成员
  3. 需要初始化引用成员数据

d.构造参数重载

为了给对象不同的初始化方法,我们可以在一个类中定义多个构造函数,虽然这些构造函数名字相同但是他们的参数或者参数类型不相同,这就称为函数的重载。

我么在这里举个栗子:

在建立对象是不给出实参的构造函数,称之为默认构造函数,无参构造函数和全缺省的构造函数都是默认构造函数,但是它们两个不能同时存在,会产生二义性。 在类中没有显示任何的构造函数时,编译器会自动生成一个无参构造函数。

//无参的构造函数
Date(){}
//全缺省的构造函数
Date(int _year=1,int month=1,int day=1){
    _year=year;;
    _month=month;
    _day=day;
}

e.explicit关键字

构造函数不仅可以构造与初始化对象,对于单个参数的构造函数,还具有类型转换的作用。 explicit关键字又来修饰构造函数,将会禁止单构造函数的隐式转换。 

(链接:单参构造函数和类型转化函数)

5.1.2析构函数

它也是一个特殊的成员函数且与构造函数功能相反,析构函数不是完成对象的销毁,局部对象销毁工作是由编译器完成的。而对象在销毁时会自动调用析构函数,完成类的一些资源清理工作。当对象生命周期结束时,会自动调用析构函数。当这个函数被调用结束时,对象应该对象应该被释放,在对象释放前自动执行析构函数。析构函数不返回任何值,没有任何函数类型,也没有函数参数。一个类可以有多个构造函数但是只能有一个析构函数,也就是说,析构函不能进行重载。如果用户没有定义析构函数,那么编译器会自动生成一个析构函数。

析构函数的特征:

  1. 析构函数名是在类名前加上字符 ~。
  2. 无参数无返回值。
  3. 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。
  4.  对象生命周期结束时,C++编译系统系统自动调用析构函数。 

5.2拷贝复制

5.2.1拷贝构造函数

a.拷贝构造函数的概念

只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象 创建新对象时由编译器自动调用。

b.拷贝构造函数的特点

拷贝构造函数也是特殊的成员函数,它的特点如下:

class Date
{
    public:
      Date(int year = 1900, int month = 1, int day = 1){
         _year = year;
         _month = month;
         _day = day;
     }
     Date(const Date& d){ //拷贝构造函数
        _year = d._year;
        _month = d._month;
        _day = d._day;
     }
    private:
     int _year;
     int _month;
     int _day;
};
  1. 拷贝构造函数是构造函数的一个重载形式;
  2. 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用;

  3.  若未显示定义,系统生成默认的拷贝构造函数。默认的拷贝构造函数对象按内存存储按字节序完成拷贝,这种拷贝我们叫做浅拷贝,或者值拷贝。

    Date d1(1900,1,1);
    Date d2(d1);//将d1对象中的成员拷贝放置到d2中

    在上面代码中,如果要对其进行析构时(此时d1与d2公用同一个空间),先销毁d2,然后释放成野指针,再一次释放d1时会崩溃。 如果一个类中未涉及到了资源管理时,拷贝函数是否提供都可以,但是涉及到了资源管理时拷贝构造函数必须提供,否则编译器生成的默认拷贝构造函数会出现问题。在底层他们共享同一份资源,这种拷贝叫做浅拷贝,浅拷贝会存在内存泄漏。(浅拷贝与深拷贝)

c.拷贝构造函数的调用时机

  1. 对象以值传递的方式传递函数参数
    class Date{
    public:
    	//构造函数
    	Date(int y){
    		year=y;
    	}
    	Date(const Date& d){
    		year=d.year;
    	}
    private:
    	int year;
    };
    void get_year(Date d1){
    	cout<<"aaa"<<endl;
    }
    int main(){
    	Date d(1900);
    	//传入对象
    	get_year(d);
    	return 0;
    }
    //调用get_year()时会的步骤:d对象传入形参时,先会产生一个临时变量d1,
    //然后调用拷贝构造函数将d的值给d1。等get_year()执行结束后析构掉d1对象。 
  2. 对象以值传递的方式进行函数返回
    class Date{
    public:
    	//构造函数
    	Date(int y){
    		year=y;
    	}
    	Date(const Date& d){
    		year=d.year;
    	}
    private:
    	int year;
    };
    void get_year(){
    	Date y(1800);
    	return y;
    }
    int main(){
    	get_year();
    	return 0;
    }
    //在这个过程中调用get_year()所要的过程:
    //先会产生一个临时变量(X),然后调用拷贝构造函数将y的值传给这个临时变量(X)
    //到函数执行到最后先析构y局部变量
    //最后在get_year()执行完后在析构X对象。
    
    
  3. 一个对象要通过另一个对象进行初始化 

5.2.2赋值重载

a.运算符重载的含义 

函数重载就是对一个已有的函数赋予新的含义且可以实现新得功能。C++为了增强代码的可读性引入了运算符重载。

b.运算符重载的方法

运算符重载是通过定义函数实现的,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。运算符重载实质上就是函数重载。函数名字为:关键字operator后面接需要重载的运算符符号。 函数原型:返回值型 operator操作符(参数列表)。

他的一般格式如下:

函数类型 operator运算符名称(形参列表){
    //对运算符进行重载处理
}

//如:
Date operator+(Date& d);

举个栗子,实现两个复数相加:

#include<iostream>
using namespace std;
#include<iostream>
using namespace std;
class Complex {
public:
	Complex(){//构造函数
		real = 0;
		imag = 0;
	}
	Complex(double r, double l){
		real = r;
		imag = l;
	}
	//声明重载运算符+
	Complex operator+(Complex& c2);
	void display();
private:
	double real;
	double imag;
};
Complex Complex::operator+(Complex& c2){
	Complex c;
	c.real = real + c2.real;
	c.imag = imag + c2.imag;
	return c;
}
void Complex::display(){
	cout << "(" << real << "," << imag << ")" << endl;
}
void Complex::display(){
	cout << "(" << real << "," << imag << ")" << endl;
}
int main(){
	Complex c1(1, 2), c2(4, -1);
	Complex cc;
	cc = c1 + c2;
	cout << "c1+c2=";
	cc.display();
	return 0;
}

运行结果: 

 

对下面代码进行比较:

class Complex{
 public:
    Complex add(Complex& c2);
    Complex operator+(Complex& c2);
 private:
    ...
};
Complex Complex::add(Complex& c2){
    Complex c;
	c.real = real + c2.real;
	c.imag = imag + c2.imag;
	return c;
}
Complex complex::operator+(Complex& c2){
    Complex c;
	c.real = real + c2.real;
	c.imag = imag + c2.imag;
	return c;
}

int main(){
    Complex c1(1,2),c2(3,4),c3,c4;
    c3=c1.add(c2);
    c3=c1+c2;
}

 在计算c3中我们调用了一个add()加法函数,而在计算c4时,C++编译器将其翻译成c1.operator+(c2),进行两个复数相加。

c.重载运算符的规则

  1. 不能通过连接其他符号来创建新的操作符:比如operator@,只能对已有的运算符进行重载
  2. 重载不能改变运算符运算对象的个数(重载运算符的函数不能有默认的参数,否则就会改变运运算符参数的个数),不能改变运算符的优先级别,不能改变运算符的结合性
  3. 重载操作符必须有一个类类型或者枚举类型的操作数
  4. 用于内置类型的操作符,其含义不能改变,例如:内置的整型+,不能改变其含义
  5. 用于对象的运算符一般必须重载,但有两个例外,运算符"="和"&"一般不用用户进行重载
  6. 作为类成员的重载函数时,其形参看起来比操作数数目少1成员函数的操作符有一个默认的形参this,限定为第一个形参
  7. .*::sizeof?:注意以上5个运算符不能重载。这个经常在笔试选择题中出现。

 d.赋值运算符的重载

在上面的知识点3中,我们知道一般情况下"="不必要用户进行重载,但是当系统提供给的默认的对象赋值运算符不能满足时,,例如数据成员中包含指向动态分配的指针成员时,在复制此成员是可能存在危险,就需要自己重载赋值运算符。

***了解运算符重载中使用引用的重要性:利用引用作为函数参数的形参可以再调用函数的过程中不适用传递值的方式进行虚实结合,而是通过传址方式使形参成为实参的别名,因此不生成临时变量(实参的副本),因此减少了时间和空间的开销。此外重载函数的返回值是对象的引用时,返回的不是常量,而是引用所代表的对象,它可以成为赋值运算符的左值,可以被赋值或者参与其他操作,但是要注意的是修改可引用,就等于修改可他所代表的对象。***

5.3取地址重载

在这里我们对普通对象取地址就不多说了,直接看对const取地址操作符重载。

5.3.1const对象取地址

a.const成员函数

在之前我哦们就已经知道const关键字,在C中我们常用#define指令爱定义符号常量,但是用这种方法容易出错,因此C++提供了const定义常变量的方法。const常与指针结合。const可以用来修饰成员函数和成员变量。这里用const修饰的成员函数称之为const成员函数,实际上,const修饰的是成员函数的隐含的this指针,表明该成员函数中不能对任何成员进行修改。

先举个栗子:

class A{
    public:
        int T1();
};
//例1
const int T1(const int a) const{
...}
//例2
const A& T2(const A& a) const{
...}

我们现在看上述例1的代码,出现了3个const,第一const修饰的是返回值,第二个const修饰的是函数的参数,第三个const修饰的是成员函数本身(也就是所谓的const成员函数)。将const修饰的类成员函数称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this 指针,表明在该成员函数中不能对类的任何成员进行修改。

我们此时再看例2的代码,该函数隐式的访问一个对象,而显式的访问了另一个对象,并返回其中一个对象的引用。第一个const表示返回const引用,第二个const表示该函数不会修改被显式地访问的对象,第三个const表明该函数不会修改被隐式的访问的对象。

我们现在根据几个问题来思考:

const对象可以调用非const成员函数吗?

直接上代码:

 因此我们可以知道,const对象不可以调用非const成员函数,但是非const对象可以调用const成员函数,因为非const对象的权限更大。

const成员函数内可以调用其他非const成员函数吗?

由此可见,编译不能通过,但是在非const成员函数中调用const成员函数是可以的。

在const成员函数中如果要进行修改的话,就必须使用关键字mutable进行修饰。

 b.const成员变量

const修饰成员变量相当于该变量是一个常量,所以只能在初始化列表上初始化。

举个栗子:

class Date{
    public:
      Date(int y = 1900,int m = 1,int d = 1):year(y),month(m)day(d){}
    private:
      int year;
      int month;
      int day;
};

 六.static成员

如果想在同类的多个对象之间实现数据共享,不想用全局对象,可以使用静态数据成员。

6.1静态数据成员

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

6.1.1静态数据成员形式

数据类型 类名::静态数据成员 = 初值;
int Date::year = 1900;

6.1.2静态数据成员的特点

  1. 静态数据成员只占一份空间,每个对象都可以引用这和静态数据成员。静态数据成员的值对所有对象都是一样的,改变它的值所有对象中的这个数据成员的值都会改变。静态数据成员不属于某一个对象,在为对象分配的空间中不包括静态数据成员所占的空间。静态数据成员实在所有对象之外单独开辟空间。
  2. 静态数据成员是在程序编译时分配的空间,在程序结束时才释放空间
  3. 静态数据成员可以进行初始化,但只能在类外进行初始化
  4. 在类外可以通过对象名引用公用的静态数据成员,也可以通过类名引用静态数据成员。(即使没有定义对象,也可以通过类名来引用静态数据成员,这说明静态数据成员并不属于对象,而是属于类的。)
  5. 有了静态数据成员,不必再使用全局变量,全局变量破坏了封装性原则。但也要注意,静态数据成员的作用域只限定于定义该类的作用域内。

6.2静态成员函数

用static修饰的成员函数,称之为静态成员函数。

class Date{
 public:
    static void get_year();
 private:
    int year;
};
Date::get_year(){
    cout<<year<<endl;
}

静态成员函数是类的一部分而不是对象的一部分,如果要在类外调用公有的静态成员函数,要用类名和域作用符"::"。与静态数据成员不同,静态成员函数的作用是为了能处理静态数据成员。静态成员函数与非静态成员函数最大的区别就是,静态成员函数没有this指针,而非静态成员函数有this指针,由此决定了静态函数无法访问本类中的非静态成员。

七.友元

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

7.1友元函数

如果在本类以外的其他地方定义了一个函数,再累体内用friend进行声明,就将其称之为友元函数。友元函数可以访问这个类内的私有成员。

例1:

//将普通函数声明成友元函数
#include<iostream>
using namespace std;

class Date{
 public:
    firend void display(Date&);//声明display()是Date类的友元函数
 private:
    int year;
};
void display(Date& y){
    cout<<y.year<<endl;
}

例二:

calss A;
class B{
 pbulic:
    void display(A&);//display()是成员函数,形参是Date类对象的引用
 private:
    int b;
};
class A{
 public:
    friend void B::display(A&);//声明B中的display()是本类中的友元成员函数
 private:
    int a;
};

注意:

  1. 友元函数可访问类的私有和保护成员,但不是类的成员函数
  2. 友元函数不能用const修饰
  3. 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
  4. 一个函数可以是多个类的友元函数
  5. 友元函数的调用与普通函数的调用和原理相同

7.2友元类

友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。 友元关系是单向的,不具有交换性。 比如上述B类和A类,在B类中声明A类为其友元类,那么可以在A类中直接访问C 类的私有成员变量,但想在C类中访问B类中私有的成员变量则不行。 友元关系不能传递 如果B是A的友元,C是A的友元,则不能说明C时B的友元。

声明友元类的一般形式:

friend B;
//friend 类名;

***友元关系是单向而不是双向,友元的关系不能传递。***

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

c++ — 类和对象 的相关文章

  • python基础-古诗词填词游戏

    很久没写爬虫了 xff0c 利用这次接单来顺便写一下爬虫 文章目录 1 项目需求2 思路梳理3 诗句处理遇到的问题有4 目录结构5 实现步骤6 收获7 不足 1 项目需求 用python实现古诗词填词游戏 诗词库的组成 初中古诗 备注 xf
  • 谈谈如何确保数据的一致性

    谈谈如何确保数据的一致性 数据库必须具备的四个特性背景什么是接口的幂等性 xff1f 幂等性在哪里会用到 xff1f 技术方案总结 参考https blog csdn net u011635492 article details 81058
  • vmware esxi 虚拟机管理常用命令

    1 查看所有虚拟机列表 vim cmd vmsvc getallvms 2 挂起虚拟机 vim cmd vmsvc power suspend 13 命令行中的数字都是为vmid xff0c 以下同理 xff01 3 恢复或打开虚拟机 vi
  • python+tensorflow CNN卷积神经网络手写字体识别

    导入所需的库模块 xff1a span class token keyword import span os span class token keyword import span cv2 span class token keyword
  • 报错记录:Error in grid.Call(C_stringMetric, as.graphicsAnnot(x$label))

    今天在跑monocle2 xff0c 出图时报了个错 Fibroref5k lt setOrderingFilter Fibroref5k disp genes p1 lt plot ordering genes Fibroref5k p1
  • KMP算法——字符串匹配问题

    贴上原址 xff1a http www ruanyifeng com blog 2013 05 Knuth E2 80 93Morris E2 80 93Pratt algorithm html 感觉这篇文章讲得很不错 xff0c 很容易懂
  • 教你怎么在Pycharm上安装Manim(Pycharm+Manim)

    首个教你怎么在Pycharm下安装Manim的教程 1 安装环境 PycharmGitManim 前两个都是程序员标配了 只需要去github download zip或者本地git clone一下Manim就可以 2 创建工程 把下载完成
  • java 后台list转换成json向前台传值

    通常前台js需要对后台传过来的值进行解析 xff0c 如果后台向前台传入的是一个json串的话 xff0c js比较容易处理 后台 根据自己需求写一个list List lt Object gt list 61 assistAdpater
  • windows美化指南秒变mac风格

    windows美化指南秒变mac风格 现如今网络的发展 xff0c 几乎每个人都有了一台电脑 xff0c 随着时代的进步 xff0c 由原来的winxp到win7到win10再到现在的win11 xff0c 每个系统无论是从底层架构还是外观
  • shell中使用ssh批量重启关闭/etc/hosts中添加了的机器

    ssh批量关闭 etc hosts中添加了的机器 xff0c 可自行修改 span class token shebang important bin bash span span class token keyword while spa
  • Windows Terminal的oh-my-posh配置方案

    参考Tutorial Set up a custom prompt for PowerShell or WSL with Oh My Posh 1 安装字体 NerdFonts字体下载 本人选择DejaVuSanMono Nerd Font
  • PX4系统学习

    PX4系统学习 扑翼飞行器的硬件组成飞控板电调电调的分类 舵机 扑翼飞行器的硬件组成 要了解学会二次开发首先要知道 xff0c 手头的飞行器的硬件结构 以及各个部分的结构是有何种作用的 xff0c 这样才能在根本上了解编程逻辑 xff0c
  • Java歌手评分系统

    有五个评委 xff0c 对一个歌手唱歌打分 xff0c 最终得分要求去掉最高分去掉最低分 xff0c 求平均分 注意要求的格式为 xff1a 输入第1个评委给分 97 1 输入第2个评委给分 89 2 输入第3个评委给分 88 6 输入第4
  • Linux 生产者消费者问题代码实现

    进程间通信 Linux xff1a 使用多线程和信号量解决生产者 消费者问题 xff1a 有一个长度为N的缓冲池被生产者和消费者共同使用 只要缓冲池未满 xff0c 生产者就可以将消息送入缓冲池中 xff1b 只要缓冲池不空 xff0c 消
  • Nocas单机启动命令

    之前在Linux中单机启动Nocas使用命令 xff1a sh startup sh m standalone 但是在cmd窗口使用该命令不行 xff0c 因为sh命令是在Linux系统中的文件 xff1b 而在本机cmd窗口运行Nocas
  • 项目中的resources目录中新建yml或properties文件,但是启动项目,没有读取到配置文件中的配置

    我的文件的图标显示这样 xff0c 说明SpringBoot加载时没有加载这个文件 1 查看yml properties文件中的内容是否有错 2 重启idea重启项目 3 手动设置该文件夹和文件为Spring的配置文件 右击resource
  • 二维数组的子数组之和的最大值

    对于一维的数组 xff0c 要求其子数组之和的最大值很简单 xff0c 动态规划轻松解决 xff0c 复杂度O N span style background color rgb 255 255 255 font family none f
  • MySQL错误ERROR 1046 (3D000): No database selected解决办法

    No database selected可以理解为没有选择种数据库 先在查看数据库种包括的database有哪些 xff0c 选择自己要操作的数据 记住database后面一定加s 选择自己要操作那一个数据库 这样选择之后mysql gt
  • CentOS7使用su命令切换到root用户:su:鉴定故障

    安装完CentOS7后 xff0c 在设置过程中设置了root密码 xff0c 但是当从普通用户切换到root用户时报错 这时就想到可以将系统模式切换到单用户模式下 xff0c 修改root密码 重启CentOS7系统 xff0c 按e键
  • CentOS7版本设置系统的静态IP地址

    每次CentOS7重新开机 xff0c 系统的IP地址都会改变 xff0c 使用起来很不方便 xff0c 所以就将系统的IP设置为静态不变的IP 首先需要找到CentOS7版本的网络配置文件在哪里 xff0c 版本不一样可能配置文件不一样

随机推荐

  • “Job for docker.service failed because the control process exited with error code.” 问题解决

    前一天使用Docker还可以 xff0c 第二天使用systemctl start docker命令启动 xff0c 结果 我找问题花了两个小时 xff0c 在哪里一通乱找 xff0c 一通乱查 xff0c 网上乱看博客 xff0c 浪费了
  • RocketMQ同一Topic、消费组创建多个消费者失败问题

    文章目录 业务场景问题复现解决方式问题跟踪 业务场景 rocketmq建议一个服务对应一个topic xff0c 但是一个服务下会有多个不同的业务消息 xff0c 同时rocketmq建议不同的业务消息对应不同的tag xff0c 当Spr
  • AD18设计PCB时常见问题及操作

    我做PCB设计时 xff0c 常采用AD18这个软件 xff0c 使用过程中经常碰到一些问题 xff0c 遇到查了半天解决了 后来又碰到了 xff0c 索性记一下吧 xff0c 以后碰到的也陆陆续续记上来 xff0c 图片不一定用自己的了
  • C/C++中的new和delete的实现过程

    文章目录 newdeletenew delete 下面是 C 43 43 Primer 5th 中P726 对 new 和 delete 过程的解释 xff1a 当我们使用一条new表达式时 xff0c 实际上执行了三步操作 xff1a n
  • 判断链表是否有环

    题目 xff1a 给你一个链表的头节点 head xff0c 判断链表中是否有环 如果链表中有某个节点 xff0c 可以通过连续跟踪 next 指针再次到达 xff0c 则链表中存在环 为了表示给定链表中的环 xff0c 评测系统内部使用整
  • Linux 我已经搭建好LAMP,怎么让别人访问到我的服务器(通过外网),端口映射,内网穿透

    使用Ngrok 免费 xff09 1 下载Ngrok https dashboard ngrok com get started setup 2 解压并移动到 usr bin mv ngrok usr bin 3 映射127 0 0 1 x
  • Springboot自动装配原理

    springboot配置文件的装配过程 1 springboot在启动的时候会加载主配置类 xff0c 开启了 64 EnableAutoConfiguration 2 64 EnableAutoConfiguration的作用 xff1a
  • 百度面试基础问题

    上午百度面试 xff0c 我投的测试 xff0c 文三路伊美大酒店 xff0c 面了接近一个小时 xff0c 问了很多基础的东西 xff0c 我有些混淆也有些回答得不全面 xff0c 可能跪了 xff0c 记录一下面试题吧 xff0c 权当
  • Python的Playwright - 1.2 ConsoleMessage

    ConsoleMessage对象通过page on console 事件按页面调度 常见的几种属性 console message argsconsole message locationconsole message textconsol
  • nginx配置导致文件上传失败

    nginx配置导致文件上传失败 问题描述问题原因解决方法 问题描述 项目在客户使用文件上传是出现了意外报错 xff0c 通过查看报错日志 xff0c 发现是nginx服务器配置导致的错误 日志报错信息如下 xff1a 2023 04 14
  • TortoiseSVN的基本使用

    TortoiseSVN的基本使用 SVN xff1a 代码版本管理工具 xff1b 他能记住每次的修改 xff1b 查看所有的修改记录 xff1b 恢复到任何历史版本 xff1b 恢复已经删除的文件 SVN与Git比的优势 xff1a 使用
  • Ubuntu18.04 反复进入登陆页面

    在安装openssh server和openssh client重启之后 xff0c 登陆用户黑屏然后再次进入登录页面 尝试过删除 Xauthority xff1a 提示没有这文件 最终解决方法 xff1a ctrl 43 alt 43 f
  • 路径、连通、连通图,强连通图、连通分量、极大连通子图以及割点、割边保姆级解释

    前言 xff1a 由于在学习最大割的过程中涉及很多定义 xff0c 下面先回顾一下关于路径 连通 连通图 xff0c 强连通图 连通分量 极大连通子图以及割点 割边的定义 目录 1 路径 2 连通 3 连通图 4 强连通图 5 连通分量 极
  • 应用宝sdk接入流程与注意事项总结

    众所周知 xff0c 应用宝sdk几乎是国内应用市场sdk中最难接入的Android sdk xff0c 很多人初始接入都会感觉很痛苦 xff0c 文档多又乱 xff0c 问题排查也是非常蛋疼的事情 xff0c 所以 xff0c 今天抽了个
  • SecoClient接收返回码超时解决办法

    SecoClient接收返回码超时解决办法 参考网址 步骤 xff1a 第一步 xff0c Windows键进入 34 设置 34 第二步 xff0c 进入 34 更新与安全 34 第三步 xff0c 找到恢复 xff0c 点击 高级启动
  • 一、ROS的五个特点

    一 ROS的五个特点 xff1a 1 点对点设计 点对点的设计通俗的讲就是一个大的工程项目 xff0c 每个动能相互是独立的 xff0c 或者耦合比较低 有一个大脑负责总的任务调度 xff0c 为服务和客户建立连接充当媒介 各个功能之间可以
  • 双系统格式化Ubuntu分区后Windows 开机出现grub rescue问题

    双系统格式化Ubuntu分区后Windows 开机出现grub rescue问题 开始电脑上装了Windows和ubuntu双系统 xff0c 突然一天脑子一热直接把ubuntu所在盘给格式化了 xff0c 潇洒的打开 xff0c 结果就出
  • 计算机组成原理常考大题合集

    计算机组成原理常考大题合集 1 在 Cache 主存 辅存 三级存储体系中 xff0c Cache 主存 结构与 主存 辅存 结构的引入为了解决什么问题 xff1f 答案扩展 2 在CPU中 xff0c 那些寄存器属于控制用指令部件 xff
  • 转载Socket详解

    一切皆Socket xff01 话虽些许夸张 xff0c 但是事实也是 xff0c 现在的网络编程几乎都是用的socket 有感于实际编程和开源项目研究 我们深谙信息交流的价值 xff0c 那网络中进程之间如何通信 xff0c 如我们每天打
  • c++ — 类和对象

    目录 一 面向对象的程序设计 1 1对象 1 2面向对象的三大特性 1 3类和对象的关系 二 类的声明和对象的定义 2 1声明类的类型 2 2对象的定义 三 类的成员函数 3 1类的作用域 3 2内置成员函数 3 3成员函数的存储方式 四