面向对象

8 面向对象

public成员是类的接口

8.2.2 类的定义
  1. 数据成员的类型符前不可使用auto、extern和register[变量存储位置和作用域,类和结构体中的变量是成员变量,其存储位置和作用域由定义对象的函数决定,不由对象本身决定],也不可在类定义时对数据成员初始化
  2. 类定义中提供的成员函数时函数的原型声明。

8.3 C++类的实现

一种是在类定义时完成成员函数的定义,二是在类定义的外部定义其成员函数

8.5 对象的作用域、可见域和生存周期

8.5.3 构造函数支持重载

定义有参构造函数和无参构造函数

8.5.4 构造函数允许按参数默认方式调用

如果仅仅是参数个数不同,推荐使用参数默认方式

8.5.5 初始化表达式

由逗号分隔的数据成员组成,初值放在一对圆括号内。只要将成员初始化表达式放在构造函数的头体之间,并用冒号将其与函数头分隔开,可实现数据成员表达式元素的初始化。在构造函数体之前进行

初始化表中的初始化顺序是由成员在类中被声明的顺序决定的。

A(int a,int b):x(a),y(b)
{}
8.5.6 析构函数

默认析构函数只清除类的数据成员所占据的空间,但**对类的函数成员通过new和malloc 动态申请的内存无能为力,应在类的析构函数中通过delete或free进行释放,这样能有效避免对象撤销造成的内存泄露**。

8.6 复制构造函数

系统默认一个复制构造函数,它是一个inline或public 的成员函数,其函数原型

point::point(const point &);
8.6.2 默认复制构造函数

赋值操作后,指针指向同一内存,当一个对象析构后 ,会导致重复释放内存和野指针问题

8.6.3 显式定义复制构造函数

如果类中含有指针型的数据成员、需要使用动态内存,程序员最好显式定义自己的复制构造函数,避免各种可能出现的内存错误。

class computer
{
	char *brand;
	float price;
	computer(const computer &cp)
	{
		brand=new char[strlen(cp.brand)+1];/*重新为brand开辟cp.brand大小的动态内存*/
		strcpy(brand,cp.brand);/*字符串复制*/
		price=cp.price;
	}
};

8.7 特殊数据成员1

成员初始化表达式
const数据成员
引用成员
类对象成员
基类构造函数
8.7.1 const 数据成员

只能通过成员初始化表达式进行初始化。编译器提供的默认构造函数无法完成对const成员的初始化,而**缺省的复制构造函数可以完成const成员的初始化**

#include<iostream>
using namespace std;
class point
{
	const int xPos;
	const int yPos;
public:
	point(const int x=0,const int y=0):xPos(x),yPos(y)
	{
		cout<<"调用构造函数"<<endl;
	}
	void print()
	{
		cout<<"y="<<yPos<<"x="<<xPos<<endl;
	}
};
int main()
{
	point p1(3,4);
	p1.print();
	point p2(p1);//缺省的复制构造函数可以完成const成员的初始化
	p2.print();
	return 0;
}
8.7.2 引用成员

只能通过成员初始化表达式来进行初始化,不可使用默认构造函数,可使用默认复制构造函数。当类中含有引用类型的数据成员(尤其是引用本对象的成员时),不要使用默认的复制构造函数,应当显式地给出复制构造函数,避免程序出现无法预料的错误。

8.7.3 类对象成员

类对象的私有数据成员xPos和yPos都是private成员,只能在pt1、pt2初始化表达式中对类进行初始化

#include<iostream>
using namespace std;
class point
{
	const int xPos;
	const int yPos;
public:
	point(const int x=0,const int y=0):xPos(x),yPos(y)
	{
		cout<<"调用构造函数"<<endl;
	}
	void print()
	{
		cout<<"y="<<yPos<<" x="<<xPos;
	}
	point(const point&p):xPos(p.xPos),yPos(p.yPos)
	{
		cout<<"调用复制构造函数"<<endl;
	}
	~point()
	{
		cout<<"调用析构函数"<<endl;
	}	
};
class line
{
	point pt1;
	point pt2;
public:
	line(const int x1,const int y1,const int x2,const int y2):pt1(x1,y1),pt2(x2,y2)
	{
		cout<<"线的构造函数"<<endl;
	}
	line(const line &l):pt1(l.pt1),pt2(l.pt2)
	{
		cout<<"线的复制构造函数"<<endl;
	}
	void drow()
	{
		pt1.print();
		cout<<" to ";
		pt2.print();
		cout<<endl;
	}
	~line()
	{
		cout<<"线的析构函数"<<endl;
	}
};
int main()
{
	line l1(1,2,3,4);
	l1.drow();
	line l2(l1);
	l2.drow();
	return 0;
}

