类和对象
类的构成
语法:
class 类名
{
private :
私有成员函数或数据成员
public :
公有成员函数或数据成员
protected :
被保护成员函数或数据成员
};//注意这里的分号;
//类成员函数的定义可以直接置于类定义中的花括号内。此时,成员函数将被缺省为按内联方式处理
类定义常见的两种形式:
类定义形式1:类界面和类实现分开。类实现在类外描述
类外的成员函数定义,我们一般使用如下格式:
返回值 类名::函数名(参数列表)
{
函数体
}
举例:
class CPerson
{
private:
char name[20];
int age;
char sex;
public:
void setname(char *nameval); //set
void setage(int ageval);
int getage(); //get
}
void CPerson::setname(char *nameval);
{
strcpy(name, nameval);
}
int CPerson::getage() //只有通过方法才能得到私有成员的值
{
return age;
}
char CPerson ::getsex()
{
return sex
}
类定义形式2:类界面和类实现全在类定义中完成
举例:
class CPerson
{
private:
char name[20];
int age;
char sex;
public:
void setname(char* nameval)//设置属性,在定义类时定义函数体
{
strcpy(name, nameval);
}
…
}
两者的区别:当类界面与类实现均在类定义中完成时,其成
员函数将默认为内联函数。
一般:当成员函数比较简单时应采用形式2;当成员函数较大时应采用形式1。
对象的概念和定义
概念:数据类型为类的变量称为类实例、类对象或对象实例。类对象和一般变量一样,在定义时分配内存。为每个类对象分配的内存中存放着每个类对象的数据成员的值。
类对象定义格式: 类名 类对象名列表
例:CPerson p1,p2;
访问
类成员的访问权限
公有成员:public成员,一般是成员函数,用于定义类的外部接口,在程序中的任何部分都可以访问。
私有成员:private成员,一般是数据成员,只能被类自身的成员函数访问。类成员在默认情况下是私有的。
保护成员:protected成员,可以被该类的成员函数、以及该类的直接或间接子类的成员函数所访问。
对象成员访问格式
类外只能访问公有数据成员,可通过公有函数接口访问私有数据成员。
对象名
- 对象名.数据成员名 p1.name
- 对象名.成员函数名(参数表) p1.setname(name); p1.getname();
对象指针
-
对象指针名->数据成员名 p->name;
-
对象指针名->成员函数名(参数表) p->setname(name); p->getname();
-
*(对象指针名).数据成员名 (*p).name;
-
*(对象指针名).成员函数名(参数表) (*p).setname(name); (*p).getname();
例:
CPerson p1, p2;
CPerson* p = &p2; //同结构体访问成员
p1.set("wangwu", 19, 'm');
p->setname("limin");
p->setage(18);
p->setsex('F');
p1
name:char* =wangwu
age :int =19
sex :char =M
p1
name:char* =limin
age :int =18
sex :char =F
类内访问成员的方法
-
访问自己的成员。一个类的成员函数可以访问该类所以成员。
格式:成员名。
-
访问其它类对象成员。只能访问公有成员
格式:对象名.成员名 或 对象指针->成员名
-
访问全局变量或函数
格式: ::变量名或函数名
this指针
定义:this指针指向用来调用成员函数的对象。 this本质就是指向本对象的一个指针。
this指向调用对象。
要引用整个对象,可以用*this
构造函数
定义
该类对象被创建的时候,编译系统对象分配内存空间,并自动调用该构造函数,由构造函数完成成员的初始化工作,故:构造函数的作用:初始化对象的数据成员
分类
● 无参构造函数
● 缺省构造函数
● 带默认值的构造函数
● 有参(无默认值)的构造函数
● 复制构造函数(拷贝构造函数)
● 一种特殊的构造函数,当对象之间复制时会自动调用拷贝构造函数
● 若类中没有显示定义拷贝构造函数,则系统会自动生成默认拷贝构造函数
!!!!注意:如果自己显示定义了一个构造函数,则不会再调用系统的构造函数,!!!!即默认的构造函数失效!!!!!
!!!!需要再自定义无参构造函数!!!!!
#include <iostream>
using namespace std;
class Student
{
private:
double c_x;
double c_y;
};
public:
// 无参构造函数
// 如果创建一个类你没有写任何构造函数,则系统自动生成默认的构造函数,函数为空,什么都不干
// !!!!注意:如果自己显示定义了一个构造函数,则不会再调用系统的构造函数,!!!!即默认的构造函数失效!!!!!
// !!!!需要再自定义无参构造函数!!!!!
Student()
{
c_x = 0;
c_y = 0;
}
//缺省构造函数
Student()=default;
// 一般构造函数
Student(double x, double y):c_x(x), c_y(y){} //列表初始化,仅构造函数可这样写,只有可在函数内用“=”的赋值才可以
// 一般构造函数可以有多个,创建对象时根据传入的参数不同调用不同的构造函数
Student(const Student& c)
{
// 复制对象c中的数据成员
c_x = c.c_x;
c_y = c.c_y;
}
double get_x()
{
return c_x;
}
double get_y()
{
return c_y;
}
int main()
{
// 调用无参构造函数,c1 = 0,c2 = 0
Student c1, c2;
// 调用一般构造函数,调用显示定义构造函数
Student c3(1.0, 2.0);
Student c5(c2);
Student c4 = c2; // 调用浅拷贝函数,参数为c2
cout<<"c1 = "<<"("<<c1.get_x()<<", "<<c1.get_y()<<")"<<endl
<<"c2 = "<<"("<<c2.get_x()<<", "<<c2.get_y()<<")"<<endl
<<"c4 = "<<"("<<c4.get_x()<<", "<<c4.get_y()<<")"<<endl
<<"c5 = "<<"("<<c5.get_x()<<", "<<c5.get_y()<<")"<<endl;
return 0;
}
c1 = (1, 2)
c2 = (0, 0)
c4 = (0, 0)
c5 = (0, 0)
请按任意键继续. . .
析造函数
定义:
简单的说就是对象的初始化和销毁,初始化对应析造函数:在创建一个对象最开始要做的事情:如清0,赋初值,分配堆栈等等。而析造函数则是:在销毁对象时:如释放内存,销毁数据等等操作。
如下面的例子:进入func()函数;创建了p1,于是打印了“初始化ok”。 执行完了后退出fuc()函数,于是打印了“退出”。这就是对象p1的创建于销毁。而在main中只只创建了p0,于是只打印了"初始化ok"
#include "iostream"
using namespace std;
class Person //类
{
public:
Person() // 构造函数
{
cout << "初始化OK" << endl;
};
~Person() // 析构函数--- ~类名(){ }
{
cout << "退出" << endl;
};
};
void func()
{
Person p1;
}
int main()
{
func();
Person po;
system("pause");
return 0;
}
构造函数与析构函数执行关系
一般情况下,调用析构函数的次序正好与调用构造函数的次序相反: 最先被调用的构造函数,其对应的(同一对象中的)析构函数最后被调用,而最后被调用的构造函数,其对应的析构函数最先被调用。
拷贝构造函数
● 拷贝构造函数是一种特殊的构造函数,具有单个形参,该形参(常用const修饰)是对该类型的引用。当定义一个新对
象并用同一类型的对象都它进行初始化时,将显示使用拷贝构造函数,当该类型的对象传递给函数返回该类型的对象时,
将隐调用拷贝构造函数
● 当类中有一个数据成员是指针时,或者有成员表示在构造函数中分配的其他资源,必须显示定义拷贝构造函数
● 构造函数的使用情况
●一个对象以值传递的方式传入函数体
●一个对象以值传递的方式从函数体返回
●一个对象需要通过另一个对象进行初始化
#include <iostream>
using namespace std;
class Test
{
private:
int t_a;
};
public:
// 构造函数
Test(int a):t_a(a){
cout<<"creat: "<<t_a<<endl;
}
// 拷贝构造函数
Test(const Test& T)
{
t_a = T.t_a;
cout<<"copy"<<endl;
}
// 析构函数
~Test()
{
cout<<"delete: "<<t_a<<endl;
}
// 显示函数
void show()
{
cout<<t_a<<endl;
}
// 全局函数,传入的是对象
void fun(Test C)
{
cout<<"test"<<endl;
}
int main()
{
Test t(1);
// 函数中传入对象
fun(t);
return 0;
}
creat: 1
copy
test
delete: 1
delete: 1
请按任意键继续. . .
浅拷贝与深拷贝
●浅拷贝
所谓浅拷贝,指的是在对象复制时,只对对象中的数据成员进行简单的赋值,默认拷贝构造函数执行的也是浅拷
贝。也就是增加了一个指针,指向原来已经存在的内存。 正常情况下,“浅拷贝”已经能很好的工作,但是一旦对
象存在动态成员(或指针变量成员),浅拷贝就会出问题。让我们考虑下面一段代码:
#include <iostream>
#include <assert.h>
using namespace std;
class Test
{
private:
int x;
int y;
int* p;
public:
Test(){
p = new int(10);
}
~Test(){
assert(p != NULL); // assert()作用是如果他的条件返回错误,则终止程序执行
delete p;
}
};
int main()
{
Test t1;
Test t2(t1); // 调用默认拷贝构造函数
return 0;
}
上述程序崩溃。在使用t1复制t2时,进行的是浅拷贝,只是将成员的值进行赋值。此时,t1.p = t2.p, 即两个指针指向了堆里的同一个空间。这样,析构函数会被调用两次,这就是错误出现的原因。此问题的解决方法是“深拷贝”。
●深拷贝
深拷贝就是对于对象中的动态成员,并不只是简单的赋值,而是重新分配空间,即资源重新分配。上述代码处理如下:
#include <iostream>
#include <assert.h>
using namespace std;
class Test
{
private:
int x;
int y;
int* p;
public:
Test(){
x = 0;
y = 0;
p = new int(10);
}
Test(const Test& t)
{
x = t.x;
y = t.y;
p = new int(10);//注意
*p = *(t.p);/注意
}
~Test(){
assert(p != NULL); // assert()作用是如果他的条件返回错误,则终止程序执行
delete p;
}
int get_x(){return x;}
int get_y(){return y;}
};
int main()
{
Test t1;
Test t2(t1); // 调用默认拷贝构造函数
cout<<"("<<t1.get_x()<<", "<<t1.get_y()<<")"<<endl
<<"("<<t2.get_x()<<", "<<t2.get_y()<<")"<<endl;
return 0;
}
(0, 0)
(0, 0)
请按任意键继续. . .
此时t1与t2的p各自指向一段内存空间,但他们指向的内容相同,这就是“深拷贝”。
复合类
复合类也称为组合类,是指将一个类的对象作为另一个类的成员变量。
例如:CPerson有注册日期、出生日期等属性。
注册日期、出生日期:年月日
时钟有时针、分针、秒针组成。
时针、分针、秒针:循环计数器
#include<iostream>
using namespace std;
class Date{
private:
int y,m,d;
public:
Date();
Date(int y,int m,int d);
void setdate(int y,int m,int d);
};
Date::Date(int y1,int m1,int d1):y(y1),m(m1),d(d1){}
void Date::setdate(int y1,int m1,int d1){
y=y1,m=m1,d=d1;
}
class CPerson{
private:
Date birth;
public:
CPerson();
CPerson(int y,int m,int d);
};
CPerson::CPerson(int y,int m,int d){
birth.setdate(y,m,d);
}
//或者用构造函数:
//CPerson::CPerson(int y,int m,int d):birth(y,m,d) {}
对象数组
对象数组的定义:
所谓对象数组,指每一个数组元素都是对象的数组,即若一个类有若干个对象,我们把这一系列的对象用一个数组来存放。对象数组的元素是对象,不仅具有数据成员,而且还有函数成员。
定义一个一维数组的格式如下:
类名 数组名[下标表达式]
与基本数据类型的数组一样,在使用对象数组时也只能访问单个数组元素,其一般形式为:
数组名[下标].成员名
在建立数组时,同样要调用构造函数。有几个数组元素就要调用几次构造函数。
静态数组
一个参数:
#include<iostream>
using namespace std;
class exam{
private:
int x;
public:
exam(int n){ //只有1个参数的构造函数
x=n;
}
int get_x(){
return x;
}
};
int main(){
exam ob1[4]={11,22,33,44}; //用只有1个参数的构造函数给对象数组赋值
for(int i=0;i<4;i++)
cout<<ob1[i].get_x()<<" ";
return 0;
}
多个参数:
#include<iostream>
#include<cmath>
using namespace std;
class Complex{
public:
Complex(double r=0.0,double i=0.0):real(r),imag(i){
}
~Complex(){
cout<<"Destructor called."<<endl;
}
double abscomplex(){
double t;
t=real*real+imag*imag;
return sqrt(t);
}
private:
double real;
double imag;
};
int main(){
Complex com[3]={ //定义对象数组
Complex(1.1,2.2), //调用构造函数,为第1个对象数组元素提供实参1.1和2.2
Complex(3.3,4.4), //调用构造函数,为第2个对象数组元素提供实参3.3和4.4
Complex(5.5,6.6) //调用构造函数,为第3个对象数组元素提供实参5.5和6.6
};
cout<<"复数1的绝对值是:"<<com[0].abscomplex()<<endl;
cout<<"复数1的绝对值是:"<<com[1].abscomplex()<<endl;
cout<<"复数1的绝对值是:"<<com[2].abscomplex()<<endl;
return 0;
}
动态对象数组
例:
第一种
int n;
cin>>n;
Point*pp=new Point[n];
for(int i=0;i<n;i++){
(pp+i)->setXY();//或pp[i].setXY();
}
第二种
cpoint *p = (cpoint *)operator new[](3 * sizeof(cpoint));
for (int i = 0; i < 3; i++)
{
cin >> x >> y;
new(&p[i]) cpoint(x,y); // 有参构造函数构造
}
for(int i=0; i<3; i++)
p[i].~cpoint(); //析构对象
operator delete(p); //释放空间