C语言进阶

张萃

目录

  • 1 第一章  C++的初步知识
    • 1.1 从C到C++
    • 1.2 C++的词法和词法规则
    • 1.3 最简单的C++程序
    • 1.4 C++对C的扩充
      • 1.4.1 C++的输入输出
      • 1.4.2 用const定义常量
      • 1.4.3 函数原型声明
      • 1.4.4 函数重载
      • 1.4.5 函数模板
      • 1.4.6 有默认参数的函数
      • 1.4.7 变量的引用
      • 1.4.8 内置函数
      • 1.4.9 作用域运算符
      • 1.4.10 字符串变量
      • 1.4.11 动态分配/回收内存运算符
  • 2 类和对象
    • 2.1 面向对象程序设计方法概述
    • 2.2 类的声明和对象的定义
    • 2.3 类的成员函数
    • 2.4 对象成员的引用
    • 2.5 类和对象的简单应用
    • 2.6 类的封闭性和信息隐蔽
  • 3 关于类和对象的进一步讨论
    • 3.1 构造函数
    • 3.2 析构函数
    • 3.3 调用构造函数和析构函数的顺序
    • 3.4 对象数组
    • 3.5 对象指针
    • 3.6 共用数据的保护
    • 3.7 对象的动态建立和释放
    • 3.8 对象的赋值和复制
    • 3.9 静态成员
    • 3.10 友元
    • 3.11 类模板
  • 4 运算符重载
    • 4.1 什么是运算符重载
    • 4.2 运算符重载的方法
    • 4.3 重载运算符的规则
    • 4.4 运算符重载函数作为类成员函数和友元函数
    • 4.5 重载双目运算符
    • 4.6 重载单目运算符
    • 4.7 重载流插入运算符和流提取运算符
  • 5 继承与派生
    • 5.1 继承与派生的概念
    • 5.2 派生类的声明方式
    • 5.3 派生类的构成
    • 5.4 派生类成员的访问属性类型兼容规则
    • 5.5 派生类的构造函数和析构函数
    • 5.6 多重继承
  • 6 多态性与虚函数
    • 6.1 多态性的概念
    • 6.2 一个典型的例子
    • 6.3 虚函数
    • 6.4 纯虚函数与抽象类
  • 7 输入输出流
    • 7.1 C++的输入输出
    • 7.2 标准输出流
    • 7.3 标准输入流
    • 7.4 对数据文件的操作与文件流
    • 7.5 字符串流
构造函数

      

      如果定义一个变量,而程序未对其进行初始化的话,这个变量的值是不确定的,因为 C和C++ 不会自觉地去为它赋值。与此相似,如果定义一个对象,而程序未对其数据成员进行初始化的话,这个对象的值也是不确定的。

      一、对象的初始化 

      在定义一个类时,不能对其数据成员赋初值,因为类是一种类型,系统不会为它分配内存空间。在建立一个对象时,需要对其数据成员赋初值。如果一个数据成员未被赋初值,则它的值是不确定的。因为系统为对象分配内存时,保持了内存单元的原状,它就成为数据成员的初值。这个值是随机的。

      C++ 提供了构造函数机制,用来为对象的数据成员进行初始化。在前面的学习中一直未讲这个概念,其实如果你未设计构造函数,系统在创建对象时,会自动提供一个默认的构造函数,而它只为对象分配内存空间其他什么也不做。

      如果类中的所有数据成员是公有的,可以在定义对象时对其数据成员初始化。

    例如: class  Time

   { public:  int  hour;

      int  minute;

      int  sec;

   };

    Time  t1{ 15, 36,26};

       在一个大括号内顺序列出各个公有数据成员的值,在两个值之间用逗号分隔。注意这只能用于数据成员都是公有的情况。

       在前面的例子里,是用成员函数对对象的数据成员赋初值,如果一个类定义了多个对象,对每个对象都要调用成员函数对数据成员赋初值,那么程序就会变得烦琐,所以用成员函数为数据成员赋初值绝不是一个好方法。

        二、构造函数的作用 

        构造函数用于为对象分配空间和进行初始化,它属于某一个类,可以由系统自动生成。也可以由程序员编写,程序员根据初始化的要求设计构造函数及函数参数。

        构造函数是一种特殊的成员函数,在程序中不需要写调用语句,在系统建立对象时由系统自觉调用执行。

        1、构造函数的特点:

     (1)构造函数的名字与它的类名必须相同。

     (2)它没有类型,与不返回值

     (3)它可以带参数,也可以不带参数。


 例3.1在例2.3的基础上定义构造成员函数

 #include <iostream>

