【c++基础】8.类和对象——类的实现1

来源:互联网 发布:linux剪辑软件 编辑:程序博客网 时间:2024/06/07 07:18

类是对象的抽象,对象是类的实例,每一个对象都对应现实中的一个事物,事物有具有属性和行为。类的设计过程就是对事物的属性和行为进行抽象的过程,将事物的属性抽象为成员数据,行为抽象为成员函数,但是在类的设计过程中也有一些基本的规则,将在后面渐渐提到。

下面以一个简单的字符串类作为例子进行介绍类的实现。

struct String{//成员数据...//成员函数...}

1.类的成员数据

在考虑类的成员数据时,实际上就是考虑你要抽象的那个实体有些什么属性,比如字符串,它最基本的要有字符串值和字符串长度,可能还有其他的属性比如字符串颜色、字体、大小之类的,但是我们在简单的字符串操作中并不关心这些,所以就不必要抽象它,这里我们就简单地只抽象这两个属性吧。
用const char*来表示字符串值,用一个unsigned int型来表示字符串长度,那么类可以是这样的:

struct String{//成员数据char * str;unsigned int length;//成员函数...}

2.类的成员函数

函数成员就是实体的行为,字符串会有些什么行为呢?

①初始化成员数据//初始化成员数据一般在构造函数中进行,这里我们先用简单函数进行初始化
②返回字符串长度
③返回字符串值
④根据字符查找第一个出现该字符的索引
⑤按索引寻找字符串中某个字符
……
我们暂且只关心这些操作,但在实际应用中的类设计时,应该考虑类接口的完备性,也就是尽量将用户可能需要的操作都考虑到。

然后就是用函数来实现这些行为,我们一般将函数声明在类内,定义在类外,也可以直接在类内定义,当在类外定义时,需要在函数名前加上“类名::”来表明该函数来自该类,完善上面那个字符串类:

struct String{//成员数据    const char * str;    unsigned int length;//成员函数    void  assign(const char *s,unsigned int len){//为成员数据赋值,类内定义         str=s;        length=len;    }    unsigned int return_length(){//返回字符串长度,类内定义         return length;    }    const char *return_str(){//返回字符串,类内定义         return str;    }    char find(int index);//通过下标来找字符,类内申明,类外定义     int find(char c);//通过字符找下标,类内申明,类外定义 };char String::find(int index){//类外定义     if(index<0||index>=length)//判断索引合法性         return '\0';     return str[index];}int String::find(char c){//类外定义     for(int i=0;i<length;i++)        if(str[i]==c)            return i;    return -1;//表示没有找到 }

3.类的使用

对上面的字符串类进行实例化和使用:

int main(){    const char *s="this is a string";    String my_str;    my_str.assign(s,16);    cout<<"string:"<<my_str.return_str()<<endl;    cout<<"length:"<<my_str.return_length()<<endl;    cout<<"index 2:"<<my_str.find(2)<<endl;    cout<<"find i:"<<my_str.find('i');    return 0;} 

输出为:

string:this is a string
length:16
index 2:i
find i:2

3.构造函数——定义

类是对象的模板,使用模板构造对象的过程叫实例化
构造函数顾名思义就是用于构造一个该类类型的对象,在实例化对象时进行显式或隐式地调用,一般用于初始化成员数据。构造函数没有返回值,其函数名与类名相同,除此之外和普通函数一样如下:

String(参数列表){//函数体}

每一个类都必须有至少一个构造函数(显然构造函数是可以重载的),当实例化一个对象时,无论以何种方式实例化,都会调用构造函数,比如上例中:

String mg_str;

这一行就是实例化一个String对象。但是我们好像没有在实现类的时候提到有构造函数啊,确实我们没有抽象构造函数,上面五个函数都是普通的成员函数,那为什么还能实例化呢?原因是如果用户没有写构造函数,编译器会自动生成一个默认构造函数(不含参数的构造函数),但这个构造函数什么也不做,只是为了必须存在而存在的。
值得注意的是,如果我们自己写了自己的构造函数,那么编译器就不会生成默认构造函数,所以在写构造函数时最好是实现一个默认构造函数。看下面一个例子:

#include <iostream>#include <string>using namespace std;struct String{//成员数据const char * str;unsigned int length;//成员函数String(const char *s,unsigned int len){//自定义的含参构造函数    str=s;    length=len;}void  assign(const char *s,unsigned int len);//类内申明,类外定义 unsigned int return_length();//类内申明,类外定义 const char *return_str();//类内申明,类外定义 char find(int index);//类内申明,类外定义 int find(char c);//类内申明,类外定义 };int main(){    const char *s="this is a string";    String my_str;    my_str.assign(s,16);    cout<<"string:"<<my_str.return_str()<<endl;    cout<<"length:"<<my_str.return_length()<<endl;    cout<<"index 2:"<<my_str.find(2)<<endl;    cout<<"find i:"<<my_str.find('i');    return 0;} void  String::assign(const char *s,unsigned int len){//类内定义     str=s;    length=len;}unsigned int String::return_length(){//类内定义     return length;}const char *String::return_str(){//类内定义     return str;}char String::find(int index){//类外定义     if(index<0||index>length)//判断索引合法性         return '\0';     return str[index];}int String::find(char c){//类外定义     for(int i=0;i<length;i++)        if(str[i]==c)            return i;    return -1;//表示没有找到 }

本类中第10行定义了一个含参的构造函数,第23行实例化一个对象,看似没问题,但却报错了,

24 9 F:\test\自定义字符串.cpp [Error] no matching function for call to ‘String::String()’

意思是没有与之匹配的构造函数,再回头看,果然,实例化的时候我们没有带参数,但是自定义构造函数时带参数的,所以这就是需要写一个默认构造函数的原因,对上面问题有两种解决办法,第一就是实例化带上参数,第二就是写一个默认构造函数:

//方法一struct String{//成员数据const char * str;unsigned int length;//成员函数String(){}//加上默认构造函数String(const char *s,unsigned int len){    str=s;    length=len;}......
//方法二int main(){    const char *s="this is a string";    String my_str(s,16);//实例化时加上参数     //String my_str=String(s,16);//也可以这么实例化    //my_str.assign(s,16);    cout<<"string:"<<my_str.return_str()<<endl;    cout<<"length:"<<my_str.return_length()<<endl;    cout<<"index 2:"<<my_str.find(2)<<endl;    cout<<"find i:"<<my_str.find('i');    return 0;} 

从这个例子还可以看出,类封装的一个好处,我们一般讲成员函数的申明写在类内,而将其定义写在类外,这样一来,因为类内已经包括了类接口的输入输出,类的用户(也是程序员)只需看类内就可以对该类进行使用,而不需关心类类的具体实现细节,但更多的时候是为了保密而不能让用户看到类的具体实现。

4.构造函数——初始化数据

前面已经说了,构造函数一般是用来进行初始化数据的额,可以在构造函数中使用赋值符号“=”对数据进行初始化,也可以使用初始化列表
对于内置数据类型和指针,引用,两种方法效率相同,但涉及类的组合时(即类的数据成员中有其他类的对象),则使用初始化列表更佳

有的时候则必须用带有初始化列表的构造函数:
1.成员类型是没有默认构造函数的类。若没有提供显示初始化式,则编译器隐式使用成员类型的默认构造函数,若类没有默认构造函数,则编译器尝试使用默认构造函数将会失败,这也是为什么建议自己写一个默认构造函数的原因。
2.const成员或引用类型的成员。因为const对象或引用类型只能初始化,不能对他们赋值。
一个初始化列表和赋值初始化区别的例子:

class B{  public:      B() {          cout << "B:B() is called" << endl;      }      B(int x) {          cout << "B:B(x) is called" << endl;          x = a;      }  private:      int a;  };  class A{  public:      A(int x) {          a = x;          b = B(x);      }      ~A() {      }  private:      int a;      B b;  //此处调用了B的默认构造函数};  int main()    {        A t(1);      system("pause");      return 0;    }   

输出:

B:B() is called
B:B(x) is called

但如果把类A里的构造函数改成

A(int x):a(x),b(B(x)){}  

就输出:

B:B(x) is called减少了一次B的默认构造函数调用。

原创粉丝点击