• 学习笔记:类(一)


    (注:个人观点,还望指正)

    定义类,就是定义某种数据类型的蓝图。

    类的术语
    1.类是用户定义的数据类型
    2.类的实例称为对象
    3.对象在定义中隐式包含数据和函数(封装)
    4.类中的数据称为数据成员或字段,函数称为函数成员或成员函数

    1.定义:

    class CBox
    {
     public:
     int length;
     int width;
     int height;
     int Area(void);
     int Volume() //内联函数
     {
      return length*width*height;
     }
    }
    int CBox::Area() //普通函数
    {
     return width*height;
    }

    成员默认属性为private

    2.声明对象
     CBox box1;
     CBox box2; //都包含各自数据成员,但未初始化
     
    3.成员函数   
     对象所占的字节大小根据字段有关,添加成员函数不会影响类对象的大小(虚函数例外),成员函数必定存储在内存中,但只有一个副本。
     
    4.内联函数
      引入内联函数的目的是为了解决程序中函数调用的效率问题。
      函数是一种更高级的抽象。它的引入使得编程者只关心函数的功能和使用方法,而不必关心函数功能的具体实现;函数的引入可以减少程序的目标代码,实现程序代码和数据的共享。但是,函数调用也会带来降低效率的问题,因为调用函数实际上将程序执行顺序转移到函数所存放在内存中某个地址,将函数的程序内容执行完后,再返回到转去执行该函数前的地方。这种转移操作要求在转去前要保护现场并记忆执行的地址,转回后先要恢复现场,并按原来保存地址继续执行。因此,函数调用要有一定的时间和空间方面的开销,于是将影响其效率。特别是对于一些函数体代码不是很大,但又频繁地被调用的函数来讲,解决其效率问题更为重要。
      引入内联函数实际上就是为了解决这一问题。在程序编译时,编译器将程序中出现的内联函数的调用表达式用内联函数的函数体来进行替换。显然,这种做法不会产生转去转回的问题,但是由于在编译时将函数休中的代码被替代到程序中,因此会增加目标程序代码量,进而增加空间开销,而在时间代销上不象函数调用时那么大,可见它是以目标代码的增加为代价来换取时间的节省。
      a)在类的内部定义了函数体的函数,被默认为内联函数,不管是否有inline关键字。(参见e)
      b)外部类加inline、为内联函数(参见e)
      c)最适用于非常短的简单函数,不会显著增加可执行模块的大小
      d)inline只是对编译器的一个建议,编译器并不一定采纳(*)
      e)通常编译器不会将带有循环、if、switch、递归和其它超过10行代码的函数作为inline,即使添加了inline关键字。
      f)virtual函数不能为inline,即使添加。(virtual等到运行时决定调用哪个,故在编译期间无法将调用之处用来替换)
      g)inline必须在函数定义前,声明时加上inline关键字没有用,经常会放在头文件中。

    5.内联函数与宏
     宏定义没有类型检查,出错率高,内联函数相反;
     宏定义在预处理器在代码优化时直接替换,后者在编译期间插入代码。
     
    6.头文件中定义类,包含数据成员,函数声明等;而在源文件中对函数定义
     
    7.构造函数
    a)类没有显式定义构造函数,编译器可能会提供默认无实参构造函数;若类中定义了构造函数,编译器不再提供默认构造函数。
    b)不管是否提供默认构造函数,无法直接调用默认构造函数,如:
    CBox c(); //表面上,定义变量c,并调用初始化;实际上编译器认为是一个函数声明,函数名c,返回CBox类型

    8.构造函数默认的形参值
    类似函数原型中给函数的形参指定默认值。类中的函数(包括构造函数)可以在定义或声明时,给形参默认值,但注意重载的冲突。
    Rect::Rect(int i=1,int j=1)
    {
     //包含3种重载,2个参数、1个参数、无参数
    }
    9.构造函数初始化列表
    Rect::Rect(int i,int j):w(i),h(j)
    {
     //对类成员w,h初始化,比赋值语句效率高
    }
    10.声明显示构造函数
    单一形参的构造函数可用作隐式类型转换。
    //头文件

    #pragma once
    class Rect
    {
     public:
     double weight;
     double height;
     Rect(double w);  //添加explicit关键字
    };

    //源文件

    #include "Rect.h"
    Rect::Rect(double w)
    {
     weight=w;
     height=w*2;
    }

    //调用
    int main()
    {
     Rect r1=2; //调用构造函数,隐式转换int为double,通过explicit禁止
    }
    ******给构造函数形参默认值,也需注意隐式转换*******
    Rect(double w=1.0,double h=1.0):weight(w),height(h)
    {}
    // Rect r=2.0; //可通过explicit禁止隐式转换

    /*************
    问题1:为什么类定义与声明放在头文件中,实现放在源文件中
    *************/
    如果类定义在cpp中,如下
    class Test
    {
     public:
      void Print()
      {
       ; //什么都不做
      }
    };
    声明类放在h中
    class Test;
    main文件通过#include包含头文件,无法访问任何类成员,对于编译器讲,此处仅仅是声明有一个Test类,其它一概不知。
    正确做法:类的定义与声明放在h中,具体实现在cpp中,在#include头文件后,编译器知道此处声明了类与类的成员,即可访问它们。

    11.友元函数
    class Test
    {
     friend void Method(); //声明
    };
    void Method() //定义无需类名::
    {
     
    }
    类的友元函数:不是类的成员函数却能访问类的所有成员(拥有特殊权限),关键字friend。
    注:
      a)它不是类的成员,访问特性不使用它们,只是拥有特权的普通全局函数,不理会private;
      b)类内部定义,默认内联函数,外部普通函数,仅仅建议编译器为内联,是否采纳编译器自行决定。(内部可访问私有成员)

    12.默认复制构造函数
    CBox box1(1.0,2.0);
    CBox box2=box1;  //使用box1初始化box2
    这种机制类似于没有定义构造函数,编译器提供默认构造函数;此时编译器提供默认的复制构造函数,来逐个复制。
    缺点:对于拥有指针(地址拷贝)和数组(完全拷贝)成员的类,可该函数可能产生严重的错误。(故创建自己的复制构造函数)

    13.字符串常量
    char* c="hello"; //标准写法const char *c="hello";
    *c='a'; //无法修改 字符串常量

    14.this指针
    任何成员函数执行时,都自动包含一个名为this的隐藏指针,它指向调用该函数时的使用对象。
    this->成员名  (间接成员访问操作符->来引用前缀对象,直接成员访问操作符为.)
    注:任何对不加限定(不加this)的成员名的引用,编译器将自动认为是引用this指向的那个对象成员。

    15.类的const对象
      a)创建固定的类对象,前面加const。
       格式:const 类名 对象名;
       很好理解,初始化后,通过对象名访问成员字段一定是只读,但对象的成员函数内部可能会修改成员字段,怎么办?
      b)类的const成员函数,在函数后附加const
         注意普通的成员函数,都隐藏this指针,通过它是可以访问和修改成员字段;
         const函数中的this指针成为const指针,不允许修改其他成员字段;
         事实上,编译器不允许const对象,访问非const的函数成员,因为非const函数内部可能改变成员字段,违背了const本意。
      c)外部成员函数的定义
         在类成员函数声明时和外部定义时都加上const.(根据需要,可以在类中定义某个函数的const和非const版本)
    演示:

    //h文件
    class Car
    {
    public:
     int weight;
     char* color;
     Car(int w,char* c);
     void Show() const;
    };
    //cpp文件
    Car::Car(int w,char* c):weight(w),color(c)
    {
    
    }
    void Car::Show() const
    {
     std::cout << "weight is " << this->weight << std::endl;
     std::cout << "color is " << this->color << std::endl;
    }
    //main
    int main(){ 
     const Car c(100,"blue");
     c.Show();
     return 0;
    }

    16.类对象的数组
    a)和内置类型的普通数组完全相同
    b)类对象数组每个元素都会调用默认构造函数(类必须提供),构造函数对所有成员字段初始化,否则调用数据无意义。

    17.类的静态成员
    static 类型 字段名,对象共用一个副本。
      a)在头文件中声明,必须在源文件中定义(*程序会对它自动初始化),否则编译错误:无法决定的外部符号。(和类的成员字段不同)
      (通过预处理#include,替换头文件内容,声明存在类名::静态成员,遇到它时,作为外部的符号,单独编译所有源文件,最后link时未找到当前符号)
      b)通过类名::静态成员访问,也可以通过对象名.静态成员访问

    18.类对象的指针和引用
     类对象可能涉及相当多的数据,在函数参数上,使用按值传递(形参为对象),非常耗时和低效,因为要复制一个对象。 
      a)类对象的指针

     CBox b;
     CBox* p(nullptr);
     p=&b;
     //p=new Cbox(); 
     //p->成员 间接访问

      b)复制构造函数

    class CBox
    {
    public:
     int h;
     CBox(int h)
     {
      this->h=h;
     }
     CBox(CBOx initB)
     {
      h=initB.h;
     } 
    }
    int main()
    {
     CBox a(1);
     CBox b(a);
    }

    注:通过构造函数,对b对象初始化,表面上看起来无问题,但构造函数是值传递,在传递前,创建a对象副本,又调用复制构造函数,而本身它是值传递,又会副本,如此陷入死循环。
    编译时,会提示错误"illegal copy constructor:first parameter must not be CBox"
    解决:
     CBox(const CBox& initB)
     引用传递,const根据情况而加,保证构造函数不能对形参修改。

  • 相关阅读:
    进程间通信、线程同步 概要
    【Stackoverflow好问题】将InputStream转换为String
    cocos2dx --- Widget 载入中 CCNode
    ZeroMQ注意事项
    2015第25周日
    2015第25周六
    2015第25周五
    2015第24周四
    2015第25周三iframe小结
    2015第25周二
  • 原文地址:https://www.cnblogs.com/goahead777/p/2868050.html
Copyright © 2020-2023  润新知