欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

VC++入门经典学习笔记--结构和类

程序员文章站 2022-06-24 20:38:51
1.自定义数据类型 C++中的结构:结构是使用关键字struct定义的用户定义类型。结构起源于C语言,C++继承并扩展了结构。C++中的结构在功能上可以由类代替,因为任何使用结构...

1.自定义数据类型
C++中的结构:结构是使用关键字struct定义的用户定义类型。结构起源于C语言,C++继承并扩展了结构。C++中的结构在功能上可以由类代替,因为任何使用结构能够做到的事情都可以使用类做到。但是因为Windows是在广泛应用C++之前用C语言编写的,所以结构遍布在Windows编程的各个方面。今天,结构仍然被广泛使用,因此我们确实需要 了解结构。

2.结构的概念
考虑一下要描述像一本书这样简单的事物需要多少信息。我们首先可能想到书名,作者,出版社,出版日期,页数,定价,主题或分类以及ISBN号,还可能不太困难地就想到一些其他信息。我们可以指定相互独立的变量来容纳描述一本书所需的每项信息,但更希望能够使用一种数据类型来包含所有这些信息。这正是结构能为我们做的事情。

3.定义结构
假设只需要在书的定义中包括书名,作者,出版社和出版年份。

 struct Book
  {
    char title[80];
    char author[80];
    char publisher[80];
    int year;
  };

这里的代码没有定义任何变量,但实际上创建了一种新的类型,该类型的名称是BOOK。关键字struct将BOOK定义成结构,构成本类型对象的元素是大括号内定义的。结构体内的元素可以是除所定义的结构类型以外的任何类型。例如,BOOK的结构定义中不能包括类型为BOOK的元素。我们可以认为这是一种局限性,但BOOK定义中可以包括BOOK类型变量的指针。
创建其他类型变量的方式来创建BOOK类型的变量:
BOOK novbel; //声明一个BOOK类型的变量novbel.

4.初始化结构
第一种将数据存入结构成员的方法,是在声明语句中为结构体成员定义初始化。

   Book novel
   {
     “Paneless Programing”,
     "I.c. Fingers",
     "Gutter Press",
     1981
   };

这些初始化值位于初始化列表内,相互之间以逗号分开,这种方式与为数组成员定义初始值的方式完全相同。

5.访问结构的成员
为了访问结构各个成员,可以使用成员选择操作符”.”,有时称之为成员访问操作符。
如果想修改Novel结构的Year成员可以这样写:

   novel.year=1988;

6.RECT结构在windows程序中,矩形用的很多
因此,包含在windows.h的windef.h头文件中有一个预定义的RECT结构,,其定义本质是

   struct RECT
   {
     LONG left;
     LONG top;
     LONG right;
     LONG bottom;
   };

MFC(微软基础类库(英语:Microsoft Foundation Classes,简称MFC)是一个微软公司提供的类库(class libraries),以C++类的形式封装了Windows API,并且包含一个应用程序框架,以减少应用程序开发人员的工作量。其中包含的类包含大量Windows句柄封装类和很多Windows的内建控件和组件的封装类。) MFC也定义了等价于RECT结构的CCRect类。当我们理解类之后,将优先使用CRect大类而非RECT结构。CRect类提供了很多处理矩形的函数,在使用MFC编写Windows程序时将大量使用这些函数。

7.使用指针处理结构
可以创建指向结构类型对象的指针。windows.h中声明的许多处理RECT对象的函数都要求实参是指向RECT的指针,因为这样可以避免给函数传递RECT实参时复制整个结构的系统开销。

  RECT* pRect{};                 //声明一个指针指向RECT

假设已经定义了一个RECT对象&aRect,那么可以使用通常的方式将aRect变量的地址赋予pRect指针:

  pRect = &aRect;

struct不能包含与被定义结构类型相同的成员,但可以包含指向struct的指针,其中包括指向相同类型struct的指针。

  struct ListElement
  {
    RECT aRect;
    ListElement* pNext;
  }

8.通过指针访问结构成员

  RECT aRect{0,0,100,100};
  RECT* pRect{&aRect};

