类和对象的使用——面向对象程序设计课堂笔记
对老师上课ppt的归纳与整理,以便加深记忆
类和对象的使用
#一、类的声明和对象的定义
注意:分号不能少
二、成员访问限定符
1. 关键字private
限定的成员称为私有成员,对私有成员限定在该类的内部使用,即只允许该类中的成员函数使用私有的成员数据,对于私有的成员函数,只能被该类内的成员函数调用;类就相当于私有成员的作用域
2. 关键字public
限定的成员称为公有成员,公有成员的数据或函数不受类的限制,可以在类内或类外自由使用;对类而言是透明的
3. 关键字protected
所限定的成员称为保护成员,只允许在类内及该类的派生类中使用保护的数据或函数。即保护成员的作用域是该类及该类的派生类
三、声明类的类型
1. 类具有封装性,并且类只是定义了一种结构(样板),所以类中的任何成员 数据均不能使用关键字extern,auto或register限定其存储类型。
2. 在定义类时,只是定义了一种导出的数据类型,并不为类分配存储空间,所以,在定义类中的数据成员时,不能对其初始化。
class Test{
int x=5,y=6;//不合法
extern float x;//不允许
};
四、定义对象的方法
1. 先声明类的类型再定义对象
(1) class 类名 对象名; class student stu1,stu2;
(2) 类名 对象名; student stu1,stu2;
2. 在声明类的同时定义对象
class student{
}stu1,stu2;
3. 不出现类名,直接定义对象
class {
}stu1,stu2;
五、类的成员函数
1.在类外定义成员函数
函数定义:通常在类定义中,成员函数仅作声明。 函数定义通常在类的声明之后,在类外进行。
返回值类型 类名::函数名(参数表)
{
···//函数体
}
其中运算符“::”称为作用域运算符,它指出该函数是属于哪一个类的成员函数。
类的成员具有类作用域。 它们不是全局函数。
2. 内置成员函数
在类内定义的函数默认为内联函数 ,在类内用inline声明的函数,在类外定义前面加inline。内联成员函数只能在定义它们的文件中调用。
class student{
int n;
public:
void display1();
inline void display2();
};
void student::display1()
{
cout<<"hi"<<endl;
}
inline void student::display2()
{
cout<<"n:"<<n<<endl;
}
六、对象成员的引用
1. 对象名.成员 (.为成员运算符)
stu1.display();
2. 通过指向对象的指针访问对象成员
student *p,stu1;
p=&stu1;
p ->display();
(*p) . display();
3. 通过对象的引用来访问对象成员
student stu1;
student &pp=stu1;
pp.display();
七、 用构造函数实现数据成员的初始化
1. 功能
构造函数是一种特殊的成员函数,它主要用于对对象成员进行初始化。(给具体数值,或给指针成员分配空间等)
2. 构造函数的使用
在创建对象时,系统会自动地调用构造函数。 从而保证了先初始化后访问的顺序。
3. 声明和定义构造函数
#include<iostream>
#include<cmath>
using namespace std;
class complex{
private:
double real,imag;
public:
//无参构造函数
complex()
{
real=6;imag=8;
}
//有参构造函数
complex(double r,double i)//形参不要与数据成员同名
{
real=r;imag=i;
}
double abscomplex()
{
double t;
t=real*real+imag*imag;
return sqrt(t);
}
};
int main()
{
complex A(1.1,2.2);//定义类的对象A时调用构造函数complex
complex B;
cout<<"abs of complex A="<<A.abscomplex()<<endl;
cout<<"abs of complex B="<<B.abscomplex()<<endl;
return 0;
}
(1)无参构造函数:构造函数内无形参
class complex{
private:
double real,imag;
public:
//无参构造函数
complex()
{
real=6;imag=8;//初始化成员变量
}
};
调用形式:complex A;
(2)有参构造函数
①
class complex{
private:
double real,imag;
public:
//有参构造函数
complex(double r,double i)//形参不要与数据成员同名
{
real=r;imag=i;
}
};
隐式调用构造函数: complex A(1.1 , 2.2);
显式调用构造函数: complex A=complex(1.1 , 2.2);
② 用初始化列表对简单的数据成员进行初始化
class complex{
private:
double real,imag;
public:
//初始化列表
complex(double r,double i):real(r),imag(i)
{}
};
③使用默认参数的构造函数
#include<iostream>
#include<cmath>
using namespace std;
class complex{
private:
double real,imag;
public:
//使用默认参数的构造函数
complex(double r=1,double i=1);
double abscomplex()
{
double t;
t=real*real+imag*imag;
return sqrt(t);
}
};
complex::complex(double r,double i)
{
real=r;imag=i;
}
int main()
{
complex A(1.1,2.2);//定义类的对象A时调用构造函数complex
complex B;
complex C(2);
cout<<"abs of complex A="<<A.abscomplex()<<endl;
cout<<"abs of complex B="<<B.abscomplex()<<endl;
cout<<"abs of complex C="<<C.abscomplex()<<endl;
return 0;
}
注:
1. 构造函数的名字必须与类名相同,否则将被当作一般的成员函 数来处理。一般声明为public 。
2. 构造函数可以有任意类型的参数,但不能具有返回值。
3. 构造函数也可采用初始化列表对简单的数据成员进行初始化,但数组和指针等成员的初始化应在构造函数体中书写。
4. 一个类中可以定义多个构造函数,以便对类对象提供不同的初 始化方法。构造函数的名字相同,参数的个数或类型不同。定义一个对象时,只能执行其中一个构造函数。
5. 在实际应用中,通常需要给每个类定义构造函数。如果没有给类定义构造函数,则编译系统自动地添加一个默认的构造函数。一旦自定义了构造函数,系统就不再生成默认的构造函数了。
complex::complex( ){ ;} //是一个无参空函数。
八、析构函数
1. 功能
析构函数也是一种特殊的成员函数。它执行与构造函数相反的操作,通常用于执行一些清理任务,如释放分配给对象的内存空间等,并不是删除对象
2. 特点
(1)析构函数与构造函数名字相同,但它前面必须加一个波浪 号(~)
(2)析构函数没有参数,也没有返回值,而且不能重载,因此在一个类中只能有一个析构函数
(3)当撤消对象时(对象生命周期结束时),系统会自动地调用析构函数
#include<iostream>
#include<cmath>
using namespace std;
class complex{
double real,imag;
public:
complex(double r=0.0,double i=0.0)
{
cout<<"construction..."<<endl;
real=r;imag=i;
}
~complex()
{
cout<<"destruction..."<<endl;
}
double abscomplex()
{
return sqrt(real*real+imag*imag);
}
};
int main()
{
complex a(1.1,2.2);
cout<<"abs of complex a="<<a.abscomplex()<<endl;
return 0;
}
(4)每个类必须有一个析构函数。
若没有显式地为一个类定义析构函数,编译系统会自动地添加一个缺省的析构函数。
~complex(){}
对于大多数类而言,缺省的析构函数已经足够了。但是,如果在一个对象完成其操作之前需要做一些内部处理,则应该显式地定义析构函数
九、调用构造函数和析构函数的顺序
1. 同一类存储类别的对象的构造和析构的执行顺序
先构造的后析构,后构造的先析构
2.对象的存储类别不同,生命周期不同
(1)全局对象
在主函数之前构造,主函数结束或调用exit(0)时析构。
(2)局部自动对象
函数调用,建立对象时构造,函数结束时析构。 多次调用函数,就多次构造和析构。
(3)局部静态对象
程序第一次调用函数时构造,主函数结束或调用exit(0)析构。其间,调用函数不构造也不析构。
十、对象数组
1.对象数组:指所有数组元素都是对象的数组
若一个类有若干个对象,我们把这一系列的对象用一个数组来存放。建立对象数组时,每一个元素都要调用构造函数初始化。
2.静态创建对象数组
#include<iostream>
#include<cmath>
using namespace std;
class complex{
double real,imag;
public:
complex(double a)
{
real=a;imag=a;
cout<<"1"<<endl;
}
complex(double r,double i)
{
real=r;imag=i;
cout<<"2"<<endl;
}
complex()
{
real=imag=0.0;
cout<<"3"<<endl;
}
double abscomplex()
{
double t;
t=real*real+imag*imag;
return sqrt(t);
}
};
int main()
{
complex demo1[5];
cout<<"demo1[0]: "<<demo1[0].abscomplex()<<endl;
cout<<"————"<<endl;
complex demo2[5]={complex(1),complex(),complex(1.1,2.2)} ;
cout<<"demo2[0]: "<<demo2[0].abscomplex()<<endl;
cout<<"————"<<endl;
complex demo3[5]={1,4,2,3,6};
cout<<"demo3[0]: "<<demo3[0].abscomplex()<<endl;
cout<<"————"<<endl;
}
3.动态创建对象数组
这个类必须有无参构造函数或形参皆有缺省值
#include<iostream>
#include<cmath>
using namespace std;
class complex{
double real,imag;
public:
complex(double a)
{
real=a;imag=a;
cout<<"1"<<endl;
}
complex(double r,double i)
{
real=r;imag=i;
cout<<"2"<<endl;
}
complex()
{
real=imag=0.0;
cout<<"3"<<endl;
}
double abscomplex()
{
double t;
t=real*real+imag*imag;
return sqrt(t);
}
};
int main()
{
complex *p=new complex[3];
cout<<"————"<<endl;
p[0]=complex(1.0,2.0);
cout<<"p[0]:"<<p[0].abscomplex()<<endl;
cout<<"————"<<endl;
p[1]=complex(3.0);
cout<<"p[1]:"<<p[1].abscomplex()<<endl;
cout<<"————"<<endl;
p[2]=complex();
cout<<"p[2]:"<<p[2].abscomplex()<<endl;
cout<<"————"<<endl;
delete []p;
return 0;
}
十一、对象指针
1.指向对象的指针:类名 *对象指针名;
对象空间的起始地址,称为对象的指针
#include<iostream>
using namespace std;
class time{
public:
int h,m,s;
void get_time();
};
void time::get_time()
{
cout<<h<<":"<<m<<":"<<s<<endl;
}
int main()
{
time *p;//p为指向time类的对象的指针变量
time t;//t为time类对象
p=&t;//p为指向time类对象t的指针变量
(*p).h=1;(*p).m=10;(*p).s=50;
(*p).get_time();
return 0;
}
2.指向对象成员的指针
(1)数据成员(公有)
double *p1=&a.m; *p1=6;
(2)函数成员(公有)
3.this指针
注:
(1)this指针指向类对象的首地址
(2)this指针是一种隐含指针,它隐含于每个类的成员函数中
(3)构造函数等特殊成员函数中也有this指针
(4)成员函数内部访问其他成员函数,this指针将被逐级传递下去。
(5)this 指针是const 指针,即编译器规定不允许在程序中修改它的值
(6)当形式参数与数据成员同名时,需要加this
class ABC{
int x;
public:
void set(int x)
{
this->x=x;//或(*this).x=x;
}
};
十二、共用数据的保护:使用const来对其属性进行限定
1.常对象
格式
类名 const 对象名 [(实参表列)];
const 类名 对象名 [(实参表列)];
注:
(1) 不允许修改常对象中的数据成员的值
(2)常对象的数据成员为常变量且必须要有初值。(由构造函数初始化)
(3)外界只能调用常对象的常成员函数(除了由系统自动调用的 隐式的构造函数和析构函数),以防止修改对象中的数据成员的值。因常成员函数可以引用数据成员,但不会改变数据成员的值。
2.常对象成员
常数据成员
用const来声明常数据成员,其值不能改变,不能被赋值。只能通过构造函数的参数初始化表对常数据成员进行初始化。
class time{
const int h;
int s;
public:
time(int hour):h(hour)//只能通过构造函数的参数初始化表对常数据成员进行初始化。
{
s=0;
}
};
常对象和常对象成员的对比
3.常成员函数
格式
返回类型 成员函数名(形参)const
注:
(1)声明和定义函数时都要加const ,调用时不需加。
(2) 只能读本类中的数据成员而不能修改它们。
(3)可变的数据成员:mutable int count; ,可用常成员函数来修改它的值。
(4)常成员函数不能调用非常成员函数。(防止间接修改)
如何利用常对象和常成员
(1)当类中只有部分数据成员的值允许改变时,可将这部分数据成员声明为const 。
(2)若所有数据成员的值都不许改变,可将所有数据成员声明为const,或将对象声明为常对象。
(3)通过常成员函数来引用常对象中数据成员。
4.指向对象的常指针
格式
类名 * const 指针变量名 =对象地址;
指针值始终保持为其初始化值,不能改变,即其指向始终不变。
time t1(10,12,15);
time *const p=&t1;
5.指向常对象的指针变量
指向常变量的指针变量: const 类型名 *指针变量名;
不能通过该指针改变其所指变量的值。
该指针可以指向不同的变量。
int a;
const int b=7;
const int *p;
p=&a;
*p=9;//错误,不能通过该指针改变其所指变量的值。
p=&b;
注:
(1)只能用指向常对象的指针变量指向常对象。
(2)指向常对象的指针变量即可指向常对象,也可指向非常对象。 不能通过该指针改变对象。 该指针变量本身可以改变。
(3)常用于函数形参,目的是保护形参指针所指向的对象,使它 在函数执行过程中不被修改。即只想在函数中引用该对象, 而不想改变它。
6.对象的常引用
void fun(const Time &t);
// 则在函数fun中不能改变t的值,也就是不能改变其对应的实参的值
十三、对象的动态建立和释放
利用new运算符动态建立对象,用delete运算符撤销对象
在执行delete运算符时,在释放内存空间之前,自动调用析构函数
十四、对象的赋值与复制
1.赋值
将对象名2的值赋给对象名1: 对象名1=对象名2
注:
(1)对象名1和对象名2必须属于同一个类
(2)对象的赋值只对其中的数据成员赋值,而不对 成员函数赋值。
(3)类的数据成员中不能包括动态分配的数据,否则在赋值时可能出现严重后果。
2.复制
用一个已有的对象快速地复制出多个完全相同的对象:①类名 对象名2(对象名1) ②类名 对象名1=对象名2;
十五、静态成员
静态成员
(1)定义
在类定义中,它的成员(包括数据成员和成员函 数)可以用关键字static声明为静态的
(2)特性
不管这个类创建了多少个对象,静态成员只有一个拷贝, 这个拷贝被所有属于这个类的对象共享。
(3)包括:静态数据成员 、静态成员函数
1.静态数据成员:声明为static的数据成员
(1)存储方式
与一般的数据成员不同,无论建立了多少个对象,在内存中都只有一个静态数据的拷贝。静态数据成员是单独存储的,而不是对象的组成部分。
(2)初始化
必须在类声明之外使用单独的语句初始化。一般在main() 开始之前、类的声明之后的全局地带为它初始化。不能在类中进行初始化,也不能通过构造函数初始化。因为在类中不给它分配内存空间。缺省时,静态成员被初始为0。
(3)格式
数据类型 类名::静态数据成员名=初值;
(4)生存期
静态数据成员与静态变量一样,是在编译时创建并初始化。它在该类的任何对象被建立之前就存在,它可以在程序内部不依赖于任何对象被访问 。到程序结束时才释放。
(5)外部访问方法
静态数据成员属于类,而不像普通数据成员那样属于某一对象,因此可以使用
“类名::” 访问公有的静态数据成员
也可通过对象名引用。
对私有静态数据成员只能通过类成员函数访问。
(6)内部访问方法:具有类作用域
(7)应用
静态数据成员的主要用途是 定义类的各个对象所公用的数据,如统计总数、平均数等, 而可以不必使用全局变量。依赖于全局变量的类几乎都 是违反面向对象程序设计的封装原理的。
2.静态成员函数:前面有static声明的成员函数
注
(1)静态成员函数可以定义成内联的,也可以在类外定义。在类外定义时,不要用static前缀。
(2)静态成员函数是一种特殊的成员函数,它不属于某一个特定的对象。而是属于类的。
(3)在一般的成员函数中都隐含有一个this指针,用来指向对象自身,而在静态成员函数中没有this指针,因为它不与特定的对象相联系。
(4)一般而言,静态成员函数访问的基本上是静态数据成员。最好不用静态成员函数引用非静态数据成员。
(5)可以用它在建立任 何对象之前处理静态数据成员,这是普通成员函数不能 实现的功能。
调用方法
(1)类名::静态成员函数名(); (2)对象.静态成员函数名(); (3)指向对象的指针->静态成员函数名();十八、友元
使得类外部的函数或类能够访问类中的私有成员
友元既可以是不属于任何类的一般函数,也可以是另一个类的成员函数,还可以是整个一个类(这样,这个类中的所有成员函数都可以成为友元函数)。
1.友元函数
友元函数不是当前类的成员函数,而是独立于当前类的外部函数,但它可以访问该类的所有对象的成员,包括私有成员和公有成员。
定义与声明
在类定义中声明友元函数时,需在其函数名前加上关键字friend。
此声明可以放在公有部分,也可以放在私有部分。
友元函数可以定义在类的内部,也可以定义在类的外部。
#include<iostream>
#include<string>
using namespace std;
class girl{
private:
string name;
int age;
public:
girl(string n,int a)
{
name=n;age=a;
}
friend void disp(girl &);//声明友元函数
};
void disp(girl &x)//定义友元函数
{
cout<<x.name<<endl<<x.age<<endl;
}
int main()
{
girl g("Alice",18);
disp(g);//调用友元函数
return 0;
}
注
(1)友元函数不是成员函数,因此在类的外部定义友元函数时,不必像成员函数那样,在函数名前加上“类名::”。
(2)友元函数应带有一个该类的入口参数。因它不能直接使用对象的成员的名称,也不能通过this指针引用对象的成员,它必须通过入口参数传递进来的对象名或对象指针来引用该对象的成员。
(3)同时为多个类的友元:当一个函数需要访问多个类时, 友元函数非常有用,普通的成员函数只能访问其所属的类, 但是多个类的友元函数能够访问相应的所有类的数据。
(4) 友元函数通过直接访问对象的私有成员,提高了程序运行的效率。经常在运算符被重载时,需要用到友元。但是友元函数破坏了数据的隐蔽性,降低了程序的可维护性,因此使用友元函数应谨慎。
2.友元成员函数
一个类的成员函数也可以作为另一个类的友元,这种成员函数不仅可以访问自己所在类对象中的私有成员和公有成员, 还可以访问friend声明语句所在类对象中的私有成员和公有成员,这样能使两个类相互合作、协调工作,完成某一任务。
说明
(1)类A的成员函数作为类B的友元函数时,必须先定义类A。
(2)程序中还要使用“向前引用”,因为函数disp()中将 girl &作为参数,而girl要晚一些时候才定义
3.友元类 friend 类名;
说明:
(1)友元关系是单向的,不具有交换性(A是B的朋友, 不能推断出:B是A的朋友)。
(2)友元关系也不具有传递性(A是B的朋友,B是C的朋友,不能推断出:A是C的朋友)。
十七、类模板
(一)函数模板
1.函数模板的作用
定义了函数模板之后,可以产生针对不同数据类型使用相同功能的函数实例。
2.函数模板的定义
template < class T >
或 template < typename T >
#include<iostream>
#include<string>
using namespace std;
template<class T>
T maxn(T a,T b)
{
return (a>b)?a:b;
}
int main()
{
cout<<maxn(10,20)<<endl;
cout<<maxn(10.5,20.5);
return 0;
}
(二)类模板
1.定义类模板
template < class 类型参数名>class 类模板名 { … … }
类型参数名
按标识符取名。如有多个类型参数,每个类型参数都要以class为前导,两个类型参数之间用逗号分隔。
类模板名:按标识符取名。
#include<iostream>
using namespace std;
template<class numtype>
class compare{
private:
numtype x,y;
public:
compare(numtype a,numtype b)
{
x=a;y=b;
}
numtype maxnum()
{
return (x>y)?x:y;
}
numtype minnum()
{
return (x<y)?x:y;
}
};
2.在类模板外定义成员函数的语法
template < class 虚拟类型参数>函数类型 类模板名<虚拟类型参数>::成员函数名(形参表) { … … }
template<class numtype>
numtype compare<numtype>::max()
{ return (x>y)?x:y;}
3.用类模板定义对象
类模板名 <实际类型名> 对象名;类模板名 <实际类型名> 对象名(实参表);
#include<iostream>
using namespace std;
template<class numtype>
class compare{
private:
numtype x,y;
public:
compare(numtype a,numtype b)
{
x=a;y=b;
}
numtype maxnum()
{
return (x>y)?x:y;
}
numtype minnum()
{
return (x<y)?x:y;
}
};
int main()
{
compare<int> cmp1(3,7);
cout<<"max:"<<cmp1.maxnum()<<endl;
cout<<"min:"<<cmp1.minnum()<<endl;
compare<float>cmp2(45.78,93.6);
cout<<"max:"<<cmp2.maxnum()<<endl;
cout<<"min:"<<cmp2.minnum()<<endl;
compare<char>cmp3('a','A');
cout<<"max:"<<cmp3.maxnum()<<endl;
cout<<"min:"<<cmp3.minnum()<<endl;
return 0;
}