类是面向对象程序设计实现信息封装的基础,是一种用户自定义类型。
一.类
1.类的定义:类是对具有相同属性和行为的一组对象的抽象与统一描述。属性以数据表示,称为类的数据成员。行为通过函数实现,称为成员函数。
c++类定义的格式:
class 类名
{
public:
公有数据成员和成员函数;
protected:
保护数据成员和成员函数;
private:
私有数据成员和成员函数;
}; //分号不能省略;
各成员函数的具体实现;
- 类成员访问控制表
public:声明公有成员。是类的外部接口,在类中和类外可见。
protected: 声明保护成员。在类和它的派生类中可见。
private: 声明私有成员。只能在类中可见,不能在类外或派生类中使用。
2.成员函数
简单的成员函数的实现可以在类中定义,复杂的或者一般将成员函数声明为函数原型,在类外具体实现成员函数。
成员函数在类外定义:
返回值类型 类名::成员函数名(参数表) //作用域区分符有两个冒号构成,它用于标识属于什么类的成员;
{
函数体
}
成员函数的作用:
- 操作数据成员,包括访问和修改数据成员
- 用于协同不同的对象操作,称为传递消息。成员函数通过参数与其他对象协同操作。
3.对象:
对象是某个类的能动实体,是类的实例或实体。说明一个类的对象后,编译器为每个对象的数据成员分配内存。对象没有成员函数的副本,类成员函数可以被对象调用。
对象的定义格式如下:
类名 对象名1,对象名2,…,对象名n;
注意:必须在定义了类之后,才可以定义类的对象。
4.类成员的访问:
使用对象包括访问对象的数据成员和调用成员函数。
对象成员的访问包括:
- 圆点访问形式:对象名.公有成员
- 指针访问形式:"->" 对象指针变量名->公有成员
二.内联函数
1.内联函数作用:减少频繁调用小子程序的运行的时间开销、
2.内联函数声明:inline 函数原型
注意:
内联函数仅在函数原型作一次声明。适用于只有1~5行的小函数,不能含有while和switch等复杂的结构控制语句,不能递归调用。.关键字 inline 必须与函数定义体放在一起才能使函数成为内联,仅将 inline 放在函数声明前面不起任何作用。
inline void Foo(int x, int y); // inline 仅与函数声明放在一起,不能成为内联函数;
inline int Coord::getx() { return x;} // inline 与函数定义体放在一起,可以成为内联函数;
inline 与函数定义体放在一起,可以成为内联函数;
三:成员函数可以重载
Ø函数重载:函数名相同,但参数不相同(类型不同,或者个数不同)的一组函数。
Ø 编译器根据不同参数的类型和个数产生调用匹配
Ø 函数重载用于处理不同数据类型的类似任务
eg:
(1)参数个数相同但类型不同
#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 ; }
(2)参数个数不同
#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)构造函数
Ø 构造函数是用于创建对象的特殊成员函数
当创建对象时,系统自动调用构造函数
Ø构造函数的作用是:
为对象分配空间;对数据成员赋初值;请求其他资源
Ø 没有用户定义的构造函数时,系统自动提供缺省版本的构造函数
Ø 构造函数名与类名相同:类名
Ø 构造函数可以重载
Ø 构造函数可以有任意类型的参数,但没有返回类型
(2)析构函数
Ø析构函数是用于取消对象的成员函数
当一个对象作用域结束时,系统自动调用析构函数(隐式调用)
Ø析构函数的作用是进行对象消亡时的清理工作,释放成员(指针)所占有的存储空间
Ø没有用户定义析构函数时,系统提供缺省版本的析构函数
Ø析构函数名为:~类名
Ø析构函数没有参数,也没有返回类型,也不能重载,因此在一个类中只能有一个析构函数;
注意:
- 如果类中没有定义构造函数,系统将自动生成一个默认形式的构造函数,用于创建对象。
- 默认构造函数形式: 类名::类名(){} 即默认的构造函数是一个空函数。
- 系统自动生成的默认析构函数形式如下:类名::~类名(){}
- 一般情况下,可以不定义析构函数。但如果类的数据成员中包含指针变量是从堆上进行存储空间分配的话,需要在析构函数中进行存储空间的回收
class Student
{
public:
Student(int n, char * a_name, char s)
{
num=n; name=new char[strlen(a_name)+1]; //学习这种表达方式;name是新申请的动态空间;
strcpy(name,a_name); sex=s;
cout<<"Constructor called."<<endl;
}
~Student()
{
cout<<"Destructor called."<<endl;
delete[] name; //释放一组首字符为name的空间,如果是delete name,则只释放第一个空间;
}
void display()
{cout<<name<<endl;cout<<num<<endl;cout<<sex<<endl; }
private:
int num; char *name; char sex;
};
(3)利用构造函数创建对象的两种方法:
- 利用构造函数直接创建对象:类名 对象名(实参表);
- 通过指针和new实现:类名 *指针变量=new 类名(实参表);
例如: Date *date1=new Date(1998,4,28); 就创建了对象(*date1)。 注意:如果对象是由new运算符动态创建的,delete运算会自动调用析构函数。eg: delete date1; //调用析构函数;
new动态创建的对象如果不用delete释放,那么即使建立对象的函数执行结束,系统也不会调用析构函数,这样会导致内存泄漏。
(4)构造函数初始化成员的两种方法:
- 使用构造函数的函数体进行初始化
- 使用构造函数的初始化列表进行初始化
a.
class Date
{
int d, m, y;
public:
Date(int dd, int mm, int yy) //使用构造函数的函数体进行初始化;
{
d=dd;
m=mm;
y=yy;
}
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)
{ }
注意:必须使用参数初始化列表对数据成员进行初始化的几种情况
1.数据成员为常量
2.数据成员为引用类型
3.数据成员为没有无参构造函数的类的对象
举例:
#include <iostream>
using namespace std;
class A
{
public:
A(int i):x(i),rx(x),pi(3.14)
{}
void display()
{cout<<"x="<<x<<"rx="<<rx<<"pi="<<pi<<endl;}
private:
int x,℞ //数据成员为引用类型;
const float pi; //数据成员为常量;
};
int main()
{
A aa(10);
aa.display();
return 0;
} //编译结果为 x=10 rx=10 pi=3.14
#include<iostream>
using namespace std ;
class A
{ public :
A ( int x ) : a ( x ) { }
int a ;
} ;
class B
{ public :
B( int x, int y ) : aa( x ), b( y ) { }
void out()
{ cout << "aa = " << aa.a << endl << "b = " << b << endl ; }
private :
int b ;
A aa ; //数据成员为没有无参构造函数的类的对象;
} ;
int main ()
{ B objB ( 3, 5 ) ;
objB . out ( ) ;
} //编译结果为 aa = 3 b = 5
- 类成员的初始化的顺序:
按照数据成员在类中的声明顺序进行初始化,与初始化成员列表中出现的顺序无关
#include <iostream>
using namespace std;
class CMyClass
{
public:
CMyClass(int x, int y):m_y(x),m_x(m_y)
{
cout<<"m_x="<<m_x<<endl;
cout<<"m_y="<<m_y<<endl;
}
private:
int m_x,m_y;
}; //按照数据成员在类中的声明顺序进行初始化,所以应先对m_x初始化,再对m_y初始化;
int main()
{
CMyClass mc(15,10);
return 0;
}
解释:先对m_x初始化,但是绑定的是m_y,m_y没有确定的值所以m_x的值为随机数;后对m_y初始化,绑定的是x,x的值为15,所以m_y的值为15;
所以编译结果为 m_x=44 所以此程序中变量y没有发挥作用;
m_y=15
- 构造函数的重载:
注意:构造函数一般被定义为公有成员
class Box
{
public:
Box();
Box(int ,int , int);
private:
int height,width, length;
};
Box::Box()
{ height=10; width=10; length=10;}
Box::Box(int h, int w,int l):height(h),width(w),length(l)
{}
五:this指针
- 类的成员函数可以用this指针区分不同的实例对象的数据成员
- 一个对象的this指针并不是对象本身的一部分,不会影响sizeof(对象)的结果。
需要显式引用this指针的三种情况
(1)在类的非静态成员函数中返回类对象本身或对象的引用的时候,直接使用return *this,返回本对象的地址时,return this。
(2)当参数与成员变量名相同时,如this->x = x,不能写成x = x(后面的x是参数)。
(3)避免对同一对象进行赋值操作,判断两个对象是否相同时,使用this指针。
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; } //重要;在add_age函数中返回person对象
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
避免对同一对象进行赋值操作
#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;
} //编译结果:x.X = 5 x.Y = 4 y.X = 5 y.Y = 4
六.复制构造函数:复制构造函数用一个已有同类对象创建新对象进行数据初始化
- C++为类提供默认版本的复制构造函数,程序员可以定义用户版本的复制构造函数
- 语法形式 : 类名::类名(const 类名 & 引用名 , …);(const保护实参对象只读)
复制构造函数的特点:
1.复制构造函数名与类名相同,并且也没有返回值类型。
2.复制构造函数可写在类中,也可以写在类外。
3.复制构造函数要求有一个类类型的引用参数。
4.如果没有显式定义复制构造函数,系统自动生成一个默认形式的复制构造函数。
复制构造函数的调用:
以下三种情况下由编译系统自动调用:
1.声明语句中用类的一个已知对象初始化该类的另一个对象时。
2.当对象作为一个函数实参传递给函数的形参时,需要将实参对象去初始化形参对象时,需要调用复制构造函数。
3.当对象是函数的返回值时,由于需要生成一个临时对象作为函数返回结果,系统需要将临时对象的值初始化另一个对象,需要调用复制构造函数。
浅复制与深复制:
(1)关于浅复制:
●在用一个对象初始化另一个对象时,只复制了数据成员,而没有复制资源,使两个对象同时指向了同一资源的复制方式称为浅复制。即:对于复杂类型的数据成员只复制了存储地址而没有复制存储内容,当删除了复制的存储地址所指向的那个空间的内容时自然也就不会在现有对象里保留之前的内容,只会保留下一些基本数据。
●默认复制构造函数所进行的是简单数据复制,即浅复制
class Person
{
public:
Person(char* name1,int a,double s);
void display(){cout<<name<<"\t"<<age<<"\t"<<salary<<endl;}
~Person(){delete name;}
private:
char* name;
int age;
double salary;
};
Person::Person(char* name1,int a,double s)
{
name=new char[strlen(name1)+1];
strcpy(name,name1);
age=a;
salary=s;
}
int main()
{
Person *P1=new Person("WangWei ",8,3880); //调用构造函数创建对象P1
P1->display();
Person P2(*P1); //调用复制构造函数,用P1的数据初始化对象P2
delete P1;
P2.display();
return 0;
}
编译结果:WangWei 8 3880
8 3880
关于深复制:
●通过一个对象初始化另一个对象时,不仅复制了数据成员,也复制了资源的复制方式称为深复制。
●自定义复制构造函数所进行的复制是浅复制。
1.深复制构造函数必须显式定义
2.深复制构造函数的特点
①定义:类名::类名([const]类名 &对象名);
②成员变量的处理:对复杂类型的成员变量,使用new操作符进行空间的申请,然后进行相关的复制操作
Person::Person(const Person& P0) //复制构造函数的实现
{
name=new char[strlen(P0.name)+1];
strcpy(name,P0.name);
age=P0.age;
salary=P0.salary;
cout<<"ff"<<endl;
}
运行结果: