c++--面向对象的详述

面向过程的设计方法

  • 重点:
    如何实现的细节和过程,将数据与函数分开。
  • 形式:
    主模块+若干个子模块(main()+子函数)。
  • 特点:
    自顶向下,逐步求精——功能分解。
  • 缺点:
    效率低,程序的可重用性差。

面向对象的方法

  • 目的:
    实现软件设计的产业化。
  • 观点:
    自然界是由实体(对象)所组成。
  • 程序设计方法:
    使用面向对象的观点来描述模仿并处理现实问题。
  • 要求:
    高度概括、分类、和抽象。

面向对象4大特点

对象:构成系统的基本单位。
对象要素: 1. 属性 2. 行为
C++中的对象都是由数据和函数构成。

抽象

  • 抽象是对具体对象进行概括,抽出这一类对象的公共性质并加以描述的过程。
    先注意问题的本质及描述,其次是实现过程或细节。
  • 数据抽象:描述某类对象的属性或状态(对象相互区别的物理量)。
  • 代码抽象:描述某类对象的共有的行为特征或具有的功能。
  • 抽象的实现:通过类的定义。

例如:
抽象实例–钟表

  • 数据抽象:
       int hour,int minute,int second
  • 代码抽象:
         setTime();showTime();

抽象实例–钟表类
class Clock{
 public:
   void setTime(int h,int m,int s);
   void showTime();
 private:
   int hour,minute,second;
};

封装

  • 将抽象出的数据成员、代码成员相结合,将他们视为一个整体。
  • 目的:增强安全性和简化编程,使用者不必了解具体的实现细节,而只需要通过外部接口,以特定的访问权限,来使用类的成员。
  • 实现封装:类定义中的{}

实例:
在这里插入图片描述

继承与派生

  • 是C++中支持层次分类的一种机制,允许程序员在保持原有类特性的基础上,进行更具体的说明。
  • 实现:定义派生类

多态性

多态:同一名称,不同的功能实现方式。
目的:达到行为标识的统一,减小程序中标识符的个数。
实现:重载函数和虚函数


类的声明与对象的定义

简述

  1. c++中对象的类型被称为类。
  2. 类是具有相同属性和行为的一组对象的集合,它为属于该类的全部对象提供了统一的抽象描述,其内部包括属性和行为两个主要部分。
  3. 利用类可以实现数据的封装、隐藏、继承与派生。
  4. 利用类易于实现大型复杂程序,其模块化程度比c中采用函数更高。

类的定义形式

声明类的方法是由声明结构体类型的方法发展而来的。
类的声明以“;”结束。

  • 类是一种用户自定义类型,定义形式:
    class 类名称{
      public: 公有成员(外部接口)
      private: 私有成员
      protected:保护型成员
    }
  1. 若类的定义中既不指定private也不指定public则默认为private类型。

  2. private 、public、protected被称为成员访问限定符。

    class Student {
    public:
    	void display();
    private:
    	int sno;
    	string name;
    };
    

定义对象的方法

  • 先声明类类型,然后定义对象
    c++中声明了类的类型后,定义对象的两种方法:
    1. class 类名 对象名;

      class Student stu;

    2. 类名 对象名;

      Student stu;

  • 在声明类的同时定义对象
    class Student {
    public:
        void display();
    private:
         int sno;
         string name;
    } stu;
    


类的成员类型:

  • 公有类型成员:
    在关键字public后面声明,它们是类与外部的接口,任何外部函数都可以访问公有类型数据和函数。

  • 私有类型成员
    1. 在关键字private后面声明,只允许本类中的函数访 问,而类外部的任何函数都不能访问。
    2. 如果紧跟在类名称的后面声明私有成员,则关键字private可以省略。

            > Class point{
            > int x; int y
            > }
    
  • 保护类型
    与private类似,其差别表现在继承与派生时对派生类的影响不同


类的成员

成员数据

与一般的变量声明相同,但需要将它放在类的定义体中。

成员函数

  • 在类中说明原型,可以在类外给出函数体实现,并在函数名前使用类名加以限定。

    class Student {
    public:
    	void display();
    private:
    	int sno;
    	string name;
    };
    void  Student::display() {
    	cout << sno << "  " << name;
    }
    
  • 直接在类中给出函数体,形成内联成员函数。

    class Student {
    public:
    	void display() {
    		cout << sno << "  " << name;
    	}
    private:
    	int sno;
    	string name;
    };
    
    
  • 允许声明重载函数和带默认形参值的函数