第一条语句声明并定义了一个RECT类型的对象aRect,将第一对成员初始化为{0,0},将第二对成员初始化为{100,100}。第二条语句将pRect声明为指向RECT类型的指针,并将其初始化为aRect的地址。现在,可以用下面的语句,通过指针访问aRect的成员:

    (*pRect).top+=10;

这里使用的通过指针访问struct成员的方法看起来相当笨拙。这种操作在C++中出现得相当频繁,因此C++提供了一个特殊操作符,间接成员选择操作符(->)。

   pRect->top +=10;  

该语句更清楚地表示自己的用途。在本书中经常会看到该操作符。

9.数据类型,对象,类和实例
基本类型的变量不能充分模拟现实世界中的对象或虚拟的对象。例如,很难用int模拟一个箱子,但可以使用struct成员为这样的对象定义一组特性。如下所示,可以定义length,width和height这3个变量来表示箱子的尺寸,并将它们作为Box结构的成员捆绑到一起:

  struct Box
  {
    double length;
    double width;
    double height;
  };

有了名为Box的新数据类型的定义之后,就可以像定义基本类型变量那样定义该类型的变量。在程序中,可以创建,处理和销毁任意数量的Box对象。这意味着可以使用struct来模拟对象,并 围绕对象编写程序。
因此,这就是面向对象编程,对吗?对,但不完全对。面向对象编程(OOP)基于与对象类型相关的3个基本概念,即封装,多态性和继承性,而我们目前所看到的不完全与之吻合。
在C++中,struct的概念远远超出了C语言中原来的struct概念,它现在合并了类的面向对象思想。类的思想–可以创建数据类型并像使用已有类型那样使用,对C++而言非常重要,因此该语言引入了一个新关键字class来描述类这一概念。在C++中,除了成员的访问控制以外,关键字struct和class几乎是等同的。保留关键字struct是为了向后兼容C语言,但使用struct能实现的一切都可以用类来实现,而且类可以比struct实现更多的功能。
定义表示箱子的类:

  class CBox
  {
    public:
      double m_length;
      double m_width;
      double m_height;
  };

与定义Box结构的情况类似,将CBox定义成类时,实质上是在定义新的数据类型。仅有的区别是使用了关键字class代替struct,还在类成员的定义前面使用了后跟冒号的关键字public。作为类组成部分被定义的变量称为类的数据成员,因为他们是存储数据的变量。

public关键字提供了区别结构和类之间的线索。以该关键字定义的类成员通常是可以访问的,其访问方式与访问结构成员相同。默认情况下,类成员一般是不可访问的,而是私有的(private)。为了使类成员可访问,就必须在他们的定义前面使用关键字public。
结构成员默认是共有的。类成员默认情况下之所以是私有的,一般是因为类对象应该是自包含实体,这样的数据使对象应该被封装起来,并且只能在受控制的情形下修改。公共的数据成员是非常少见的例外情况。
MFC采用了在所有类名称以C作为前缀的约定,因此我们也要养成这样的习惯。MFC还给类的数据成员添加m_前缀,以便将他们与其他变量区别开来。
可以像下面这样,声明一个表示CBox类类型的实例bigBox

  CBox bigBox;

10.类的起源:
类的概念是以为英国人为使普通人保持心情愉快而发明的,它源于”知道自己在社会中的地位和作用的人们,生活将比那些对此茫然无知的人更感到安全和舒适”这一理论。著名的C++发明者丹麦人Bjarne Stroustrup在剑桥大学期间学到了深奥的类概念,并非常成功地将之应用到他的新语言中。
类通常都有非常精确的任务和一组可以执行的动作,从这一点来讲,C++中的类与英语原意相同。但是,C++中的类与英语原意是有区别的,因为他们很大程度上专注于实现工作类的价值。实际上,在某些方面他们甚至与英语原意相反,因为C++中的工作类往往位于那些完全不做任何事情的类背后。