using namespace std;

class Time

{public:

   Time( )

   {hour=0;

    minute=0;

    sec=0;

   }

   void set_time();

   void show_time();

  private:

   int hour;

   int minute;

   int sec;

};

 void Time::set_time()

{cin>>hour;

cin>>minute;

cin>>sec;

}

void Time::show_time()

{

  cout<<hour<<":"<<minute<<":"<<sec<<endl;

}

 int main()

{

  Time t1;

  t1.set_time();

  t1.show_time();

  Time t2;

  t2.show_time();

  return 0;

}

       说明:在类Time中定义了构造函数Time,它与所在的类同名。在建立对象时自动执行构造函数,该函数的作用是为对象中的各个数据成员赋初值 0。 注意只有执行构造函数时才为数据成员赋初值。

       程序运行时首先建立对象t1,并对t1中的数据成员赋初值0,然后执行t1.set_time函数,从键盘输入新值给对象t1的数据成员,再输出t1的数据成员的值。接着建立对象t2,同时对t2中的数据成员赋初值0,最后输出t2的数据成员的初值。

       程序运行的情况为:

        10    25   54

        10:25:54           // 输出t1的值

         0: 0: 0             // 输出t2的值

        也可以在类内声明构造函数然后在类外定义构造函数。将程序修改为:

          Time();

         然后在类外定义构造函数:

          Time::Time()

           {hour=0;

            minute=0;

            sec=0;

            }

        2、构造函数使用说明

     (1)什么时候调用构造函数呢?当函数执行到对象定义语句时建立对象,此时就要调用构造函数,对象就有了自己的作用域,对象的生命周期开始了。

     (2)构造函数没有返回值,因此不需要在定义中声明类型。

     (3)构造函数不需要显式地调用,构造函数是在建立对象时由系统自动执行的,且只执行一次。构造函数一般定义为public。

     (4)在构造函数中除了可以对数据成员赋初值,还可以使用其他语句。

     (5)如果用户没有定义构造函数,C++系统会自动生成一个构造函数,而这个函数体是空的,不执行初始化操作。

       三、带形参数的构造函数 

       可以采用带参数的构造函数,在调用不同对象的构造函数时,从外边将不同的数据传递给构造函数,实现不同对象的初始化。

       1、构造函数的首部的一般格式:

           构造函数名(类型  形参1,类型 形参2,…)

       2、在定义对象时指定实参,定义对象的格式:

          类名  对象名(实参1,实参2,…);

     例3.2:有两个长方柱,其长、宽、高分别为

            (1)12,25,30

            (2)15,30,21

     编写程序,在类中用带参数的构造函数,计算它们的体积。

     分析:可以在类中定义一个计算长方体体积的成员函数计算对象的体积。

#include <iostream>

using namespace std;

class Box

 {public:

     Box(int,int,int);

     int volume();

  private:

     int height;

     int width;

     int length;

 };

Box::Box(int h,int w,int len)   // 长方体构造函数

 {height=h;

  width=w;

  length=len;

 }


int Box::volume()    //  计算长方体的体积

 {return(height*width*length);

 }

int main()

 {

  Box box1(12,25,30);    // 定义对象box1,指定实参

  cout<< " box1体积=" << box1.volume() <<endl;

  Box box2(15,30,21);    // 定义对象box2,指定实参

  cout<< " box2体积= " << box2.volume()<<endl;

  return 0;

 }

       构造函数Box有3个参数,分别代表长、宽、高。在主函数中定义对象box1时,指定了实参12,25,30。然后调用成员函数计算长方体的体积。

        程序运行的结果如下:

             box1体积= 9000

             box2体积= 9450 

        注意:

       (1)带形参的构造函数在定义对象时必须指定实参

       (2)用这种方法可以实现不同对象的初始化。

        四、用参数初始化表对数据成员初始化 

        C++提供了参数初始化表的方法对数据成员初始化。这种方法不必在构造函数内对数据成员初始化,在函数的首部就能实现数据成员初始化。

        格式:函数名(类型1 形参1,类型2 形参2):成员名1(形参1),成员名2(形参2) {    }

       功能:执行构造函数时,将形参1的值赋予成员1,将形参2的值赋予成员2,形参的值由定义对象时的实参值决定。

       此时定义对象的格式依然是带实参的形式:

          类名  对象名( 实参1,实参2);

