c++ — 类和对象

Posted 水澹澹兮生烟.

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了c++ — 类和对象相关的知识,希望对你有一定的参考价值。

目录

一.面向对象的程序设计

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;
class Complex {
public:
	Complex(){//构造函数
		real = 0;
		imag = 0;
	}
	Complex(dluble 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;
}
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 类名;

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

以上是关于c++ — 类和对象的主要内容,如果未能解决你的问题,请参考以下文章

C++类和对象--继承

C++类和对象1

成功创建c ++类和对象[重复]

C++从青铜到王者第二篇:C++类和对象(上篇)

C++类和对象的简单应用举例

C++初阶类和对象