11.类的操作
在c++中,可以创建新的数据类型–类,来表示任何希望表示的对象。类(以及结构)不仅限于容纳数据,还可以定义成员函数,甚至可定义在类对象之间使用标准C++运算符执行操作。还可以在CBox类中实现使box相加,相减乃至相乘的操作。事实上,几乎任何在box上下问中有实际意义的操作都可以实现。
这里谈论的是令人难以置信的强大技术,它使我们采用的编程方法产生了重大变化。我们不在根据本质上与计算机相关的数据类型(整数,浮点数等)分解问题,然后编写程序,而是根据与问题相关的数据类型(换一种说法就是类)进行编程。

12.类的相关术语
类是用户定义的数据类型。
面向对象编程(OOP)是一种编程风格,它基于将自己的数据类型定义成类的思想,这些数据类型专用于打算解决的问题的问题域。

 声明类的对象有时称作实例化,因为这是在创建类的实例。
 类的实例称为对象,
 对象在定义中隐式地包含数据和操作数据的函数,这种思想称为封装。

13.理解类
类是用户定义的数据类型的说明,其包含的数据元素可以是基本类型或其他用户定义类型的变量。类的数据元素是单数据元素,数组,指针,几乎任何种类的指针数组或其他类的对象,因此在类类型可以包括的数据方面有非常大的灵活性。类还包含通过访问类内的数据元素来处理本类对象的函数。因此,类组合了构成对象的元素数据的定义和处理奔雷对象中数据的方法。
类中的数据和函数称为类的成员。奇怪的是类的数据项称为数据成员,函数的类成员称为函数成员或成员函数。类的成员函数有时也称作方法。

 定义类时,就是在定义某种数据类型的蓝图。我们没有实际定义任何数据,但确实定义了类名的意义--即类对象将由那些数据组成,对这样的对象可以执行什么操作。如果要编写基本类型double的说明,则情况完全相同。但我们编写的不是double类型的实际变量,而是这种变量的构成和操作的定义。为了创建基本数据类型的变量,需要使用声明语句。创建变量的情形完全相同。

14.定义类:定义表示箱子的类:

  class CBox
      {
        public:
          double m_length;
          double m_width;
          double m_height;
      };
  类名跟在class关键字后面,3个数据成员在大括号内定义的。数据成员的定义使用我们已经很熟悉的声明语句,整个类定义以分号结束。所有类成员的名称都是该类的局部变量。因此,可以在程序中的其他地方使用相同的名称,包括其他类定义。

  类的访问控制:
  public关键字决定着后面那些类成员的访问属性。将数据成员指定为public,意味着在包含这些成员的类对象的作用于内的任何位置都可以访问它们。还可以将类成员指定为private或protected,此时这些成员不能在类的外部访问。事实上,如果完全省略访问说明,则成员的默认访问属性是private(这是类和结构之间的唯一区别--结构的默认访问说明符是public)。

15.声明类的对象
声明类的对象的方法与声明基本类型对象的方法完全相同。因此,可以用下面的语句声明类类型CBox的对象:

      CBox Box1;
      CBox box2;
  box1和box2这两个对象都拥有各自的数据成员。

16.访问类的数据成员
可以使用访问结构成员时用过的直接成员选择操作符来引用类对象的数据成员。

17.对象成员的初始化
因为CBox对象的数据成员是public,所以在创建对象时,可以在初始化列表中指定他们的值:

      CBox box1{2.5,3.5,4.5};
  box1的成员按顺序获取列表的值,所以m_Length是2.5,m_Height是4.5。如果在列表中提供的值少于数据成员的个数,未获取值的成员就设置为0。
 CBox box2{2.5,3.5};
  也可以提示一个空列表,把所有成员都初始化为0:
      CBox box3{};

18.初始化类成员

   class CBox
      {
        public:
          double m_length{1.0};
          double m_width{1.0};
          double m_height{1.0};
      };
  初始化类成员的语法与普通变量相同,也是使用初始化列表。初始化应用于所创建的任何CBox对象的成员,除非成员的值通过其他方式设置。

19.类的成员函数是其定义或原型在类定义内部的函数,他们可以处理本类的任何对象,有权访问本类对象的所有成员。