例:定义带形参初始化表的构造函数

Box::Box( int h, int w, int len): height ( h),

width(w), length( len ){ } 

定义对象:

        Box box1(12,25,30);

       …

         Box box2(15,30,21);

      五、构造函数的重载 

       构造函数也可以重载。一个类可以有多个同名构造函数,函数参数的个数、参数的类型各不相同。

        例3.3:在例3.2的基础上定义两个构造函数其中一个无参数,另一个有参数。

#include <iostream>

using namespace std;

class Box

{public:

        Box();               

        Box(int h,int w ,int len): height(h), width(w), length(len) { }

         int volume();

  private:

         int height;

         int width;

         int length;

};

Box::Box()                 

{height=10;

  width=10;

  length=10;

}

int Box::volume()

{return(height*width*length);

}

int main()

 {

      Box box1;                                    

      cout<<"box1 体积= "<<box1.volume()<<endl;

      Box box2(15,30,25);                          

      cout<<"box2 体积= "<<box2.volume()<<endl;

       return 0;

 }

        说明:例子中定义了两个构造函数,一个无参数另一个带三个参数。系统根据定义对象的格式决定调用哪个构造函数。对象box1没有实参系统为它调用无参数的构造函数;对象box2带三个实参系统为它调用带形参的构造函数。

       注意:

      (1)不带形参的构造函数为默认构造函数每个类只有一个默认构造函数,如果是系统自动给的默认构造函数,其函数体是空的。 

      (2)虽然每个类可以包含多个构造函数,但是创建对象时,系统仅执行其中一个。

        六、使用默认参数值的构造函数 

        C++允许在构造函数里为形参指定默认值,如果创建对象时,未给出相应的实参时,系统将用形参的默认值为形参赋值。

        1、格式:

          函数名(类型 形参1=常数,类型 形参2=常数,…);

        例3.4:将例3.3中的构造函数改用带默认值的参数,长、宽、高的默认值都是10。

     #include <iostream>

     using namespace std;

     class Box

{public:

     Box(int w=10,int h=10,int len=10);

     int volume();

  private:

     int height;

     int width;

     int length;

};

     Box::Box(int w,int h,int len)

     {height=h;

      width=w;

      length=len;

      }

     int Box::volume()

     {return(height*width*length);

      }

    int main()

   {

     Box box1;

     cout<<"box1 体积= "<<box1.volume()<<endl;

     Box box2(15);

     cout<<"box2 体积 "<<box2.volume()<<endl;

     Box box3(15,30);

     cout<<"box3 体积 "<<box3.volume()<<endl;

     Box box4(15,30,20);

     cout<<"box4 体积"<<box4.volume()<<endl;

     return 0;

    }

   程序运行结果为:

   box1 体积=1000

   box2 体积=1500

   box3 体积=4500

   box4 体积=9000


    构造函数也可以改写成带参数初始化表的形式:

   Box::Box( int h, int w, int len): height ( h),width(w), length( len ){ } 

    整个函数只需一行,简单方便。

 

       2、说明:在构造函数中使用默认参数提供了建立对象的多种选择,它的作用相当于多个重载构造函数。

       3、注意:

     (1)如果在类外定义构造函数,应该在声明构造函数时指定默认参数值,在定义函数时可以不再指定默认参数值。

      (2)在声明构造函数时,形参名可以省略例如:

          Box(int =10,int =10,int =10); 

      (3)如果构造函数的所有形参都指定了默认值,在定义对象时,可以指定实参也可不指定实参。由于不指定实参也可以调用构造函数,因此全部形参都指定了默认值的构造函数也属于默认构造函数。为了避免歧义,不允许同时定义不带形参的构造函数和全部形参都指定默认值的构造函数。

      (4)同样为了避免歧义性,如定义了全部形参带默认值的构造函数后,不能再定义重载构造函数。反之亦然。

 例:Box(int =10,int =10,int =10); 

        Box();

        Box(int, int );

       若定义对象:

        Box box1;

        Box box2(15, 30 );

        这时应该调用哪个构造函数呢?如果构造函数中参数并非都带默认值时,要具体分析情况。如有以下三个原型声明:

    Box();

    Box(int ,int =10,int =10); 

    Box(int, int );

    若定义对象:

     Box box1;                //  正确,调用第一个

     Box box2(15);         //   调用第二个

     Box box3(15, 30 );  //   不知调用哪一个

     因此不要同时使用重载构造函数和带默认值的构造函数。