2020-08-13

类和对象

*定义类

class<类名>
{ public:
公有段数据成员和成员函数;
protected:
保护段数据成员和成员函数;
private:
私有段数据成员和成员函数;
};

class是定义类的关键字。
“类名”是用户自定义的标识符,用于表示类型的名字。

例如,一个日期类的说明如下:
class Date
{ public:
void SetDate(int y, int m,int d);
int IsLeapYear();
void PrintDate();
private:
int year, month, day;
};
Date 类有三个私有数据成员:year,month,day
三个成员函数SetDate用于获取对象的值,设置日期;IsLeapYear用于判断是否是闰年;PrintDate用于输出日期。
成员函数在类外定义使用作用域分符进行说明,函数头形式为: 返回类型 类名::函数名(参数表)
void Date::SetDate(int y, int m,int d)
{ year=y;
month=m;
day=d;
}

*访问对象成员
运算符“.” 和“->”用于访问对象成员

class Tclass
{public:
int x,y;
void print()
{cout<<x<<","<<y<<endl;};
};
int add(Tclass *ptf)
{return (ptf->x+ptf->y);}
int main ()
{Tclass test,*pt=&test;
pt->x=100;//对象指针访问数据成员
pt->y=200;
pt->print();//用对象指针调用函数成员
test.x=150;
test.y=450;
test.print();
cout<<“x+y=”<<add(&test)<<endl;//把对象地址传给指针参数
}

用this指针表示:void Tclass::print ()
{cout<<this ->x<<","<<this ->y<<endl;}

*构造函数

类名::类名(){}

在这里插入图片描述
在这里插入图片描述

#include <iostream>
using namespace std;
class Date 
{
	public:
	Date(); // 无参构造函数
	void setDate(int y,int m,int d);
	void showDate();
	private:
	int year, month, day; 
};
Date::Date() // 构造函数的实现
{ year=0; month=0; day=0; }
void Date::setDate(int y,int m,int d)
{ year=y; month=m; day=d; }
void Date::showDate()
{ cout<<year<<"."<<month<<"."<<day<<endl;}
int main()
{
	Date a_date; 
	a_date.showDate();
	a_date.setDate(2014,3,25);
	a_date.showDate();
	return 0;
}
*//带参构造函数
Date::Date(int y,int m,int d) 
{ year=y; month=m; day=d; }*

利用构造函数创建对象有以下两种方法:
(1) 利用构造函数直接创建对象.其一般形式为:
类名 对象名[(实参表)];
这里的“类名”与构造函数名相同,“实参表”是为构造函数
提供的实际参数。

利用构造函数直接创建对象
#include <iostream>
using namespace std;
class Date {
// 省略,同前
};
// 省略,同前
void main()
{
Date date1(1998,4,28);
cout<<"Date1 output1:"<<endl;
date1.showDate();
date1.SetDate(2002,11,14);
cout<<"Date1 output2:"<<endl;
date1.showDate(); }

(2) 利用构造函数创建对象时,通过指针和new运算符动态建立对象,用 delete运算符撤销对象。其一般语法形式为:
*类名 指针变量 = new 类名[(实参表)];
new 运算符动态分配内存后,返回指向新对象的指针,需要定
义一个指向该类对象的指针变量存放新对象指针,以便对其
访问。

例如:
Date *d1=new Date(1998,4,28); //创建对象(*date1)
(*d1). setDate(2020,3,10); d1->setDate(2020,3,10);
Box *pt;
pt=new Box; pt=new Box(12,13,14); //调用构造函数初始化
delete pt; //调用析构函数
Shandong Agricultural University 
void main()
{
Date *date1;
date1=new Date(1998,4,28);
// 以上两条语句可合写成:
// Date *date1=new Date(1998,4,28);
cout<<"Date1 output1:"<<endl;
date1->showDate();
date1->setDate(2002,11,14);
cout<<"Date1 output2:"<<endl;
date1->showDate();
delete date1; }

构造函数初始化成员有两种方法
A.使用构造函数的函数体进行初始化
B.使用构造函数的初始化列表进行初始化