内联成员函数

  • 为了提高运行时的效率,对于较简单的函数可以声明为内联形式。
  • 内联函数体中不要有复杂结构(如循环语句和switch语句)。
  • 在类中声明内联成员函数的方式:将函数体放在类的定义中。

    1.不加inline关键字
    在这里插入图片描述
    2. 使用inline关键字。
    在这里插入图片描述

成员函数的存储方式

  • 同一个类的不同对象中的数据成员的值一般是不相同的,而不同对象函数代码相同的,不论调用哪个对象的函数代码,调用的都是相同内容。
  • c++编译系统用同一段空间存放共同函数的目标代码。在调用各对象的函数时,都去调用这个公共函数代码。
  • 每个对象所占用的内存空间只是其数据成员所占用的存储空间。
    1. 无论成员函数用何种方式定义的,它们的存储方式都相同,不会占用对象的存储空间。
    2. inline函数也不占用对象空间,它只是提高了程序执行效率。


对象

类的对象是该类的某一特定实体,即类类型的变量。

定义形式:

类名 对象名; // 不用new

例:
Clock myClock;

类中成员的访问方式

只能访问public成员。

  • 通过对象名和成员运算符访问
    一般形式:
    对象名.成员名;

    #include <iostream>
    #include <string>
    using namespace std;
    class Student {
    public:
    	Student() { sno = 123; name = "yj"; }
    	void display() {
    		cout << sno << "  " << name;
    	}
    private:
    	int sno;
    	string name;
    };
    int main() {
    	Student s;
    	s.display();//123  yj
    }
    
    
  • 通过指向对象的指针访问对象中的成员

    class Student {
    public:
    	Student() { sno = 123; name = "yj"; }
    	void display() {
    		cout << sno << "  " << name;
    	}
    private:
    	int sno;
    	string name;
    };
    int main() {
    	Student s;
    	Student *p;
    	p = &s;
    	p->name = "tom"; //错误,name是private数据,不可以通过对象访问
    	p->display();    // 123 yj 
    	(*p).display();  // 123 yj 
    }
    
  • 通过对象的引用访问对象的成员

    class Student {
    public:
    	Student() { sno = 123; name = "yj"; }
    	void display() {
    		cout << sno << "  " << name;
    	}
    private:
    	int sno;
    	string name;
    };
    int main() {
    	Student s;
    	Student &ss=s;
    	ss.display();// 123 yj 	
    }
    



类的封装与信息隐蔽:

共用接口与私有实现的分离

  • 公有成员函数是用户使用类的公用接口,或者说是类的对外接口。
  • 一切与用户操作无关的部分都封装在机箱里,用户看不见,改不了,这就是接口与实现分离。
  • 类的公用接口与私有实现的分离,形成信息隐蔽。

类声明与成员函数定义的分离。

  • 若一个类只被一个程序使用,类的声明和成员函数定义可以直接写在程序的开头。
    若被多个程序使用,把类的声明放在指定的头文件里,用户使用时直接把有关头文件包含进来。

  • 类声明头文件是用户使用类库的共用接口。

  • 一个c++的程序由3部分组成:

    1. 类声明头文件(后缀.h)
    2. 类实现文件(后缀为.cpp):类成员函数的定义
    3. 类的使用文件(后缀.cpp):即主文件
  • 类库组成:

    1. 类声明的头文件
    2. 已经过编译的成员函数的定义,即目标文件。

    把类库装入自己的计算机系统中,并在程序中用#include指令将有关类声明的头文件包含到程序中。就可以使用这些类和其中的成员函数。



构造函数对类对象进行初始化

对象的初始化

类并不是一个实体,并不占存储空间,所以无法容纳数据。
若一个类中的数据成员都是公用的,则可以在定义定义对象时对其初始化。

class Student{
public:
    int sno;
    string name;
};
Student stu={123,"yj"};