在这里插入图片描述

8.7.5 static 数据成员

使用static修饰数据成员,成员在编译时就被创建并初始化(在定义性声明时被创建的)(与之相比,对象是在运行时才被创建的)

静态数据成员,应在类声明之外使用单独的定义性声明语句完成其初始化,该语句不能再使用static

初始化一定放在cpp文件中,不能放在h 文件中,因为h 文件会出现在多个cpp 文件中,出现初始化的复本,引发错误。

静态成员使用const修饰(const与static没有前后之分),而且是整型、浮点型、布尔型或枚举型,但不能是类对象、数组、引用和指针,C++允许该成员在类定义中初始化,这样,便不能在外部再次对该成员进行定义性声明,但对该成员的引用性声明是允许的。

8.8 特殊函数成员

接口:public

内部实现:private

8.8.1 静态成员函数

如果需要在静态函数成员内访问类的非静态成员,需要将对象引用或指向对象的指针作为参数。既可定义在类内,也可定义在类外,定义在类外不使用static

不能用const修饰的原因:

一个静态成员函数访问的值是其参数、静态数据和全局变量,这些数据都不是对象状态的一部分。对成员函数用const修饰是为了表明函数并不会修改访问对象的数据成员。既然一个静态成员函数根本不访问非静态数据成员,那么没必要使用const

class computer
{
	char *brand;
	float price;
	computer(const computer &cp)
	{
		brand=new char[strlen(cp.brand)+1];/*重新为brand开辟cp.brand大小的动态内存*/
		strcpy(brand,cp.brand);/*字符串复制*/
		price=cp.price;
	}
    /*访问类的非静态成员,需要将对象引用或指向对象的指针作为参数*/
	static void print(computer &p)
	{
		cout<<p.price<<endl;
	}
};
8.8.2 const成员函数

把const 关键字放在函数的参数表和函数体之间,表示该成员函数只能读取类的数据成员,不能修改类成员数据,const成员函数不能调用另一个非const成员函数或者改变该类的数据成员

任何不修改成员数据的函数都应该声明为const函数,这有助于提高函数的可读性和可靠性

class computer
{
	char *brand;
	float price;
	computer(const computer &cp)
	{
		brand=new char[strlen(cp.brand)+1];/*重新为brand开辟cp.brand大小的动态内存*/
		//strcpy(brand,cp.brand);/*字符串复制*/
		price=cp.price;
	}
	 void print(computer &p) const
	{
		cout<<p.price<<endl;
	}
};

8.9 对象的组织

可以const对象来创建指向对象的指针和创建对象数组,还可以使用new和delete等创建动态对象。

8.9.1 const对象

能作用于const对象的成员函数除了构造函数和析构函数外,只有const成员函数,因为const对象只能被创建、撤销以及只读访问,不许改写。

8.9.3 对象的大小

对象的大小一般是类中所有非static成员的大小之和

  1. C++将类中的引用成员当作“指针”来维护,占4个内存字节
  2. 类中有虚函数时(虚析构函数除外),还会分配一个指针用来指向虚函数表,因此,加4个字节
8.9.4 this指针

编译器不会向静态成员函数传递this指针,这既是“当静态函数访问类的非静态成员时,需要将对象的引用或指向对象的指针作为参数”的原因

this指针的作用

  1. 显式指明类中数据成员,尤其是和形参以及全局变量相区别
  2. 返回本对象的指针或引用

8.10 可变参数

9.1 类的作用域

访问类的成员数据x,有两种途径:一是使用“类名::数据成员名”(也可访问嵌套类),二是使用this指针(不可用this指针访问嵌套类)