A.使用构造函数的函数体进行初始化
class Date
{
int d, m, y;
public:
Date(int dd, int mm, int yy) {
d=dd;
m=mm;
y=yy; }
Date(int dd, int mm)
{
d=dd;
m=mm;
} }
B.使用构造函数的初始化列表进行初始化
格式:
funname(参数列表):初始化列表
{ 函数体,可以是空函数体 }
其中,初始化列表的形式:
成员名1(形参名1),成员名2(形参名2),成员名n(形参名n)
class Date
{
int d, m, y;
public:
Date(int dd, int mm, int yy) :d(dd),m(mm),y(yy)
{ }
Date(int dd, int mm) : d(dd),m(mm)
{ }
};

必须使用参数初始化列表对数据成员进行初始化的几种情况

  1. 数据成员为常量
  2. 数据成员为引用类型
  3. 数据成员为没有无参构造函数的类的对象

*复制构造函数

 生成一个对象的副本有2种途径:
途径1:建立一个新对象,将一个已有对象数据成员的值取出来
赋给新对象。
途径2:使用复制构造函数。
复制构造函数是一种特殊的构造函数,用来生成一个对象的副本。
 复制构造函数的作用:
使用一个已经存在的对象初始化一个同类的新对象。
 复制构造函数的特点:
复制构造函数名与类名相同,并且也没有返回值类型。
复制构造函数可写在类中,也可以写在类外。
复制构造函数要求有一个类类型的引用参数。
 如果没有显式定义复制构造函数,系统自动生成一个默认形式的复制构
造函数。

复制构造函数的形参为本类的对象引用。
class 类名
{ public : 类名(形参);//构造函数
类名([const ]类名&引用名);//复制构造函数

};
类名::类名([const] 类名 &引用名) //复制构造函数的实现 { 函数体 }

在以下情况调用复制构造函数:
1、当用类的一个对象去初始化该类的另一个对象时系统
自动调用复制构造函数实现拷贝赋值。

#include <iostream> 
using namespace std; 
class Complex {
public:
Complex(double r, double i) 
{ real = r; imag = i; }
Complex( Complex & c)
{
real = c.real; imag = c.imag;
cout<<"copy constructor!"<<endl;
}
private :
double real, imag;
}; 
int main( ){
Complex c1(1,2); 
Complex c2(c1); 
Complex c3= c1; //用对象c1去初始化该类的另一个对象c2|c3时复制构造函数被调用
c3=c2; //非初始化,不调用复制构造函数
return 0;
}
程序执行结果为:
copy constructor!
copy constructor!

2、若函数的形参为类对象,调用函数时,实参传递给形参,系
统自动调用复制构造函数,用实参对象去初始化形参对象。
如果某函数有一个参数是类Complex的对象,那么该函数被
调用时,类Complex的复制构造函数将被调用。

void func(Complex c) { };
int main( )
{
Complex c1(1,2);
func(c1); //调用复制构造函数c<-c1
return 0;
}
程序执行结果为:
copy constructor!

3、如果函数的返回值是类的对象,函数执行完返回调用者时
,需要生成一个临时对象作为函数返回结果,此时,系统需要
调用复制构造函数完成临时对象的生成。

例如:
Complex func() 
{
Complex c1(1,2);
return c1; //调用复制构造函数
};

除此之外,还将调用析构函数销毁临时对象。
如果有形如 a=func()的表达式,还将调用赋值函数完成赋值操作。

*浅复制

●浅复制:被复制对象的所有变量都含有与原来的对象相同的值,而其所有的对其他
对象的引用都仍然指向原来的对象。
说明:
一个对象中的数据成员:
 有的是值类型的数据,它的值就是简单的值,  有的是引用类型的数据,它的值是地址,例如:指针类型的成员函数。
浅复制在复制时,将这个对象的值数据和引用数据(均为非静态数据)全部复制过
去,获得了这个对象的值和地址。
即:当其中一个对象的引用字段所指向的地址中的变量变化时,所有浅复制对象中
的该引用字段都会发生变化。
●默认复制构造函数所进行的是简单数据复制,即浅复制。