20.类的成员函数
类的成员函数是其定义或原型在类定义内部的函数,它们可以处理本类的任何对象,有权访问本类对象的所有成员,而不管访问指定符是什么。在成员函数体中使用的类成员名称自动引用用于调用函数的特定对象的成员,且只能给该类类型的特定对象调用该函数。如果尝试在调用成员函数时不指定对象名,程序就不会编译。

      class CBox
      {
        public:
          double m_length{1.0};
          double m_width{1.0};
          double m_height{1.0};
          double volume()
          {
            return m_length*m_width*m_width;
          }
      };
      int main()
      {
            double boxVolume{box1.volume()};
      }
  在成员函数内访问成员时,不需要以任何方式限定这些成员的名称。未定义的成员名自动引用调用该成员函数时当前对象的成员。

21.在类的外部定义成员函数
可以在类的外部定义成员函数。此时只需要将函数原型放在类内部。如果这样重写前面的CBox类,并将函数定于放在类的外部,则类定义如下:

        class CBox
      {
        public:
          double m_length{1.0};
          double m_width{1.0};
          double m_height{1.0};
      };
      double CBox::volume()
      {
            return m_length*m_width*m_width;
      }
 因为volume()成员函数的定义在类的外部,所以必须以某种方式告诉编译器:该函数属于CBox类--即给函数名加上类名作为前缀,并用作用域解析运算符::(两个冒号)将两者分开。

22.内联函数
在内联函数中,编译器设法以函数体代码代替函数调用。这样可以避免调用函数时的大量系统开销,加速代码的运行。
注意:当然,编译器将确保展开内联函数时不引起任何与变量名冲突或作用域有关的问题。
把函数指定为内联,并不能保证函数是内联的。编译器不一定总能插入内联函数的代码(如递归函数或返回地址的函数),但通常应该没有问题。内联函数最适用于那些非常短的简单函数,如CBox类的volume()函数,因为这样的函数执行的更快,而且插入其函数体的代码不会显著增加可执行模块儿的大小。编译器不能把函数变成内联函数内时,代码仍能编译运行。
在函数定义位于类定义外部时,也可以告诉编译器将函数视为内联函数–即在函数头前面加上关键字inline:

    inline double CBox::Volume()
     {
        return m_length*m_width*m_height;
     }

使用上面的函数定义,程序将与原来完全相同。因此可以把成员函数的定义放在类的外部,且仍然保留内联函数在执行性能方面的优势。也可以对程序中与类好不相干的普通函数应用关键字inline,并得到相同的效果。但请记住,该关键字最适合用于短小的简单的程序。
如前所述,给内联函数使用关键字inline不是必须的。即使该函数没有标记为inline,编译器有时也可以确定内俩函数是否有意义。

23.类构造函数:当定义不具有public属性的类数据成员时,不能以任何方式从类外不访问这些成员。必须有更好的方法,当然–那就是类构造函数。
类构造函数是类的特殊函数,在创建新的类对象时调用它。因此,该函数提供了创建对象时进行初始化的机会,并确保数据成员只包含有效值。构造函数是一个成员函数,所以无论成员的访问特性是什么,都可以设置成成员的值。
在命名类构造函数方面,我们没有回旋余地,它们总是与所属的类的名称相同,甚至类有两个或多个构造函数时,也是如此。构造函数没有任何返回类型。给构造函数指定返回类型错误的,即使写成void也不允许。类构造函数的首要目的是给类的数据成员赋予初始值,因此任何返回类型都是不必要或不允许的。

       class CBox
      {
        public:
          double m_length{1.0};
          double m_width{1.0};
          double m_height{1.0};

          CBox(double lv,double wv,double hv)
          {
            m_length = lv;
            m_width = wv;
            m_height = hv;
          }
      };

24.默认的构造函数
默认构造函数如下所示:CBox(){}
因为没有为数据成员提供任何初始值。默认构造函数既可以是定义中没有指定任何形参的构造函数,也可以是实参全部有默认值的构造函数。