使用“::x”可以在程序的任何地方访问全局变量。

9.2 类定义的作用域与可见域

2.类作用域

一个类可以定义在另一个类中,这是所谓的嵌套类。若类A定义在类B中,A的访问权限为public 则A的作用域可以认为和B相同,不同之处是在于必须使B::A的形式访问A的类名。当然,若A的访问权限是private,则只能在类内使用类名来创建该类的对象,无法在类外创建A类的对象。嵌套类的成员函数既可以定义为inline也可定义在类外,合理使用作用域限定符"::".

9.3 对象的生存期、作用域和可见域

9.3.1 先定义后实例化

不创建对象,仅仅声明一个指向类型B对象的指针

class B;/*引用性声明*/
B* pB=NULL;

9.4 友元

可以定义一个函数、类为类的友元,则友元可以访问类的私有成员

9.4.1 友元的非成员函数

在某个类的定义中用friend声明一个外部函数(或者其他类的函数成员,可以是public也可private)后,这个外部函数称为类的友元。

特点:

  1. 类内只需对函数进行声明,声明的位置没有要求
  2. 函数定义要放在类外,具体定义位置没有要求
  3. 友元函数不是类的成员函数,在实现时和普通函数一样,在实现时不用“::”指示属于那个类
  4. 一个函数可以同时作为多个类的友元函数
9.4.2 友元的成员函数

当A类的成员函数作为B类的友元函数时,必须先定义A类,而仅仅是声明它,对其实现没有具体要求。

9.4.3 友元函数的重载

使得一组重载函数全部成为类的友元,必须一一声明,否则只有匹配的那个函数会成为类的友元,编译器仍将其他函数当作普通函数来处理。

9.4.4 友元类

类A作为类B的友元,类A 先声明,B类定义后A类再定义。

  1. 友元关系是单向的
  2. 友元关系不具有传递性
  3. 友元关系不被继承

9.5 运算符重载

operator是C++的一个关键字,它和运算符一起使用,表示一个运算符重载函数,在理解时可将operator和运算符视为类的一个成员函数1

对双目运算符来说,编译器将左边对象解释为调用对象,将右边对象解释为传递给运算符的参数。cx1+cx2等价于cx1.operator+(cx2)

complex& complex::operator ++()/*解释为对象.operator++(),其他前置单目运算符与此类似*/
{
    cout<<"前置++"<<endl;
    real+=1;
    imag+=1;
    return (*this);/*返回自身引用(*this)使得++对象 可以作为左值*/
}
complex complex::operator ++(int)/*解释为对象.operator(0),其他后置单目运算符与此类似*/
{
    cout<<"后置++"<<endl;
    complex ctemp=*this;
    ++(*this);
    return ctemp;/*以传值形式返回,不能成为左值*/
}

操作符定义为友元函数的形式可让程序更容易实现类型的自动转换,使两个操作符都被当作函数的参数。

9.6 运算符重载

9.6.1 赋值运算符
class obj1=obj2;/*调用类的复制构造函数,完成obj1的创建并初始化*/
class obj1;/*先调用obj1的无参构造函数(或所有参数都有默认值的构造函数)完成obj1的创建*/
obj1=obj2;/*调用赋值运算符将obj2所有的成员的值复制到obj1*/
/*问题在于当class中包含指针指向动态内存时,会导致两个对象的指针指向同一块内存,导致其中一个对象的指针变为野指针*/
/*解决方法:将复制构造函数的特殊操作等同定义在赋值运算符重载中*/
class computer
{
    private:
    char *brand;
    float price;
    /*复制构造函数*/
    computer(const computer&p)
    {
		price=p.price;
		brand=new char[strlen(p.brand)+1];
		if(brand!=NULL)
		{
			strcpy(brand,p.brand);
		}
		cout<<"复制构造函数被调用"<<endl;
    }
	computer& operator=(const computer &p)
	{
        /*
        忽视的几点问题
        1.判断是否为自赋值。自己给自己赋值没有意义,如果不加判断就为指针重新申请内存,原来所指向的动态内存就泄露了
        2.释放brand所指向的内存,delete一个NULL指针是不会出现问题的。为了有效防错,delete后就立即将brand置为NULL
       */
		if(this==&p)/*如果时自赋值返回当前对象*/
		{
			return (*this);
		}
		price=p.price;
        /*防止内存泄露,先释放brand(不是p.brand)指向的内存*/
		delete [] brand;
		brand=new char[strlen(p.brand)+1];
		if(brand!=NULL)
		{
			strcpy(brand,p.brand);
		}
		return (*this);/*返回当前对象的引用,为的是实现链式赋值*/
	}
}
9.6.2 函数调用运算符

