类是对象的实现,面对对象中的类是抽象概念,而类是程序开始过程中定义一个对象,用类定义对象可以是现实生活中的真实对象,也可以是从现实生活中抽象的对象。
1.1 C++类
1.1.1 类的概述
面向对象中的对象需要通过定义类来声明,对象是一种形象的说法,在编写代码过程中则是通过定义一个类来实现对象的功能。
C++类不同于汉语中的类、分类、类型,它是一种特殊的概念,可以是对一个统一事物的概述,也可以是对某一个层次结构的不同结点。例如可以将所有的电脑包装称computer类,将所有的动物写成animal类等等,有现实中的很多东西都有很多相似的地方,这些相似的事物就可以写成一个类,来减少我们的工作量。
类是一个新的数据类型,它与结构体有一些相似,结构体是不同数据类型的一个集合体而类在结构体的基础之上多了可以操作数据类型的行为,即为函数。
1.1.2 类的声明与定义
在1.1.1的小节之中对类的概念进行了一点的说明,可以看出类是由用户自主研发的一个类型。如果程序之中要用到类这种类型,就必须自己根据自己所需要的东西来设计属于自己的类并且进行类的声明,或者使用别人设计好的类。下面我们进入正题,如何去设计一个类。
类的声明格式如下:
class 类名标识符
{
public:
数据成员的声明;
成员函数的声明;
private:
数据成员的声明;
成员函数的声明;
protected:
数据成员的声明;
成员函数的声明;
};
类的声明格式的说明如下:
class 是定义类结构体的关键字,大括号内被称为类体或类空间。
类名标识符指定的就是类名,类名就是一个新的数据类型,通过类名我们可以声明对象。
类的成员有函数和数据两种类型。
大括号内是定义和声明类成员的地方,关键字public、private、protected是类成员访问的修饰符。
类中的数据成员的类型可以是任意的,包含整型、浮点型、字符型、数组、指针和引用(&)等,也可以是对象。另一个类的对象可以作为该类的成员,但是自身类的对象不可以作为自己的类成员,而自身类的指针和医用又可以作为自己类的成员。
注意:定义类结构体和定义结构体时大括号后要有分号。
如下,我们声明一个球队的信息类:
class Cbasket
{
/*数据成员*/
int num;
char name[128];
char clothes[28];
/*成员函数*/
int setnum(int m_num);
int getnum();
int setname(char m_name[128]);
char* getname();
int setclothes(char m_clothes[28]);
char* getclothes();
};
在代码中,class关键字是用来定义类这种结构的,Cbasket是定义的球队信息的类名称,在大括号中包含了3个数据成员分别表示球员的号码、姓名、衣服尺寸(Cbasket的属性),包含了6个成员函数来表示Cbasket类的行为。
1.1.3 类的实现
1.1.2小节只是在Cbasket类中声明了类的成员。然而要使用这个类中的方法,即成员函数,就是要对其进行定义其具体的操作。下来我们来看看是如何定义类中的方法。
第一种方法:将类的成员函数都定义在类体之内。以下的代码都保存在basket.h头文件内,类的成员函数都定义在类体之内。
#include<iostream>
#include<string.h>
using namespace std;
class Cbasket
{
public:
//数据成员
int m_num;
char m_name[128];
char m_clothes[28];
//成员函数
int setnum(int num)
{
m_num=num;
return 0; //执行成功返回0
}
int getnum()
{
return m_num;
}
int setname(char name[128])
{
strcpy(m_name,name);
return 0; //执行成功返回0
}
char* getname()
{
return m_name;
}
int setclothes(char clothes[28])
{
strcpy(m_clothes,clothes);
return 0; //执行成功返回0
}
char* getclothes()
{
return m_clothes;
}
};
第二种方法:将类体内的成员函数的实现放在类体之外,但如果类成员定义在类体外的时候,需要用到域运算符“::”,放在类体内和类体之外的效果是一样的,只是具体的放置位置不同。具体的不同可以官观察下面的代码和上一块的代码有何不同。
#include<iostream>
#include<string.h>
using namespace std;
class Cbasket
{
public:
//数据成员
int m_num;
char m_name[128];
char m_clothes[28];
//成员函数
int setnum(int num);
int getnum();
int setname(char name[128]);
char* getname();
int setclothes(char clothes[28]);
char* getclothes();
};
//类成员函数的实现部分
int basket::setnum(int num)
{
m_num=num;
return 0; //执行成功返回0
}
int basket::getnum()
{
return m_num;
}
int basket::setname(char name[128])
{
strcpy(m_name,name);
return 0; //执行成功返回0
}
char* basket::getname()
{
return m_name;
}
int basket::setclothes(char clothes[28])
{
strcpy(m_clothes,clothes);
return 0; //执行成功返回0
}
char* basket::getclothes()
{
return m_clothes;
}
注意如果要将成员函数放在类体之外实现的话需要在类体之中对该函数进行声明,否则将无法使用,并且在外部实现成员函数的时候需要运用到::符号。
关于类的实现有两点说明:
(1) 类的数据成员需要初始化,成员函数还要添加实现代码。类的数据成员不可以在类的声明之中进行初始化。如下初始化是错误的:
class Cperson
{
int num=10; //错误写法不可以在类中进行初始化
char name[128]="小元"; //错误写法不可以在类中进行初始化
.....................................
.....................................
};
(2)空类是C++中最简单的类,其声明方式如下:
class Cperson{ };
空类只是起到占位的作用,需要的时候在定义类成员及实现。
1.1.4 对象的声明
定义一个新类后,既可以通过类名来声明一个对象。声明的形式如下所示:
类名 对象名表
类名是定义好的新类的标识符,对象名表中是一个或多个对象的名称,如果声明的是多个对象就是要用逗号将其分隔开来。
例如 声明一个对象如下:
Cperson p;
声明多个对象如下:
Cperson p1,p2,p3;
声明完对象就要讲解对象的引用了,对象的引用有两种方式,一种是成员引用方式,一种是对象指针方式。
(1)成员引用方式
成员变量引用的表示如下:
对象名.成员名
这里“.”是一个运算符,该运算符的功能是表示对象的成员,
成员函数应用的表示如下:
对象名.成员名(参数表)
例如:
Cperson p;
p.m_index;
(2)对象指针方式
对象声明形式中的对象名表,除了是用逗号运算符分隔的多个对象名外,还可以是对象名数组、对象名指针和引用形式的对象名。
具体的实现如下所示:
Cperson *p;
Cperson p[10];
但要想使用对象的成员,需要用"->" 运算符,他就是表示成员的运算符,与"."运算符的意义相同。"->"用来表示对象指针所指的成员,对象指针就是指向对象的指针,例如:
Cperson *p;
p->m_index;
下面是对象数据成员的两种表示形式:
对象指针名->数据成员
(*对象指针名).数据成员
对函数的应用也是如此。
这两种形式的表示是等价的
使用对象引用成员
在实例中,应用定义的类来定义对象,然后使用该对象引用其中的成员
#include<iostream>
#include<string.h>
using namespace std;
class stu{
public:
int m_num;
char m_name[25];
int m_year;
//函数成员
int getnum();
int setnum(int num);
int getyear();
int setyear(int year);
int setname(char name[25]);
char* getname();
};
int stu::getnum()
{
return m_num;
}
int stu::setnum(int num)
{
m_num=num;
return 0;
}
int stu::setyear(int year)
{
m_year=year;
return 0;
}
int stu::getyear()
{
return m_year;
}
char* stu::getname()
{
return m_name;
}
int stu::setname(char name[25])
{
strcpy(m_name,name);
return 0;
}
int main()
{
stu y;
int i=-1;
int num,year;
cin>>num;
num=y.setnum(num);
cout<<"m_num is"<<y.getnum()<<endl;
cin>>year;
year=y.setyear(year);
cout<<"m_year is"<<y.getyear()<<endl;
char name[]="小元";
i=y.setname(name);
cout<<"m_name is"<<y.getname()<<endl;
return 0;
}
该实例中的运行的代码如下所示:
在实例中我们可以看到我们给其定义了一个y对象,然后引用y对象来引用类中的成员函数以及数据,分别引用y来引用类中的m_num、m_name[25]、m_year、 getnum()、 setnum(int num)、getyear()、 int setname(char name[25])、char* getname()等等类中的数据类型。
1.2 构造函数
1.2.1 构造函数概述
在类的实例进入作用域时,也就是建立一个对象时,构造函数就会被程序所调用,那么构造函数的作用就是给对象进行初始化的操作,例如对数据成员进行赋值操作、设置类的属性等,而这些操作刚好放在构造函数中完成。
类的构造函数是可以有参数的,可以存在也可以不存在,具体的实现如下段代码:
class person
{
public:
int m_number;
char m_name[128];
person() //无参构造函数对数据进行初始化
{
m_number=10;
cin>>m_name;
}
person(int num,char name) //有参构造函数
{
m_number=num;
strcpy(m_name,name);
}
};
构造函数的使用代码如下:
#include<iostream>
#include<string.h>
using namespace std;
class person
{
public:
int m_number;
char m_name[128];
person() //无参构造函数对数据进行初始化
{
cout<<"请输入号码:";
cin>>m_number;
cout<<"请输入姓名:";
cin>>m_name;
}
person(int num,char name[128]) //有参构造函数
{
m_number=num;
strcpy(m_name,name);
}
int getnum()
{
return m_number;
}
char* getname()
{
return m_name;
}
};
int main()
{
person p;
cout<<"num is "<<p.getnum()<<endl;
cout<<"name is "<<p.getname()<<endl;
person t(30,"小侯");
cout<<"num is "<<t.getnum()<<endl;
cout<<"name is "<<t.getname()<<endl;
return 0;
}
具体运行之后的结果为:
在程序之中声明了两个对象p和t,p使用默认构造函数进行初始化,t使用带参数的构造函数进行初始化,所以在调用同一个类的成员函数时所运行的结果不同。
1.2.2 复制构造函数
在开发程序中可能需要保存对象的副本,以便在后面执行的过程中恢复对象的状态。该实现恢复对象状态的可以使用复制构造函数来对一个已经初始化的对象来新生成一个一模一样的对象,复制构造函数时编译器调用来完成一些基于同一类的其他对象的构建以及初始化,其唯一的形参必须是引用。
复制构造函数的应用
#include<iostream>
#include<string.h>
using namespace std;
class phone
{
public:
char speak[128];
phone(char spoken[128])
{
strcpy(speak,spoken);
}
phone(phone & copyp1) //复制构造函数
{
strcpy(speak,copyp1.speak);
}
char *speaker()
{
return speak;
}
int fix()
{
char name[28];
cin>>name;
strcpy(speak,name);
cout<<"fixname is "<<speak<<endl;
return 0;
}
};
int main()
{
phone p1("李牧歌");
phone p2(p1); //将对象p1的内容复制给p2
cout <<"name is "<<p1.speaker()<<endl;
cout<<"name is "<<p2.speaker()<<endl;
p1.fix();
return 0;
}
运行程序先用带参构造函数声明p1,然后通过复制构造函数声明p2,因为p1已经是初始化完成的类对象,可以作为复制构造函数的参数,通过输出结果可以看出,两个对象是相同的。
运行的结果如下:
1.3 析构函数
构造函数和析构函数是类体定义中比较特殊的两个成员函数,他们两个都没有返回值。构造函数名标识符和类名标识符相同,析构函数名表示符就是在类名标识符前加上“~”符号。
构造函数只要是在建立对象后立即使用,给对象中的一些数据成员赋值,主要目的就是初始化对象。析构函数主要功能是用来释放一个对象的,在对象删除之前,析构函数和构造函数刚刚好是相反的样子。
析构函数的使用
#include<iostream>
#include<string.h>
using namespace std;
class stu
{
public:
int m_num;
stu();
~stu(); //析构函数
int get();
char* m_pass;
};
stu::stu()
{
cout<<"请输入学生的学号:"<<endl;
cin>>m_num;
m_pass=new char[128];
}
stu::~stu()
{
delete[] m_pass; //删除m_pass的内容
m_num=0;
}
int stu::get()
{
cout<<"学号是:"<<m_num<<endl;
strcpy(m_pass,"恭喜你通过了!");
cout<<m_pass<<endl;
return 0;
}
int main()
{
stu s;
s.get();
return 0;
}
程序运行如图所示:
程序在构造函数之中使用new为成员m_pass分配空间,在析构函数之中使用delete释放由new分配的空间。成员m_pass为字符指针,在get函数之中输出字符指针所指向的内容。
使用析构函数所要注意的事项:
(1)一个类中只能定义一个析构函数。
(2)析构函数不可以重载。
(3)构造函数和析构函数不能使用return语句返回值,不用加上关键字void。
构造函数和析构函数的调用环境
(1)自动变量的作用域时某个模块,此模块被激活时,自动变量调用构造函数,当退出此模块时会调用析构函数。
(2)全局变量在进入main()函数之前就会调用构造函数,在程序终止时会调用析构函数。
(3)动态分配的对象在使用nre为对象分配内存时会调用构造函数;使用delete删除对象时会调用析构函数。
(4)临时变量是为支持计算由编译器自动产生的。临时变量的生存期的开始和结尾会调用构造函数和析构函数。
1.4 类成员
1.4.1 访问类成员
类的三大特点之中包含“封装性”,封装在类里面的数据可以设置称为对外可见或者是不可见。通过关键字public、private、protected可以设置类中的数据成员是对外可见还是不可见,也就是其他类是否可以访问该数据成员。
关键字public、private、protected说明类成员是共有的、私有的,还是受保护的。这3个关键字将类划分为3个区域,在public区域的类成员可以在类的作用域之外被访问,而private区域和protected区域只能在类的作用区域被访问。
private区域要想被外部类访问是需要用到友元,protected区域可以被衍生类所访问。
如果在类中没有加任何的关键字,系统默认状态类成员都在private区域中。
具体的设置情况如下所示:
class basket
{
private:
int num;
public:
int getnum() {return num};
int setnum(){num=15 retrun 0;};
};
此设置情况为num是不可以对外访问到的,但是可以通过共有的函数让外界访问到num的值以及对num进行具体的操作。
有了不同的区域,开发人员可以根据需求来进行封装,将不想让其他类访问和调用的类成员定义在private区域和protected区域,这就保证了类成员的隐蔽性。
每一个区域的终止就是等到下一个区域的出现。
1.4.2 内联成员函数
定义函数时,可以使用inline关键字将函数定义为内敛函数。在定义类的成员函数时,也可以使用inline关键字将成员函数定义为内敛成员函数。其实,对于成员函数来说,如果定义是在类体中。即使没有使用inline关键字,该成员函数仍然是内敛成员函数。例如
class basket
{
private:
int num;
private:
inline int getnum()const; //定义一个内联成员函数
int setnum();
};
int basket::getnum()const
{
return num;
}
int basket::setnum()
{
cin>>num;
}
class basket
{
private:
int num;
private:
int getnum()const; //定义一个成员函数
int setnum();
};
inline int basket::getnum()const //函数为内联成员函数
{
return num;
}
int basket::setnum()
{
cin>>num;
}
以上代码分别是在类体内定义一个内联成员函数,一个是在类体之外定义一个内联成员函数。
程序中的代码演示了应在何处使用inline关键字将其设置称为内联函数。对于内联函数来说,程序会在函数调用的地方直接插入该函数的代码,如果函数体语句较多则会导致程序代码膨胀,如果将类的析构函数定义为内联函数,则可能会导致潜在的代码膨胀。
类联函数需要在注意的地方:
(1)内联成员函数不允许使用循环和开关语句。
(2)内联函数的定义必须出现在其第一次调用之前。
内联函数它与不同函数不同的是,函数在遇到调用时程序会跳入函数体内执行,执行完成之后则返回调用它的语句,而内联函数则是将调用的表达式用内联函数体来替换,类似于重新再写一遍代码在调用语句之处。
1.4.3 静态类成员
本小节之前定义的类成员,都是通过对象来访问的,不能通过类名直接来访问。如果将类成员定义为静态类成员,则允许使用类名直接访问。静态类成员是在类成员定义之前使用static关键字标识。例如
class Cbook
{
public:
static int m_price;
};
在定义静态数据成员时,通常需要在类体外部对静态数据成员进行初始化。例如
int Cbook::m_price=10;
对于静态数据成员来说,不仅可以通过对象访问,还可以通过直接使用类名来访问。例如
int main()
{
Cbook book; //定义一个Cbook类对象book
cout<<Cbook::m_price<<endl; //通过类名访问静态成员
cout<<book.m_price<<endl; //通过对象访问静态成员
return 0;
}
在一个类中,静态数据成员是被所有的类对象所共享的,其中一个对象的静态成员改变则其余的静态成员也会跟着改变。
对于静态数据成员,还需要注意以下几点。
静态数据成员可以是当前类的类型,而其他数据成员只能是当前类的指针或引用类型。
class Cbook
{
public:
static int m_price;
Cbook m_book //非法的定义,不允许在类中定义所属类的对象
static Cbook m_Vcbook //正确静态数据成员允许定义所属类的对象
Cbook *m_pbook; //正确可以是指针或引用的类型
};
静态数据成员可以作为成员函数的默认参数(普通成员函数不可作为默认参数不可在参数处赋值)
class Cbook
{
public:
static int m_price; //定义一个静态数据成员
int m_num;
void output(int data=m_price) //定义一个函数,将静态数据作为默认参数
{
cout<<data<<endl;
}
void ouputnum(int num=m_num) //错误的定义,类的普通成员不能作为默认参数
{
cout<<num<<endl;
}
};
类的静态成员函数只能访问类的静态数据成员,而不能访问普通的数据成员。
class Cbook
{
public:
static int m_price; //定义一个静态数据成员
int m_num;
static void output() //定义一个静态成员函数
{
cout<<m_price<<endl; //可以访问静态数据成员
cout<<m_num<<endl; //错误,静态函数只能访问静态成员
}
};
此外,静态成员函数不能定义为const函数,即静态成员函数末尾不能使用const关键字。例如,下面的静态成员函数是非法的。
static void ouput()const;
在定义静态数据成员函数时,如果有函数的实现代码在类体的外部则不需要在实现部分加入static关键字否则会出现非法行为。
这篇博客就先到这里结束了,就如上图所示:人生如逆旅,我亦是行人。不要因为别跟你做的东西不同而感到焦虑,因为在人生这个道路之上和你同行的人少之又少,希望大家可以在各自的学习领域之中继续探索。