若类中有protected和private数据成员,就不可以采用这种方式。

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

  • 构造函数是特殊的成员函数,他不可以被调用,而是在建立对象时由编译系统自动调用。
  • 构造函数的作用是在对象被创建时使用特定的值构造对象,或者说将对象初始化为一个特定的状态。
  • 如果程序中未声明,则系统自动产生出一个隐含的参数列表为空的构造函数
  • 允许为内联函数、重载函数、带默认形参值的函数
  • 无返回值,也无类型,函数名和类名一致。
  • 可以使用一个类对象初始化另一个类对象。

带参数的构造函数

  • 构造函数一般形式:
    构造函数名 (类型1 形参1,类型2 形参2·····)
  • 定义对象时:
    类名 对象名(实参1,实参2······)

构造函数的实现:
在这里插入图片描述
建立对象时构造函数作用:
在这里插入图片描述

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

在函数首部实现,不在函数体内。

  • 一般形式:
    类名 :: 构造函数名([参数表])[:成员初始化表]{
    [构造函数体]
    }

若函数参数中含有数组,要在函数体内进行初始化,不可以在首部。

```
class Student{
  public:
      Student(int sno1, char name[],char sex1) : sno(sno1),sex(sex1){//初始化器
           strcpy(name,name1);
      }
  private:
      int sno;
      char name[20];
      char sex;      
}
```

构造函数重载

一个类中可以有多个构造函数。
参数个数不同或者参数类型不同。

class Box{
public:
	Box() { height = 10; width = 2; length = 5; }
	Box(int h, int w , int l) :height(h),width(w),length(l){}
	Box(int h, int w) { height = h; width = w; length = 100; }
	int volume();
private:
	int height;
	int width;
	int length;
};

带默认值的构造函数

若用户不指定,则使用默认值。
一个类只能有一个带默认值的构造函数。
在声明时指定默认值,而非只在定义时指定默认值。
定义了全部是默认参数的构造函数后,不能再定义重载构造函数。

#include <iostream>
using namespace std;
class Box{
public:
	Box(int h = 10, int w = 10, int l = 10) :height(h),width(w),length(l){}
	int volume();
private:
	int height;
	int width;
	int length;
};
int Box::volume() {
	return height * width*length;
}
void main() {
	Box b;
	cout<<b.volume();  //1000 --> 10*10*10
	Box b1(1);
	cout << b1.volume();//100 -->1*10*10
	Box b2(1, 2);
	cout << b2.volume();//20 -->1*2*10
	Box b3(2, 2, 2);  
	cout << b3.volume();//8  -->2*2*2 
}

