一、类与对象的定义与访问
类是具有相同性质和功能的东西构成的集合,简单说,就是允许用户自己为定义应用所需类型及功能,方便调用,从而使得程序简洁且易修改,有效的吧数据组织起来,避免重复定义数据。
(一)类的声明
class<类名>
{
public:
公有段数据成员和成员函数;
protected:
保护段数据成员和成员函数;
Private:
私有段数据成员和成员函数;
};——分号不能省略!!!
class是关键字,私有成员只能在类中可见,不能在类外或派生类中使用;保护成员在类和它的派生类中可见;公有成员在类中和类外可见。除class外,关键字struct也可以定义类,由此定义的类,若不特别指出,则所有成员都是公有的。
- 类的成员可以是其他类的对象,但不能以自身的对象作为本类的成员,而类自身的指针和引用可以作为类的成员。
- 类定义必须以分号“;”结束。
- C++默认类的成员访问是私有的。
- 对象必须通过成员使用,不能整体操作。
2.成员函数
类的成员函数是实现类的行为属性的成员。一般将成员函数声明为函数原型,在类外具体实现成员函数。成员函数重点是对数据成员进行操作。
成员函数的说明语句
返回值类型 类名::成员函数名(参数表)
{
函数体
【例】Date类的成员函数定义在类外时写为:
VoidDate::SetDate(int y,int m,int d)
{
year = y;
month = m;
day = d;
}
Int Date::IsLeapYear()
{
return(year%4==0 && year%100!=0)||(year%400==0);}/
void Date::PrintDate()
{ cout<<year<<”.”<<month<<”.”<<day;}
简单的成员函数可以直接在类中定义:
//……
public:
void SetDate(int y,int m,int d)
{ year = y;
month = m;
day = d;
}
3.对象
对象是类的实例或实体,类与对象的关系,如同c++基本数据类型和该类型的变量之间的关系。
对象的说明语句:
类名 对象名1,对象名2,…,对象名n;
定义对象应注意
●必须在定义了类之后,才可以定义类的对象。
4.类成员的访问
对象成员的访问包括:
●圆点访问形式:对象名.公有成员
1.
#include<iostream>
using namespace std;
class Tclass
{
public:
int x,y;
void print()
{ cout<<x<<”,”<<y;}
};
int main()
{
Tclass test;
test.x=100; //通过圆点访问形式访问公有成员函数;
test.y=200;
test.print();
}
#include<iostream>
using namespace std;
class Tclass
{
public:
int x,y;
void print()
{ cout<<x<<”,”<<y;}
};
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;
}
【注】类定义和使用时应注意:
1. 类的任何成员都必须指定访问属性,一般将数据成员定义为私有成员或保护成员,将成员函数定义为公有成员。
2. 类在类的定义中不能对数据成员进行初始化。
3. 中的数据成员可以是C++语法规定的任意数据类型。
4. 类的成员可以是其他类的对象,称为类的组合。但不能以类自身的对象作为本类的成员。
5. 类定义必须以分号“;”结束
6. class与struct的不同:
① class中,成员缺省情况是private。
② struct中,成员缺省情况是public。
5.函数重载:
函数名相同,但参数不相同(类型不同,或者个数不同)的一组函数。
编译器根据不同参数的类型和个数产生调用匹配。
函数重载用于处理不同数据类型的类似任务 。
参数个数相同
#include<iostream>
using namespace std ;
int abs ( int a ) ;
double abs ( double f ) ;
int main ()
{ cout << abs ( -5 ) << endl ;
cout << abs ( -7.8 ) << endl ;
}
int abs ( int a )
{ return a < 0 ? -a : a ; }
double abs ( double f )
{ return f < 0 ? -f : f ; }
#include<iostream>
using namespace std ;
int max ( int a , int b ) ;
int max ( int a , int b, int c ) ;
int main ()
{ cout << max ( 5, 3 ) << endl ;
cout << max (4, 8, 2 ) << endl ;
}
int max ( int a , int b )
{ return a > b ? a : b ; }
int max ( int a , int b, int c )
{ int t ;
t = max ( a , b ) ;
return max ( t , c ) ;
}
二、 构造函数和析构函数
1.构造函数
构造函数是用于创建对象的特殊成员函数,当创建对象时,系统自动调用构造函数。当用户定义了构造函数时,缺省的构造函数不再起作用,必须定义两个,一个不带参数(起缺省作用),一个带参数。
构造函数的作用是:为对象分配空间;对数据成员赋初值;请求其他资源。
没有用户定义的构造函数时,系统提供缺省版本的构造函数
构造函数名与类名相同:类名
构造函数可以重载,构造函数可以有任意类型的参数,但没有返回类型。构造函数一般定义为公有成员。
构造函数的说明语句:类名::类名(参数表)
默认构造函数:
如果类中没有定义构造函数,系统将自动生成一个默认形式的构造函数,用于创建对象,默认构造函数形式:类名::类名(){}
默认构造函数是一个空函数。
通常,利用构造函数创建对象有以下两种方法:
(1) 利用构造函数直接创建对象.其一般形式为: 类名 对象名[(实参表)];
这里的“类名”与构造函数名相同,“实参表”是为构造函数提供的实际参数。
(2) 利用构造函数创建对象时,通过指针和new来实现。其一般语法形式为:
类名 *指针变量 =new 类名[(实参表)];
对象不要在函数以外定义,即不要定义成全局变量。
构造函数的初始化列表
--------数据成员的初始化
构造函数初始化成员有两种方法:
A.使用构造函数的函数体进行初始化
B.使用构造函数的初始化列表进行初始化
B.使用构造函数的初始化列表进行初始化
格式:
funname(参数列表):初始化列表
{ 函数体,可以是空函数体 }
初始化列表的形式: 成员名1(形参名1),成员名2(形参名2),成员名n(形参名n)
【注】
必须使用参数初始化列表对数据成员进行初始化的几种情况
- 数据成员为常量
- 数据成员为引用类型
- 数据成员为没有无参构造函数的类的对象
类成员的初始化的顺序:
按照数据成员在类中的声明顺序进行初始化,与初始化成员列表中出现的顺序无关。
2.构造函数的调用
在生成对象时调用构造函数。
如:
class Person
{
public:
void setAge( unsigned n ){ age = n; }
unsigned getAge( ) { return age; }
private:
unsigned age;
};
int main()
{
person a;
person b[60]
…….
2.析构函数
对象生存期结束时,需要做清理工作,比如:释放成员(指针)所占有的存储空间
析构函数可以完成上述工作。析构函数自动调用(隐式调用)。
析构函数没有返回值,不能有参数,也不能重载
定义格式如下(类外实现):
类名::~类名()
{
函数语句
}
若没有显式定义析构函数,则系统自动生成一个默认形式的析构函数。系统自动生成的默认构造函数形式如下:类名::~类名(){}
一般情况下,可以不定义析构函数 。 但如果类的数据成员中包含指针变量是从堆上进行存储空间分配的话,需要在析构函数中进行存储空间的回收。【例】
#include <iostream>
#include <string>
using namespace std;
class Student{
public:
Student(int n, string a_name, char s) {
num=n;
name=a_name;
sex=s; cout<<"Constructor called."<<endl;
}
~Student()
{ cout<<"Destructor called."<<endl; }
void display()
{cout<<name<<endl;cout<<num<<endl;cout<<sex<<endl; }
private:
int num; string name; char sex;
};
int main()
{
Student stud1(10001,"Wang_li",'f');
stud1.display();
Student stud2(10002,"Zhao_Wu",'s');
stud2.display();
return 0;
}
3.this指针:在类内使用,指明对象本身
用类去定义对象时,系统会为每一个对象分配存储空间。如果一个类包括了数据和函数,要分别为数据和函数的代码分配存储空间。按理说,如果用同一个类定义了10个对象,那么就需要分别为10个对象的数据和函数代码分配存储单元。
需要显式引用this指针的三种情况:
(1)在类的非静态成员函数中返回类对象本身或对象的引用的时候,直接使用 return *this,返回本对象的地址时,return this。
(2)当参数与成员变量名相同时,如this->x = x,不能写成x = x。
(3)避免对同一对象进行赋值操作,判断两个对象是否相同时,使用this指针。
【例】
(1)函数返回对象的引用
#include <iostream>
#include <string>
using namespace std;
class Person{
public:
Person(string n, int a) {
name = n; //这里的 name 等价于this->name
age = a; //这里的 age 等价于this->age
}
int get_age(void) const{ return age; }
Person& add_age(int i) {age += i; return *this; }
private:
string name;
int age;
};
int main()
{
Person Li("Li", 20);
cout<<"Li age = "<< Li.get_age()<<endl;
cout<<"Li add age = "<< Li.add_age(1).get_age()<<endl;
//增加1岁的同时,可以对新的年龄直接输出;
return 0;
}
程序执行结果为:
Li age = 20
Li add age = 21
(2)参数与成员变量名相同
#include <iostream>
using namespace std;
class Point
{
public:
int x;
Point ():x(0){}
Point (int a){ x=a; }
void print(){ cout<<"x = "<<x<<endl; }
void set_x(int x){ x = x; }
};
int main(){
Point pt(5);
pt.set_x(10);
pt.print();
return 0;
}
程序执行结果为:
x = 5
若将set_x函数改为:
void set_x(int x) { this->x = x; }
程序执行结果为:
x = 10
(3)避免对同一对象进行赋值操作
#include <iostream>
using namespace std;
class Location
{
int X,Y; //默认为私有的
public:
void init(int x,int y) { X =x; Y = y;};
void assign(Location& pointer);
int GetX(){ return X; }
int GetY(){ return Y; }
};
void Location::assign(Location& pointer)
{
if(&pointer!=this) //同一对象之间的赋值没有意义,所以要保证pointer不等于this
{ X=pointer.X; Y=pointer.Y; }
}
int main(){
Location x;
x.init(5,4);
Location y;
y.assign(x);
cout<<"x.X = "<< x.GetX()<<" x.Y = "<<x.GetY();
cout<<"y.X = "<< y.GetX()<<" y.Y = "<<y.GetY();
return 0;
}
三、复制构造函数
1.复制构造函数用一个已有同类对象创建新对象进行数据初始化,C++为类提供默认版本的复制构造函数。程序员可以定义用户版本的复制构造函数
(1) 语法形式:
类名 :: 类名(const 类名 & 引用名 , …)
(2)复制构造函数的特点:
- 复制构造函数名与类名相同,并且也没有返回值类型。
- 复制构造函数可写在类中,也可以写在类外。
- 复制构造函数要求有一个类类型的引用参数。
- 如果没有显式定义复制构造函数,系统自动生成一个默认形式的复制构造函数。
(3)复制构造函数的调用:
以下三种情况下由编译系统自动调用:
- 声明语句中用类的一个已知对象初始化该类的另一个对象时。
- 当对象作为一个函数实参传递给函数的形参时,需要将实参对象去初始化形参对象时,需要调用复制构造函数。
- 当对象是函数的返回值时,由于需要生成一个临时对象作为函数返回结果,系统需要将临时对象的值初始化另一个对象,需要调用复制构造函数。
2.浅复制与深复制
(1)浅复制:
在用一个对象初始化另一个对象时,只复制了数据成员,而没有复制资源,使两个对象同时指向了同一资源的复制方式称为浅复制。即:对于复杂类型的数据成员只复制了存储地址而没有复制存储内容;默认复制构造函数所进行的是简单数据复制,即浅复制
(2)深复制
通过一个对象初始化另一个对象时,不仅复制了数据成员,也复制了资源的复制方式称为深复制。
四、类的其他成员
1.常成员
(1)常数据成员
l 常数据成员是指数据成员在实例化被初始化后约束为只读。
l 在类中,定义常成员用const约束。
l 在函数体内不能修改数据成员。
Ø 常数据成员的初始化方法
(1)如果在一个类中说明了常数据成员,那么构造函数就只能通过初始化列表对该数据成员进行初始化,而任何其他函数都不能对该成员赋值。
(2)常数据成员可以在构造函数中直接使用常量进行初始化。
(3) 使用带参数的构造函数,创建对象时,用实际参数对常数据成员赋值。
(2) 常对象
a.如果在说明对象时用const修饰,则被说明的对象为常对象。
b. 常对象的说明形式如下: 类名 const 对象名[(参数表)];
或者 const 类名 对象名[(参数表)];
c.在定义常对象时必须进行初始化,而且不能被更新。
【注】说明:
(1)C++不允许直接或间接更改常对象的数据成员。
(2)C++规定常对象只能调用它的常成员函数、静态成员函数、构造函数(具有公有访问权限)。
(3)常成员函数
在类中使用关键字const说明的函数为常成员函数,常成员函数的说明格式如:类型说明符 函数名(参数表) const;
const是函数类型的一个组成部分,因此在函数的实现部分也要带关键字const。
常成员函数不能更新对象的数据,也不能调用非const修饰的成员函数(静态成员函数、构造函数除外)。
2.静态成员
Ø 类成员冠以static声明时,称为静态成员。静态数据成员为同类对象共享。静态成员函数与静态数据成员协同操作。
Ø 静态成员不属于某一个单独的对象,而是为类的所有对象所共有。
Ø 静态成员函数的作用不是为了对象之间的沟通,而是为了能处理静态数据成员: 保证在不依赖于某个对象的情况下,访问静态数据成员。
(1) 静态数据成员
a. 静态数据成员在定义或说明时前面加关键字static.
b. 对于类的普通数据成员,每一个对象都各自拥有一个副本。(分配不同的存储空间)
c. 对于静态数据成员,每个类只拥有一个副本 。(在静态存储区分配一个存储空间,对所有对象都是可见的.
d. 公有访问权限的静态成员,可以通过下面的形式进行访问
类名::静态成员的名字
对象名.静态成员名字
对象指针->静态成员的名字
在静态成员函数内部,直接访问。
静态数据成员声明及初始化
在类外进行静态数据成员的声明:类型 类名::静态数据成员[=初始化值];不能在成员初始化列表中进行初始化;如果未进行初始化,则编译器自动赋初值(默认值是0);初始化时不能使用访问权限.
(2) 静态成员函数
当一个成员函数冠以static声明时,称为静态成员函数。静态成员函数提供了一个不依赖于类数据结构的共同操作,它没有this指针。因为静态成员函数只能访问类的静态数据成员,所以设计静态成员函数与静态数据成员可协同操作。
定义静态成员函数的格式如下:
static 返回类型 静态成员函数名(参数表);
与静态数据成员类似,调用公有静态成员函数的一般格式有如下几种:
类名::静态成员函数名(实参表)
对象. 静态成员函数名(实参表)
对象指针->静态成员函数名(实参表)
【注】说明:
(1)静态成员函数在类外定义时不用static前缀。
(2)静态成员函数主要用来访问同一类中的静态数据成员。
(3)私有静态成员函数不能在类外部或用对象访问。
(4)可以在建立对象之前处理静态数据成员。
(5)编译系统将静态成员函数限定为内部连接(在其他文件中不可见)。
(6)静态成员函数中是没有this指针的。
(7)静态成员函数不访问类中的非静态数据成员。如有需要,只能通过对象名(或指向对象的指针)访问该对象的非静态成员。
五、类的包含
Ø 类的包含是程序设计中一种软件重用技术。即定义一个新的类时,通过编译器把另一个类 “抄”进来;一个类的数据成员中包含另外一个类的对象。
Ø 当一个类中含有已经定义的类类型成员,带参数的构造函数对数据成员初始化,须使用初始化语法形式。构造函数 ( 形参表 ) : 对象成员1(形参表 ) , … , 对象成员n (形参表 ) ;
Ø 组合对象的初始化:1先初始化被包含的对象成员;
2再初始化自己的对象成员。
Ø 成员对象的构造函数调用次序和成员对象在类中的说明次序一致(声明顺序为:a1、b1、b2),与它们在成员初始化列表中出现的次序无关(初始化列表顺序为:b1、b2、a1)。析构函数的调用顺序相反。
【例】在学生信息管理的过程中, 需要知道每个学生的姓名,学号以及考试成绩
可以定义一个学生类,数据成员包括name(string), number(string)
考试科目比较多
方法一:在类中增加新的数据成员
方法二,定义成绩类,采用类的组合解决问题
#include<bits\stdc++.h>
using namespace std;
class Score
{
public:
Score(float c, float e, float m );
Score();
void show();
void modify(float c, float e, float m);
private:
float computer;
float english;
float mathematics;
};
class Student
{
private:
string name;
string stu_no;
Score score1;
public:
Student(string name1, string stu_no1, float s1, float s2, float s3):score1(s1,s2,s3){
name=name1;
stu_no=stu_no1;
}
~Student();
void modify(string name1, char *stu_no1, float s1, float s2, float s3);
void show();
};
void main()
{
Student stu1("LiMing", "990201", 90, 80, 70);
stu1.show ();
cout<<endl;
stu1.modify ("ZhangHao", "990202", 95, 85, 75);
stu1.show ();
}
六、对象数组
所谓对象数组是指每一数组元素都是对象的数组。 定义一个一维对象数组的格式如下:类名 数组名[下标表达式];
(1) 对象数组的初始化
1.当对象数组所属的类中包含带参的构造函数,可用初始化列表完成对象数组的初始化。
2.当对象数组所属的类中包含无参的构造函数,也可以先定义,再给每个数组元素赋值。
3.当对象数组所属的类中包含单个参数的构造函数,可简写。
(2)成员对象数组的初始化
成员对象的初始化可在构造函数初始化列表中进行,推想对于成员对象数组的初始化,也可以在本类构造函数使用初始化列表。
【例】
#include <iostream>
using namespace std;
class A{
public:
A(){ cout<<"Hello A."<<endl; }
A(int i):x(i) { cout<<"x = "<<x<<endl; }
int x;
};
class B{
public:
B() {
a[0] = A(0);
a[1] = A(1);
}
A a[2];
}
int main(){
B b;
cout<<b.a[0].x<<endl;
cout<<b.a[1].x<<endl;
return 0;
}
学习心得
描述信息的类为为数据类,成员函数肯定有一组set,get函数和构造函数;操作类关注功能实现,一定有数据成员,数据成员一定是集合类型,即对象数组。调试程序时,采用滚雪球的方法,即每定义一个功能就调试一次,先调试通一个在进行下一个函数类型的调试。
关于友元函数,友元是类对象操作的一种辅助手段。一个类的友元可以访问该类各种性质的成员。在类体中用friend对其进行声明。
【例】用友元函数计算两点之间的距离
#include<iostream>
using namespace std ;
#include<math.h>
class Point
{ public:
Point(double xi, double yi) { X = xi ; Y = yi ;}
double GetX() { return X ; }
double GetY() { return Y ; }
friend double Distance ( Point & a, Point & b ) ;
private: double X, Y ;
} ;
double Distance(Point & a, Point & b )
{ double dx = a.X - b.X ;
double dy = a.Y - b.Y ;
return sqrt ( dx * dx + dy * dy ) ;
}
int main()
{ Point p1( 3.0, 5.0 ) , p2( 4.0, 6.0 ) ;
double d = Distance ( p1, p2 ) ;
cout << "This distance is " << d << endl ;
}