Loading

C++:类(一)—— 基本概念

一、内联成员函数

如果有的成员函数需要被频繁调用,而且代码比较简单,则这个函数可以定义为内联函数。内联函数的函数体会在编译时被插入到每一个调用它的地方,这样做可以减少调用的开销(函数的调用过程要消耗一些内存资源和运行时间来传递参数和返回值),提高执行效率,但是会增加编译后代码的长度。

 内联函数的声明有隐式声明和显式声明两种。

  (1)隐式声明(将函数体直接放在类内)

 1 class Clock
 2 {
 3 private:
 4     int hour, minute, second;
 5 public:
 6     void SetTime(int h, int m, int s);
 7     void ShowTime()
 8     {
 9         cout << hour << ':' << minute << ':' << second << endl;
10     }
11 };

 

 

 (2)显式声明(将函数体定义在类外,在函数返回类型前面加inline关键字)

inline void Clock::ShowTime()
{
    cout << hour << ':' << minute << ':' << second << endl;
}

 

二、const成员函数

void show() const { } ;    //指该函数为常函数,类的成员变量不能被修改
                           //即常成员函数不能修改调用它的对象的内容。但是函数内非成员变量的值可以修改。
                           
const void show() { };     // 指该函数的返回值为const void

 

常量对象,以及常量对象的引用或指针都只能调用常量成员函数。

 

三、编译器处理类的顺序

编译器处理完类中的全部声明后才会处理成员函数的定义

 

四、使用引用,避免拷贝

拷贝大的类的类型对象或者容器对象效率较低,甚至有的类类型根本就不支持拷贝操作(比如IO类型)。当某种类型不支持拷贝操作是,函数只能通过引用形参访问该类型的对象。

例如,编写一个函数比较两个string对象的长度时,由于string对象可能会非常常,所以应该尽量避免直接它们,这时就应该使用引用形参。又因为比较长度无须改变string对象的内容,所以把形参定义成对常量的引用。

//使用string的引用,避免拷贝
bool isShorter(const string &s1, const string &s2)  //函数无须改变引用形参的值,最好将其声明为常量引用
{
    return s1.size() < s2.size() ;               
}

 

五、默认构造函数

1.默认构造函数按以下规则初始化类的数据成员:

(1) 如果存在类内的初始值,用它来初始化成员。

(2) 否则,默认初始化该成员。即,定义于块作用域内的内置类型变量将不被初始化,其值未定义;定义于块作用域外的全局变量被初始化为0。但如string类型的变量,不论在哪定义,都被默认初始化为空串。

class X
{
    int a;
public:
    void ShowX(){cout << a ;}
    X() = default;
};
int main()
{
   X xx;
   xx.ShowX();    //对象xx中的a成员的值被默认初始化,由于a是在块作用域内定义的,所以此处输出的值未定义

   return 0;
}

 

2.如果类没用显式的定义构造函数,那么编译器就会为我们隐式的定义一个默认构造函数(默认合成的是public的)。但一旦我们定义了一些其他的构造函数,那么除非我们在定义一个默认的构造函数,否则类将没有默认构造函数。可以在参数列表后面写上=default来要求编译器生成构造函数。如果=default出现在类的内部,则生成的默认构造函数是内联的;如果出现在类的外部,则生成的默认构造函数不是内联的。因此,当我们定义了其他的构造函数时,必须同时定义出默认构造函数。

 

六、析构函数

当一个类未定义自己的析构函数时,编译器会为它定义一个合成析构函数。合成析构函数的函数体为空,它不能带任何参数,也没有返回值,只能有一个析构函数,不能重载。

当程序中没有析构函数时,系统会为它定义一个合成析构函数,即:
<类名>::~<类名>(){ },即不执行任何操作。
在一个析构函数中,首先执行函数体,然后销毁成员。成员按初始化顺序逆序销毁。
 
析构函数只会隐式销毁该类对象的所有成员,对于指针成员,创建对象时,只为该指针分配了内存,此时指针指向的对象还不一定知道,所以,对于有指针成员的类,析构函数只会销毁指针成员自身占用的内存,而不会释放该指针指向的对象的内存。因此,对于有指针成员的类,我们需要人为定义一个析构函数来delete 这个成员指针。(对于有指针成员的类,必须定义自己的析构函数,而不应该使用合成析构函数,以免释放不了指针所指对象的内存!)
 1 #include <iostream>
 2 
 3 using namespace std;
 4 
 5 class A 
 6 {
 7 public:
 8     A() = default;
 9     A(int a)
10     {
11         p = new int(a);
12     }
13     ~A() 
14     { 
15         delete p;    //合成的默认析构函数只会销毁该类对象的所有成员,所以此处若无delete p,则只会释放指针p的内存,不会释放p指向的对象(本例中为a)的内存。
16     }
17 private:
18     int* p;
19 };
20 
21 int main()
22 {
23     A aa(3);
24 
25     return 0;
26 
27 }

 

 

七、友元

友元可以访问类中所有成员。(不论成员是public,protected还是private,友元都可访问)

即     类内所有成员相对于友元来说都是public的。

类内友元的声明仅仅指定了访问的权限,而非一个通常意义上的函数声明。如果我们希望类的用户能够调用某个友元函数,那么我们就必须在类内友元声明之外再专门对函数进行一次声明(即在类外再声明一次)。通常把友元的声明与类本身放置在同一个头文件中,类内和类外都要进行一次友元的声明。(在头文件中声明,在源文件中定义)

 1 class student
 2 {
 3     friend void print1(student& stu);
 4     friend int main();   //正确。主函数也可以声明为友元函数
 5 private:
 6     string name;
 7 protected:
 8     int age;
 9 };
10 
11 void print1(student& stu)
12 {
13     cout << stu.name << ' ' << stu.age;  //正确。print1是student类的友元函数,可以访问其所有成员
14 }
15 
16 void print2(student& stu)
17 {
18     cout << stu.name << ' ' << stu.age;  //错误。print2不是student的友元,不可以访问其private与protected成员
19 }

 

 

八、类的声明

直到类被定义之后数据成员才能被声明成这种类类型,只有当类全部完成后类才算被定义,所以一个类的成员类型不能是该类自己。但是,一旦一个类的名字出现后,它就被认为是声明过了(但尚未定义),因此类允许包含指向它自身类型的引用或指针。

class student
{
    string name;
    int age;
    student A;         //错误!类的定义还未全部完成,成员的类型不能是该类自己
    student* next;     //正确。类已经声明过了,该类允许包含指向它自身类型的指针
    static student B;  //正确。静态成员可以是不完全类型
};

 

例:定义一对类X,Y,其中X包含一个指向Y的指针,而Y包含一个类型为X的对象。

class Y;     //必须先声明但不定义Y
class X
{
   Y* y;
};
class Y
{
    X x;
};
 

 

posted @ 2018-07-24 10:37  拾月凄辰  阅读(301)  评论(0编辑  收藏  举报