在这里插入图片描述
Box b;系统不知道调用第1个还是第三个构造
Box b1(12,10);系统不知道调用第2个还是第3个构造。

  • 拷贝构造函数
    1. 拷贝构造函数是一种特殊的构造函数,其形参为本类的对象引用。
    2. 形式:
      class 类名 {
        public:
          类名 (形参;//构造函数
          类名 (类名 &对象名);//拷贝构造函数
            …
      };
      类名::类(类名 &对象名){//拷贝构造的实现
          函数体
      }

举例:
在这里插入图片描述

  1. 调用拷贝构造函数
    1. 当用类的一个对象去初始化该类的另一个对象时系统自动调用拷贝构造函数实现复制。当用类的一个对象去初始化该类的另一个对象时系统自动调用拷贝构造函数实现复制。

      在这里插入图片描述

    2. 若函数的形参为类对象,调用函数时,实参赋值给形参,系统自动调用拷贝构造函数。

    3. 当函数的返回值是类对象时,系统自动调用拷贝
      构造函数。

    在这里插入图片描述



析构函数

析构函数是与构造函数作用相反的函数。

  1. 完成对象被删除前的一些清理工作,并不是删除对象。
  2. 在对象的生存期结束的时刻系统自动调用它,然后再释放此对象所属的空间。
  3. 如果程序中未声明析构函数,编译器将自动产生一个隐含的析构函数。
  4. 不难被重载

在这里插入图片描述

调用构造函数和析构函数的顺序

先构造的后析构,后构造的先析构。

  1. 若定义全局对象,它的构造在本文件所有函数执行之前调用。main函数执行完毕或者调用exit函数时调用析构函数。
  2. 若为局部自定变量,则在建立对象时调用构造;若对象所在的函数被多次调用,则每次建立时都需要调用构造函数,函数调用结束,对象释放时先调用析构函数。
  3. 若在函数中定义静态局部变量,则在程序第一次调用此函数定义对象时调用构造函数一次,调用结束并不释放对象,也不调用析构函数。main函数执行完毕或者调用exit函数时调用析构函数。



对象数组

对象数组的每一个元素都是同类对象。

#include <iostream>
using namespace std;
class Box{
public:
	Box(int h = 10, int w = 10, int l = 10) :height(h),width(w),length(l){}
	int volume();
private:
	int height;
	int width;
	int length;
};
int Box::volume() {
	return height * width*length;
}
void main() {
	Box b[3] = {
		  Box(1),
		  Box(1,4),
		  Box(1,4,5)
	};
	for (int i = 0; i < 3; i++) {
		cout << b[i].volume()<<"  ";   //100  40  20
	}
}



对象指针

指向对象的指针

对象空间的起始地址就是对象的指针。
一般形式:
类名 * 对象数组名;

#include <iostream>
using namespace std;
class Box{
public:
	Box(int h = 10, int w = 10, int l = 10) :height(h),width(w),length(l){}
	void volume();
private:
	int height;
	int width;
	int length;
};
void Box::volume() {
	cout<<height * width*length;
}
void main() {
	Box b;
	Box *p;
	p = &b;
	p->volume();  //p所指对象中的vilume成员函数 1000
	(*p).volume();//p所指对象中的vilume成员函数  1000
}

指向对象成员的指针

存放对象起始地址的指针变量就是指向对象的指针变量。
存放对象成员地址的指针变量就是指向对象成员的指针变量。

  • 指向对象数据成员的指针
    1. 一般形式:
      数据类型名 * 指针变量名;

      class Box{
      public:
      	Box(int h = 10, int w = 10, int l = 10) :height(h),width(w),length(l){}
      	void volume();
      	int c;
      private:
      	int height;
      	int width;
      	int length;
      };
      void Box::volume() {
      	cout<<height * width*length;
      }
      void main() {
      	Box b;
      	int *p;
      	p = &b.c;
      	*p = 4;
      	cout << *p; //输出  4
      }
      
    2. 指向对象成员函数的指针

      1. 指向普通函数的指针变量:类型名 (* 指针变量名 )(参数列表);

        void (*p) ();
        p=fun;
        (*p)(); //调用fun函数

      2. 指向公用成员函数的指针:
        数据类型名 (类名::*指针变量名)(参数类型);
        使指针变量指向一个公有成员函数的形式:
        指针变量名 = &类名 :: 成员函数名;

        #include <iostream>
        using namespace std;
        class Box{
        public:
        	Box(int h = 10, int w = 10, int l = 10) :height(h),width(w),length(l){}
        	void volume();
        	int show(int);
        	int c=10;
        private:
        	int height;
        	int width;
        	int length;
        };
        void Box::volume() {
        	cout<<height * width*length;
        }
        int Box::show(int cc) {
        	return c + cc;
        }
        void main() {
        	Box b;
        	void (Box::*p)();
        	p = &Box::volume;
        	(b.*p)();//1000;
        	int c = 6;
        	int (Box::*pc)(int );
        	pc = &Box::show;
        	cout<<(b.*pc)(c); //16
        
        }
        

this指针

  • 每一个成员函数中都含有一个特殊的指针,称为this指针。
  • 它是指向本类对象的指针,它的值是当前被调用的成员函数所在的对象的起始地址。(是对象的入口地址,而不是成员函数的地址)
  • this指针是隐式使用的,他作为参数被传递给成员函数。
            1. void Box::volume() {
			 	cout<<height * width*length;                   //隐式使用this指针
			 }
			当c++调用这个成员函数时,c++把它处理成:
	    	2. void Box::volume(Box *this) {
				cout<<this->height *this-> width*this->length;  //显式使用this指针
			}
			调用时 b.volume()被处理成b.volume(& a);
			*this就等价于当前对象
		    3. void Box::volume(Box *this) {
				cout<<(*this). height * (*this). width * (*this).length;  //显式使用this指针
			}
			以上三种等价



const

常对象

在定义对象时加关键字const,指定对象是常对象。
常对象必须要有初值,以后数据不在改变。
在存在的生命期间,数据成员的值不会被改变。

  • 一般形式:
           类名 const 对象名 [(实参列表)];
    也可以把const写在最左边:
         const 类名 对象名[(实参表)];
  1. 若一个对象被声明为常对象,它只能调用常成员函数,而不能调用普通成员函数(除了系统自动调用的隐式的构造函数和析构函数)
  2. 常成员函数可以访问常对象中的数据成员,但是任然不允许修改常对象中数据成员的值。若要修改,需要把该成员数据声明为mutable。

常对象成员

可以把对象的成员声明为const,常成员数据和常成员函数。

  • 常数据成员
    形式:const 类型名 数据成员名;
    1. 只能通过构造函数的参数初始化表对常数据成员进行初始化,任何其他函数都不能对常数据成员赋值。
    2. 常对象的数据成员都是常数据成员,因此定义常对象时,构造函数只能用初始化参数表对常数据成员进行初始化。
  • 常成员函数
    只能引用本类中的数据成员,而不能修改它们的值。
    形式:类型名 函数名 (参数表)const;
非const的普通成员函数 const的成员函数
可以引用非const的普通数据成员,可以改值 可以引用非const的普通数据成员,不能改值
可以引用const数据成员 , 不能改值 可以引用const数据成员 , 不能改值
不允许引用const对象 可以引用const对象,不改变值
  1. 若一个类中,有些数据成员的值可以改变,有些数据成员的值不允许改变,则可以将一些数据声明为const,保证其值不变。可以用非const的成员函数引用这些const数据成员的值,并修改非const数据成员的值。

  2. 要求所有数据成员值不可以改变,可以将他们全部声明为const,获将对象声明为const对象,通过const成员函数引用数据。

  3. 不要误认为常对象的成员函数都是常成员函数,常对象只保证数据成员是常数据成员。

指向对象的常指针

指针变量声明为const型,指针变量始终保持为初值,不可改变,即其指向不变。

  • 一般形式:
    类名 * const 指针变量名
  • 若想将一个指针变量固定的与一个对象相联系,就可以把它声明为const型指针。

指向常对象的指针变量

  • 一般形式:
    const 类型名 * 指针变量名;
  • 指向常变量的指针变量;
    1. 若一个变量已被声明为常变量,只能用指向常变量的指针指向它。

          const char name[]="yjhds";
          char *p;
          p=name; //正确,若一个变量已被声明为常变量,只能用指向常变量的指针指向它
          char *p2=name;//错误,name是一个常变量
      
    2. 指向常变量的指针变量还可以指向不带const的变量。但是此时不能通过该指针修改非const变量的值。

          const char name[] = "yjhds";
      	const char *p;
      	p = name; //正确,若一个变量已被声明为常变量,只能用指向常变量的指针指向它
      	char sex = '1';
      	p = &sex;// 可以改变指向
      	*p = '2';// 企图修改sex对象的值,但是不能修过,具有常变量的特征
      
    3. 若一个函数的形参是指向普通变量的指针变量,实参只能用指向普通变量的指针,不可以用指向const变量的指针,可修改形参指针变量所指向变量的值。

    4. 指向常变量的指针可以指向const和非const型变量。
      指向非const型变量的指针变量只能指向非const型变量。

    • 指向常对象的指针变量
      1. 若一个对象已被声明为常对象,只能用指向常对象的指针指向它,而不能用一般的(非const)指针变量指向它。

          const Box b;
          const Box *p = &b;
          Box *p1 = &b; //C2440“初始化” : 无法从“const Box *”转换为“Box *”
        
      2. 指向常对象的指针变量还可以指向不带const的对象。但是此时不能通过该指针修改非const对象的值。

            const Box b;
            Box b2;
        	const Box *p = &b;
        	p = &b2;  // 可以修改指向为非const对象
        	(*p).c=2;//错误	C3490,由于正在通过常量对象访问“c”,因此无法对其进行修改
        
      3. 指向常对象的指针最常用于函数形参,为了保护形参指针所指对象,使他在函数运行过程中不被改变

      4. 指向常对象的指针可以指向const和非const型对象。
        指向非const型对象的指针变量只能指向非const型对象。

对象的常引用

实际上引用是一个指针常量,用来存放变量的地址。
把引用声明为const,称为常引用。

#include <iostream>
using namespace std;
class Box{
public:
	Box(int h = 10, int w = 10, int l = 10) :height(h),width(w),length(l){}
	void volume();
	int show(int);
	int c=10;
private:
	int height;
	int width;
	int length;
};
void Box::volume() {
	cout<<height * width*length;
}
void fun(Box &t){
	t.c = 18;
}
void main() {
    Box b;
	fun(b);
	cout << b.c;  //15
}

若不想在fun函数中修改实参b的值,可以把fun的形参用const修饰

void fun(const Box &b);//不能再b所代表的实参值。



对象的动态建立和访问

  • 用new运算符动态地分配内存后,将返回一个指向新对象的指针的值,即所分配的内存空间的起始地址。用户可以获得这个地址,并用这个地址来访问对象。

  • C++还允许在执行new时,对新建的对象进行初始化。

  • 用new建立的动态对象一般是不用对象名的,而是通过指针进行访问,它主要应用于动态的数据结构,如链表等。访问链表中的结点,并不需要通过对象名,而是在上一个结点中存放下一个节点的地址,从而用上一个结点找到下一个结点,构成连接关系。

  • 在不再需要使用new建立的对象时,,可以用delete运算符予以释放。在执行delete运算符的时候,在释放内存空间之前,会自动调用析构函数。

#include <iostream>
using namespace std;
class Box{
public:

	Box(int h = 10, int w = 10, int l = 10) :height(h),width(w),length(l){
	    cout << "*********调用构造函数********\n";
	}
	~Box(){
		cout << "*********调用析构函数*********\n";
	}
	void volume();
	int c=10;
private:
	int height;
	int width;
	int length;
};
void Box::volume() {
	cout<<height * width*length;
}

void main() {
	Box *p = new Box;
	cout << p->c << endl;
	(*p).volume();
	cout << endl;
	delete p;
}

结果:
在这里插入图片描述



对象的赋值

  • 一般形式:
    对象名1=对象名2;

    #include <iostream>
    using namespace std;
    class Box{
    public:
    
    	Box(int h = 10, int w = 10, int l = 10) :height(h),width(w),length(l){
    	}
    	void volume();
    	int c=10;
    private:
    	int height;
    	int width;
    	int length;
    };
    void Box::volume() {
    	cout<<height * width*length;
    }
    
    void main() {
    	Box b(12, 10, 1), b1;
    	b1 = b;      //对象b的成员一一复制给另一个对象对应的成员。
    	b.volume();  //120
    	b1.volume(); //120
    }
    
  • 说明:

    1. 在使用对象赋值语句进行对象赋值时,两个对象的类型必须相同,如对象的类型不同,编译时将出错。
    2. 两个对象之间的赋值,仅仅使这些对象中数据成员相同,而两个对象仍是分离的。
    3. 对象赋值是通过默认赋值运算符函数实现的
    4. 将一个对象的值赋给另一个对象时,多数情况下都是成功的,但当类中存在指针时,可能会产生错误。



拷贝构造函数

拷贝构造函数

  • 拷贝构造函数是一种特殊的构造函数,其形参为本类的对象引用。

  • 作用:在建立一个新对象时,使用一个已经存在的对象去初始化这个新对象。

    例如: Point p2(p1);
    其作用是:在建立新对象p2时,用已经存在的对象p1去初始化对象p2,在这个过程中就要调用拷贝构造函数。

  • 形式:
    class 类名 {
      public:
        类名 (形参);//构造函数
        类名 (类名 &对象名);//拷贝构造函数
          …
    };
    类名::类(类名 &对象名){//拷贝构造的实现
        函数体
    }

  • 拷贝构造函数具有以下特点:
    (1)因为该函数也是一种构造函数,所以其函数名与类名相同,并且该函数也没有返回值类型。
    (2)该函数只有一个参数,并且是同类对象的引用.
    (3)每一个类都必须有一个拷贝构造函数。程序员可以自定义拷贝构造函数,用于按照需要初始化新对象。如果程序员没有定义类的拷贝构造函数,系统就会自动生成产生一个默认的拷贝构造函数,用于复制出数据成员值完全相同的新对象。

    举例:
    在这里插入图片描述

  • 调用拷贝构造函数时机

    1. 当用类的一个对象去初始化该类的另一个对象时系统自动调用拷贝构造函数实现复制。当用类的一个对象去初始化该类的另一个对象时系统自动调用拷贝构造函数实现复制。

      在这里插入图片描述

    2. 若函数的形参为类对象,调用函数时,实参赋值给形参,系统自动调用拷贝构造函数。

      void fun(Box b){ //形参是类Box的对象b
         b.c=10;; 
      }
      void main(){
        Box b;
        fun(b); //调用函数fun时,实参b是类Box的对象 ;将调用拷贝构造函数,如果类Box中有自定义的拷贝构造函数时,就调用拷贝的构造函数,否则就调用系统自动生成的默认拷贝构造函数,初始化形参对象b
      }
      
      
    3. 当函数的返回值是类对象时,系统自动调用拷贝构造函数。

      	例如: Box fun() {//函数fun2()的返回值类型是Box类类型 
              Box b1; //定义类Box的对象b1 
      	    return b1; // 函数的返回值是类的对象 
      	} 
      	void main(){
      	   Box b; //定义类的对象b 
      	   b=fun(); //函数执行完成,返回调用者时,调用拷贝构造函数(赋值法) 
      	} 
      

      在这里插入图片描述

隐含的拷贝构造函数

  1. 如果程序员没有为类声明拷贝初始化构造函数,则编译器自己生成一个隐含的拷贝构造函数。
  2. 这个构造函数执行的功能是:用作为初始值的对象的每个数据成员的值,初始化将要建立的对象的对应数据成员。



对象的复制

参考拷贝构造函数的讲解。

用一个已有的对象快速的复制出多个完全相同的对象。类似于克隆。

  • 一般形式:
    类名 对象名2(对象名1);
    用对象1复制出对象2
  • 创建对象时调用一个特殊的构造函数—拷贝构造函数(复制构造)


静态成员

静态数据成员

  • 静态数据成员初始化的格式如下:
      数据类型 类名::静态数据成员=值;
      1. 静态成员初始化与一般数据成员初始化不同,初始化只能在类体外进行,前面不加static。
      2. 初始化时不加该成员的访问权限控制符private,public等。
      3.初始化时使用作用域运算符来标明它所属类,因此静态数据成员是类的成员,而不是对象的成员。
      4. 若未对静态数据成员赋初始值,编译系统自动赋初值。
  • 在类中,静态成员可以实现多个对象之间的数据共享,并且使用静态数据成员还不会破坏隐藏的原则,即保证了安全性。
  • 静态成员是类的所有对象中共享的成员,而不是某个对象的成员。为对象分配的空间里不包含静态数据成员所占空间。静态数据成员在所有对象之外单独开辟空间,只存储一处,供所有对象共用。so使用静态数据成员可以节省内存。
  • 静态数据成员的值对每个对象都是一样,但它的值是可以更新的。
  • 静态数据成员的使用方法和注意事项如下:
    1. 静态数据成员在定义或说明时前面加关键字static。
    2. 静态数据成员是静态存储的,它是静态生存期,必须对它进行初始化。
    3. 引用静态数据成员时,采用如下格式:
         类名::静态成员名 //静态变量的使用方式
         或者: 对象名.静态成员名
#include <iostream>
using namespace std;
class Box{
public:

	Box(int h ,int w):height(h),width(w){}
	Box(Box &b) {
		height = b.height;
		width = b.width;
		length = b.length;
	}
	void volume();
	static int length;
private:
	int height;
	int width;
	static int c;
};
void Box::volume() {
	cout<<"体积为:"<<height<<"*"<<width<<"*"<<length<<"="<<height * width*length<<endl;
}
int Box::length = 5;
int Box::c = 6;
void main() {
	Box b(12, 10),b1(1,2);  

	//通过对象b改变length的值,发现b1对象共享length的值
	b.length = 10;
	cout << " b.length:" << b.length << "  b1.length:" << b1.length << "   Box::length:" << Box::length << endl;
	b.volume();
	b1.volume();
	cout << endl;
	
	//通过对象b1改变length的值,发现b对象共享length的值
	b1.length = 8;
	cout << " b.length:" << b.length << "  b1.length:" << b1.length << "   Box::length:" << Box::length << endl;
	b.volume();
	b1.volume();
	cout << endl;

	//通过类名引用改变length的值,发现b,b1对象共享length的值
	Box::length = 4;
	cout << " b.length:" << b.length << "  b1.length:" << b1.length << "   Box::length:" << Box::length<<endl;
	b.volume();
	b1.volume();

   // Box::c = 0;//错误C2248:“Box::c”: 无法访问 private 成员(在“Box”类中声明)		
}

结果:
在这里插入图片描述

静态成员函数

  • 静态成员函数和静态数据成员一样,它们都属于类的静态成员,它们都不是对象成员。
  • 静态成员函数没有this指针。
  • 在静态成员函数的实现中不能直接引用类中说明的非静态成员,可以引用类中说明的静态成员。如果静态成员函数中要引用非静态成员时,可通过对象来引用。
  • 调用静态成员函数使用如下格式:
    类名::静态成员函数名(<参数表>);
    1. 类外代码可以使用类名和作用域操作符来调用静态成员函数。
    2. 静态成员函数只能引用属于该类的静态数据成员或静态成员函数。
#include <iostream>
using namespace std;
class Box{
public:

	Box(int h ,int w):height(h),width(w){}
	Box(Box &b) {
		height = b.height;
		width = b.width;
		length = b.length;
	}
	void volume();
	static void show(int,int);
	static int length;
private:
	int height;
	int width;
	static int c;
};
void Box::volume() {
	cout<<"体积为:"<<height<<"*"<<width<<"*"<<length<<"="<<height * width*length<<endl;
}
void Box::show(int c1,int l) {
	c = c1;
	length = l;
	//cout << width;//错误C2597:对非静态成员“Box::width”的非法引用
	cout << "静态私有成员c:" << c << "   静态公有成员length:" <<length<< endl;
}
int Box::length = 5;
int Box::c = 6;
void main() {
	Box b(12, 10),b1(1,2);   //调用普通构造函数 
	b.show(4, 10);
	cout << " b.length:" << b.length << "  b1.length:" << b1.length << "   Box::length:" << Box::length << endl;
	b.volume();	
	/*
	 静态私有成员c:4   静态公有成员length:10
     b.length:10  b1.length:10   Box::length:10
     体积为:12*10*10=1200
	 */
}

组合类

  1. 类中的成员数据是另一个类的对象。
  2. 可以在已有抽象的基础上实现更复杂的抽象。
    在这里插入图片描述
  • 类组合的构造函数设计
    1. 原则:不仅要负责对本类中的基本类型成员数据赋初值,也要对对象成员初始化。
    2. 定义形式:
      类名::类名(对象成员所需的形参,本类成员形参)
      :对象1(参数),对象2(参数), …
      { 本类初始化 }
  • 类组合的构造函数调用
    1. 构造函数调用顺序:先调用内嵌对象的构造函数(按内嵌时的声明顺序,先声明者先构造)。然后调用本类的构造函数。(析构函数的调用顺序相反)
    2. 初始化列表中未出现的内嵌对象,用默认构造函数(即无形参的)初始化
    3. 系统自动生成的隐含的默认构造函数中,内嵌对象全部用默认构造函数初始化
  • 前向引用声明
    1. 类应该先定义,后使用

    2. 如果需要在某个类的定义之前,引用该类,则应进行前向引用声明。

    3. 前向引用声明只为程序引入一个标识符,但具体定义在其他地方。

      class B; //前向引用声明
      class A {
      public:
      void f(B b);
      };
      class B {
      public:
      void g(A a);
      };

    4. 使用前向引用声明虽然可以解决一些问题,但它并不是万能的。需要注意的是,尽管使用了前向引用声明,但是在提供一个完整的类定义之前,不能定义该类的对象,也不能在内联成员函数中使用该类的对象。

      class Fred; //前向引用声明
      class Barney {
      Fred x; //错误:类Fred的定义尚不完善
      };
      class Fred {
      Barney y;
      };

      class Fred; //前向引用声明
      class Barney {
      public:
      ……
      void method() {
      x.yabbaDabbaDo(); //错误Fred类的对象在定义之前使用
      }
      private:
      Fred &x;//正确,经过前向引用声明,可以声明Fred类对象引用
      };
      class Fred {
      public:
      void yabbaDabbaDo();
      private:
      Barney &y;
      };

猜你喜欢

转载自blog.csdn.net/qq_41498261/article/details/82907943
今日推荐