25.在构造函数中使用初始化列表:
可以在构造函数定义的开头使用构造函数的初始化列表初始化数据成员。

CBox(double lv=1.0,double wv = 1.0,double hv = 1.0):m_length{lv},m_width{wv},m_height{hv}
{
    cout<<"Constructor called."<

这种编写方式嘉定构造函数位于类定义内部。构造函数的初始化列表与形参列表之间以冒号分开,各个成员的初始化器之间以逗号分开。成员初始化列表总是在函数体之前执行,所以可以在构造函数体重使用已在列表中初始化的成员值。

对于const或引用类型的类成员,其初始化方式是无法选择的。唯一的方式是在构造函数中使用成员初始化列表。构造函数体中的赋值语句是无效的。还要注意,成员初始化的顺序不同于它们在构造函数初始化列表中的顺序,而与它们类在定义中的顺序相同。

25.类的友元函数: 由于某种原因,我们希望某系虽然不是类成员的函数能够访问类的所有成员–它们拥有特殊权限。这样的函数称为类的友元函数,使用关键字friend来定义。可以在类定义中添加友元函数的原型,也可以添加整个函数定义。在类定义内定义的友元函数默认也是内联函数。 友元函数不是类的成员,因此访问特性不适用于它们。这些函数只是拥有特殊权限的普通全局函数。

26.默认复制构造函数


    CBox box1{78.0,24.0,18.0};

现在,要创建另一个与box1相同的CBox对象,希望能够用box1将其初始化。


    int main()
    {
        CBox box1{78.0,4.0,18.0};
        CBox box2{box1};

        cout<

该程序像我们期望的那样工作,结果是两个箱子具有相同的体积。但从输出可以看出,仅在创建box1时调用了一次构造函数。那么,box2是如何创建的呢? 这里的机制类似于前面没有定义构造函数时所使用的机制–即编译器提供默认的构造函数,以允许创建对象。本示例中,编译器生成一个默认的赋值构造函数。

复制构造函数做的就是我们在这里做的事情–即通过用同类的现有对象进行初始化来创建类对象。复制构造函数的默认版本通过诸葛成员地复制现有的对象来创建新对象。


*注意:默认构造函数对CBox这样的简单类工作得不错,但对许多类来说--如拥有指针成员的类,该函数将不能正常工作。实际上,对这些类应用默认复制构造函数进行逐个成员地复制,可能产生严重的程序错误。这种情况下,必须创建自己的复制构造函数,来替代默认的复制构造函数。*

27.this指针 在CBox类中,用类定义中的类成员名编写volume()函数。当然,创建的所有CBox类型的对象都包含这些成员,因此必须有某种机制使该函数能够引用调用它的具体对象的成员。 任何成员函数执行时,都自动包含一个名为this的隐藏指针,它指向调用该函数时使用的对象。因此,m_Length成员名出现在volume()函数体中时,实际上是this->m_length–用于调用函数对象成员的全称。编译器负责在函数中给成员名添加必要的指针名this。 如果需要,也可以在成员函数中显式的使用this指针。


        class CBox
      {
        public:
          double m_length{1.0};
          double m_width{1.0};
          double m_height{1.0};
          double volume()
          {
            return m_length*m_width*m_width;
          }
          bool compare(CBox& xBox)
          {
            return this->volume()>xBox.volume();
          }
      };
      int main()
      {
        CBox match{2.2,1.1,0.5};
        CBox cigar{8.0,5.0,1.0};
        if(cigar.compare(match))
          cout<<"match is smaller than cigar"<

28.类的静态成员 静态数据成员是程序启动时自动创建的,并且初始化为0–除非将其初始化为其他值。因此,仅当希望类的静态数据成员最初是非零值时,才需要初始化它们。但仍需要定义它们,例如:


     int CBox::objectCount;

该语句定义了objectCount,但没有显式初始化它,所以其默认值为0。

29.类的静态成员函数 通过将某个函数声明为static可以使该函数独立于本类的任何具体对象。因此它没有this指针。static成员函数的优点是:即使本类的任何对象都不存在,它们也能存在并被调用。在这种情况下,静态成员函数只能使用静态数据成员函数,因为后者是唯一存在的数据成员。因此,即使不能肯定是否有类对象存在,也可以调用类的静态函数成员来检查静态数据成员。 静态成员函数的原型可以如下所示:


    static void aFunction(int n);

可以用下面这条语句通过具体对象调用静态函数:

    aBox.aFunction(10);

该函数不能访问aBox的非静态成员。不通过引用对象也可以调用同一个函数,这种情况下该语句的形式如下:CBox::aFunction(10); 类名CBox限定了函数。使用类名和作用域解析运算符,可以告诉编译器aFunction()函数属于哪个类。

30.类对象的指针和引用 使用类对象的指针和引用–特别是引用,在面向对象编程和函数形参说明方面非常重要。类对象可能涉及相当多的数据,因此对对象使用按值传递机制非常耗时和低效,因为需要复制每一个实参对象。使用引用形参可以避免这个系统开销,而且引用形参对于一些类的操作是必不可少的。 如果不使用引用形参,将不能编写复制构造函数。

31.类对象的指针 能以生命其他指针的相同方式,声明指向类对象的指针。


    CBox* pBox{};

现在可以使用取址运算符,按通常的方式在赋值语句中使用该指针来存储CBox对象的地址:

    pBox = &ciger;
        cout<volume();

该语句再次使用了间接成员访问操作符。

32.类对象的引用 当随ongoing类一起使用时,才能真正体验引用的价值。如同指针一样,在声明和使用类对象的引用于声明和使用基本变量的引用之间,实质上没有任何区别。例如,为了声明对象cigar的引用,可以这样写:


    CBox& rcigar{cigar};

为了使用引用计算对象cigar的体积,只需在应该出现对象名的位置使用引用名即可:

    cout<
我们可能还记得,引用用作被引用对象的别名,因此其用法与原来的对象名完全相同。

实现复制构造函数: 引用重要性实际体现在函数(特别是类的成员函数)的形参和返回值等上下文中。现在仍以复制构造函数为例。我们暂且回避何时需要编写自己的复制构造函数这个问题,而全神贯注与如何编写复制构造函数。 复制构造函数是用同类的现有对象进行初始化,从而创建新对象的构造函数,因此需要接受同类的对象作为实参。

    CBox(CBox initB);

现在考虑调用该函数时将发生什么事情。如果编写CBox myBox(cigar); 这样一条声明语句,那么将生成如下所示对复制构造函数的调用。该语句似乎没有任何问题,但其实参是通过按值传递机制传递的。在可以传递对象cigar之前,编译器需要安排创建该对象的副本。因此,编译器为了处理复制构造函数的这条调用语句,需要调用复制构造函数来创建实参的副本。但是,由于是按值传递,第二次调用同样需要创建实参的副本,因此还得调用复制构造函数,就这样持续不休。最终得到的是对复制构造函数的无穷调用。 解决方法是使用const引用形参。可以将复制构造函数的原型写成如下形式:

    CBox(const CBox& initB);

现在,不再需要复制构造函数的实参。实参是用来初始化引用形参的,因此没有复制发生。如果函数的形参是引用,则调用该函数时不需要复制实参。函数直接访问被调用函数中的实参变量。const限定符用来确保该函数不能修改实参。 这是const限定符的另一个重要用途。我们应该总是将函数的应用形参声明为const,除非该函数将修改实参。 可以像下面这样实现这个复制构造函数:

CBox::CBox(const CBox& initB):
        m_length(initB.m_length),m_width(initB.m_Width),m_height(initB.m_height){}

该复制构造函数的定义假定其位于类定义外部,因此构造函数名用类名和作用域解析运算符加以限定。被创建对象的各个数据成员用传递给构造函数的实参对象的对应成员进行初始化。 有点儿乱,留作回看!本来没怎么系统的学过C,C++只是学cocos2d-x的时候,大致学了一下要用到的一些东西。发现,回过头来看这些基础的东西,学习后感觉更加的踏实了!哈哈