一个浅复制的例子
#include <iostream>
using namespace std;
class Test
{
private:
int a,b;
public:
Test(int x, int y) //提供的形式参数,是为了给数据成员直接初始化的
{ a=x; b=y; }
Test(const Test& C) //复制构造函数,提供一个同类型对象作为参数
{ a=C.a; b=C.b; }
void show ()
{ cout<<a<<" "<<b<<endl; }
}; 
int main()
{
Test xx(100,10); //执行构造函数Test::Test(int x, int y)
Test yy(a); //执行构造函数Test::Test(const Test& C)
Test zz=a; //也执行构造函数Test::Test(const Test& C)
yy.show();
zz.show();
return 0;
}
Shandong Agricultural University 
**浅复制存在的问题**
#include <iostream>
#include <string>
using namespace std;
class Test
{
private:
int a;
char *str;
public:
Test(int b, char *s)
{ a=b; str=new char[strlen(s)+1]; strcpy(str,s); }
void setA(int b) {a=b; }
void setStr(char *s) { strcpy(str,s); }
void show () {cout<<a<<","<<str<<endl; }
};
int main()
{
Test xx(100,"hello");
Test yy(a);
xx.show(); yy.show();
xx.setA(80); xx.setStr("abc");
xx.show(); yy.show();
return 0;
}
//运行结果
100,hello
100,hello
80,abc
100,abc

*深复制

●深复制:通过一个对象初始化另一个对象时,
不仅将被复制对象中所有非引用类型的字段复制给新对象,
也将引用类型所指向地址中存储的对象复制给新的对象。
定义支持深复制的复制构造函数

  1. 缺省的复制构造函数是浅复制构造函数
  2. 深复制构造函数必须显式定义
  3. 当成员变量中含有指针变量时,需要定义深复制构造
    函数
  4. 深复制构造函数的特点
    ① 定义:类名::类名([const] 类名 &对象名); ② 成员变量的处理:对指针类型的成员变量,使用new操作符
    进行空间的申请,然后进行相关的复制操作