函数调用运算符同样只能重载为成员函数形式

function(arg1,arg2,...)
function.operator()(arg1,arg2,...)/*其作用是将函数调用运算符()作用在对象function上,不过其参数并没有个数限制*/

一个类如果重载了函数调用符,可以将对象作为一个函数使用,这样的类的对象又称为函数对象,函数也是一种对象

#include<iostream>
using namespace std;
/*一个类如果重载了函数调用符,可以将对象作为一个函数使用,这样的类的对象又称为函数对象,函数也是一种对象*/
class Demo
{
public:
	double operator()(double x,double y);
	double operator()(double x,double y,double z);
};
 double Demo::operator()(double x,double y)
 {
	 cout<<"1:";
	 return x>y?x:y;
 }
double Demo::operator()(double x,double y,double z)
{
	cout<<"2:";
	return (x+y)*z;
}
int main()
{
	Demo de;
	cout<<de(1.0,2.3)<<endl;
	cout<<de(1.0,2.0,2.5)<<endl;
	return 0;
}
9.6.3 下标运算符

返回引用类型很关键,这使得返回值可以作为左值

返回类型& operator[](参数类型);/*返回引用类型很关键,这使得返回值可以作为左值*/
#include<iostream>
using namespace std;
class Demo
{
	int len;
	char* pBuf;
public:
	Demo(int l)
	{
		len=l+1;
		pBuf =new char[len];
	}
	~Demo()
	{
		delete [] pBuf;
		cout<<"调用析构函数"<<endl;
	}
	int GetLen()
	{
		return len;
	}
	char& operator[](int i)
	{
		static char def='\0';
		if(i<len&&i>=0)
			return pBuf[i];
		else
		{
			cout<<"下标越界"<<endl;
			return def;
		}
	}
};
int main()
{
	Demo de(5);
	char *sz="hello";
	for(int i=0;i<strlen(sz);i++)
		de[i]=sz[i];
	for(int i=0;i<de.GetLen();i++)
		cout<<de[i];
	cout<<endl;
	return 0;
}
9.7.1 由其他类型向定义类的转换

能接受一个参数的构造函数称为转换函数,多出来的参数必须有默认值

point(anotherpoint ap);/*p1=p2隐式转换可用*/
explicit point (anotherpoint ap);/*p1=p2隐式转换不可用,只能p1=point(p2);才能完成*/
9.7.2 由自定义类向其他类型的转换

自定义类型是用户定义的强制类型转换函数,如何创建强制类型转换函数呢?需要在类中定义如下形式的转换函数

推荐使用显式转换

operator 目标类型名()
{
    return (目标类型的构造);
}
  1. 转换函数必须是成员函数,不能是友元函数形式
  2. 转换函数不能指定返回类型,但在函数体内必须用return语句以传值方式返回一个目标类型的变量
  3. 转换函数不能有参数
-9.8.2 完全匹配

完全匹配允许的不一致

实参形参
typetype&
type&type
type[]type*
返回类型 函数名(参数列表)返回类型 (*指针)(参数列表)
返回类型 (*指针)(参数列表)返回类型 函数名(参数列表)
typeconst type
type*const type*
typevolatile type
type*volatile type*

多个完全匹配的优选

  1. 指向非const变量或对象的指针或引用优先于const 的
    多个完全匹配的优选
  2. 指向非const变量或对象的指针或引用优先于const 的
  3. 非模板函数优先于模板函数

  1. https://blog.csdn.net/wucz122140729/article/details/98582930 ↩︎ ↩︎

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值