```cpp
#include <iostream>
#include <string>
using namespace std;
class Test
{
private:
int a;
char *str;
public:
Test(int b, char *s)
{ a=b; str=new char[strlen(s)+1]; strcpy(str,s); }
void setA(int b) {a=b; }
void setStr(char *s) { strcpy(str,s); }
void show () {cout<<a<<","<<str<<endl; }
Test(const Test& C) {
a=C.a; str=new char[strlen(C.str)+1];
strcpy(str,C.str); //str=C.str;
}
};
int main()
{
Test xx(100,"hello");
Test yy(a);
xx.show(); yy.show();
xx.setA(80); xx.setStr("abc");
xx.show(); yy.show();
return 0;
}
//运行结果
100,hello
100,hello
80,abc
100, hello

*析构函数

对象生存期结束时,需要做清理工作,比如:释放成员(指针)所占有的存
储空间。析构函数可以完成上述工作。
 作用:用于完成对象被删除前的一些清理工作。至于完成怎样的清理工
作,由设计者在函数体中实现.  在对象的生存期结束的时刻,即在删除一个对象前由系统自动调用,然
后再释放此对象所属的空间。
 设计者希望在最后一次使用对象之后所执行的任何操作都可以放在析构
函数中执行。
 如果程序中未声明析构函数,编译器将自动产生一个默认的析构函数。
 规定:
 是类的公有函数成员,名称由类名前加” ~”构成
 没有参数,没有返回值
 一个类中只能定义一个析构函数,不能重载。

默认析构函数
若没有显式定义析构函数,则系统自动生成一个默认形式的
析构函数。

系统自动生成的默认构造函数形式如下: 类名::~类名(){}

一般情况下,可以不定义析构函数
但如果类的数据成员中包含指针变量是从堆上进行存储空间
分配的话,需要在析构函数中进行存储空间的回收。

构造函数和析构函数举例
 数据成员是字符串时,可以用分别使用:字符数组,string类的对象,字符
指针表示字符串。处理方法略有不同。例如:
#include <iostream>
#include <string>
using namespace std;
class CStudent
{private:
int number;
char name[20]; //字符数组
string addr; //string类的对象
char *email; //字符指针
int age;
public:
CStudent(int xh=0, char *xm="Noname", string ad="Noad",char *em="Noemail",int a=18);
CStudent(const CStudent & s); //复制构造函数
~CStudent( ); //析构函数
void setStudent(int xh=0, char *xm="Noname", string ad="Noad",char *em="Noemail",int a=18);
void printStudent( );
int GetAge( );
};
CStudent::CStudent(int xh, char *xm, string ad,char *em,int a)
{ number = xh;
strcpy(name, xm); //字符数组
addr=ad; //string类的对象
email=new char[strlen(em)+1];strcpy(email, em); //字符指针
age = a; 
}
CStudent::CStudent(const CStudent & s)
{ if(this!=&s){
number = s.number;
strcpy(name, s.name); //字符数组
addr=s.addr; //string类的对象
email=new char[strlen(s.email)+1];strcpy(email, s.email);
//字符指针
age = s.age; 
} }
void CStudent::setStudent(int xh, char *xm, string ad,char *em, int a)
{ number = xh;
strcpy(name, xm); //字符数组
addr=ad; //string类的对象
delete []email; //字符指针
email=new char[strlen(em)+1];strcpy(email, em);
age = a; }
CStudent::~CStudent( ){
delete [ ]email; //字符指针
//cout<<number<<endl; }
void CStudent::printStudent(){
cout<<number<<" "<<name<<" "<<addr<<" "<<email<<" "<<age<<endl;}
int CStudent::GetAge()
{ return age;}
61
int main()
{
int sum=0;
CStudent s[8] = { CStudent(10000, "AAAAAA", "shanghai", "aaa@126.com", 20),
CStudent(10001, "BBBBBB", "qinghai", "bbb@126.com",22 ),
CStudent( ),CStudent( ),
CStudent(10004, "EEEEEE", "shangdang", "eee@126.com",18 )
};
s[2].setStudent(10002, "CCCCCC", "weihai", "ccc@126.com",24 );
s[3].setStudent(10003, "DDDDDD", "shandong", "ccc@126.com",21 );
s[5].setStudent(10005, "FFFFFFF", "heihai", "fff@126.com",23 );
s[6].setStudent(10006, "GGGGG", "shanxi", "ggg@126.com",20 );
s[7].setStudent(10007, "HHHHH", "jiangsu", "hhhh@126.com",20 );
for(int i=0; i<8; i++)
{ sum += s[i].GetAge();
s[i].printStudent(); }
cout << sum/8 << endl;
return 0;
}

*对象数组

对象数组:数组中的每一个元素都是类的对象。
声明一个一维对象数组的一般形式
类名 数组名[常量表达式];
引用对象数组元素的公有成员
数组名[下标].成员名;
对象数组的初始化
调用构造函数对每个元素初始化
如: Box a[3]={Box(10,12,15),Box(15,16,17),Box(16,20,26)};

*类的其他成员

一、常成员
1.常数据成员:在类中定义的不能修改其值的一些数据成员,类似于常变量,虽然是变量,也有自己的地址,但是一经赋初值,便不能再被修改。
适用于类中定义一些初始化之后不希望被修改的数据成员。常数据成员表示它在某个对象生存期内是常量, 即在对象生成时给出常量值,在对象生存期内其值不可改变。而对于整个类而言,不同的对象其常数据成员的值可以不同。
定义方法:const 类型名 变量名;
类中的常数据成员只能通过构造函数的参数初始化表进行初
始化,任何其他函数都不能对该成员赋值 。

2.常成员函数的引入:
由于数据封装在类的内部,需要小心处理,不能破坏成员数据。
可以使用const来保护数据成员——常数据成员, 也可以使用const来保护成员数据不被成员函数改变,这种成员函数称为常成员函数。它告诉编译器,这个函数是安全的,不会改变对象的数据成员的值(一个没有被明确声明为常成员函数的成员函数被认为是危险的,因为它可能会改变数据成员的值)
常成员函数的说明格式:
类型说明符 函数名(参数表) const;
 const是函数类型的一个组成部分,因此在函数的实现部分也要带关键字const。
常成员函数不能更新对象的数据,也不能调用非const修饰的成员函数(静态成员函数、构造函数除外)
3.常对象
如果一个对象在初始化后不需要修改其值,可定义为常对象。
常对象的说明形式: 类名 const 对象名[(参数表)];
或者 const 类名 对象名[(参数表)];
定义常对象时必须进行初始化,而且不能被更新。
说明:
(1)C++不允许直接或间接更改常对象的数据成员(数据成员的值,包括公有、私有访问权限的所有数据成员) 。
(2)C++规定常对象只能调用它的:常成员函数,静态成员函数,构造函数(具有公有访问权限)。
对比:普通对象既可以调用非常成员函数,也可以调用常成员函数(具有公有访问权限的任何的成员函数)

二、静态成员
 声明为static的类数据成员称做静态数据成员
 声明为static的类成员函数称做静态成员函数
能在类的范围内共享数据。 • 静态成员不属于某一个单独的对象,而是为类的所有对象所共有
• 根据静态存储的特点,当对象不存在时,仍可以访问该静态成员。
• 静态成员函数的作用不是为了对象之间的沟通,而是为了能处理静态数据成员: 保证在不依赖于某个对象的情况下,访问静态数据成员。
1.静态数据成员
含有静态数据成员的类和静态数据成员的定义如:
class A
{ int n;
static int s;
} CMyclass;
int A::s=0; //在类外定义和初始化
注意:
sizeof 运算符不计算静态成员变量,sizeof(CMyclass)等于4。使用静态数据成员可以节省内存,因为它是所有对象所公有的,因此,对多个对象来说,静态数据成员只存储一处,供所有对象共用。
2.静态成员函数
 静态成员函数用于操作静态数据成员,它只能直接引用属于该类的静态数据成员或静态成员函数。
 类的静态成员是属于类的而不是属于哪一个对象的,它们都不是对象成员。通常定义成公有的,因此,对静态成员的引用不需要用对象名。类外代码可以使用类名和作用域操作符来调用静态成员函数。
 静态成员函数没有this指针,只能对静态数据操作。

定义静态成员函数的格式如下: static 返回类型 静态成员函数名(参数表);

与静态数据成员类似,调用公有静态成员函数的一般格式有如下几种:
类名::静态成员函数名(实参表)
对象. 静态成员函数名(实参表)
对象指针->静态成员函数名(实参表)

说明:
(1)静态成员函数在类外定义时不用static前缀。
(2)静态成员函数主要用来访问同一类中的静态数据成员。
(3)私有静态成员函数不能在类外部或用对象访问。
(4)可以在建立对象之前处理静态数据成员。
(5)编译系统将静态成员函数限定为内部连接:与现行文件相连接的其他文件中的同名函数不会与该函数发生冲突,维护了该函数使用的安全性,这是使用静态成员函数的另一个原因。
(6)静态成员函数中没有this指针。
(7)静态成员函数不能直接访问类中的非静态数据成员。如有需要,可以通过指向对象的指针或对象的引用访问该对象的非静态成员。

三、友元
友元是一种定义在类外部的普通函数,但它需要在类体内进行说明,为了和该类的成员函数加以区别,在说明时前面加以关键字friend。友元不是成员函数,但是他能够访问类中的私有成员。友元的作用在于提高运行效率,但 是,他破坏了类的封装性和隐藏性,使得非成员函数能够访问类的私有成员。
友元可以是函数,称为友元函数;
友元也可以是类,称为友元类。
友元关系不是对称关系。即 Y 是 X 的友元,不意味着 X
是 Y 的友元。
友元关系不是传递关系。即 Y 是 X 的友元,Z 是 Y 的友
元,但 Z 不一定是X的友元。
1.友元函数

定义一个boat类,含有重量weight数据成员。定义类外的一个
函数, 该函数能输出一个boat对象的重量weight。
class Boat{
int weight;
public:
Boat(int w):weight(w){}
friend void display_weight(Boat &a);
};
void display_weight( Boat &a ){
cout<<a.weight<<endl; }
int main(){
Boat b(100);
display_weight(b);
return 0;
}

2.友元类

#include<iostream>
using namespace std ;
class A
{ friend class F ;
public :
void Display( ) { cout << x << endl ; }
private :
int x ;
} ;
class F
{ public :
void Set ( int i ) { Aobject . x = i ; }
void Display ( ) { Aobject . Display ( ) ; }
private :
A Aobject ;
} ;
int main( )
{ F Bobject ;
Bobject.Set ( 100 ) ;
Bobject.Display ( ) ;
return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值