• C++ Primer 第七章 类


    类的基本思想是数据抽象和封装,数据抽象是一种依赖于接口与实现分离的编程技术。

    定义抽象数据类型

    成员函数的声明必须在类的内部,它的定义则既可以在类的内部也可以在类的外部。作为接口组成部分的非成员函数,它们的定义和声明都在类的外部。定义在类内部的函数是隐式的inline函数。

    当调用成员函数时,编译器隐式的传递该对象的地址,并传递给成员函数的隐式形参,在成员函数内部,该形参用this表示,this表示指向该对象的指针,是常量指针,因此不允许改变this的值。

    在成员函数的参数列表之后紧跟const表示该成员函数为常量成员函数。该const用来修改this的类型,通常this的类型为指向非常量对象的常量指针,虽然this是隐式的,也要遵循初始化规则,这意味着我们不能把this绑定到常量对象上,这也使我们不能在常量对象上调用普通的成员函数。常量成员函数用来修改this的类型为指向常量的指针,因此可以在常量上调用常量成员函数。常量对象以及常量对象的引用或指针都只能调用常量成员函数。

    类的成员函数可以使用任意数据成员而无需数据成员是否出现在成员函数后面。

    在类的外部定义成员函数时,一定保证成员函数与声明保持一致,包括返回类型,名称,参数列表与参数列表后面的const关键字,并且函数名必须使用域运算符。

    一般来说,如果类的非成员函数是类接口的一部分,这些函数的声明应该与类在同一个头文件中。

    IO类属于不能拷贝的类型,因此只能通过引用来传递它们。

    构造函数的作用是初始化类的数据成员。构造函数不能声明为const,直到构造函数完成初始化过程,对象才能取得其“常量”属性,因此在构造函数中可以改变常量对象的值。

    只有当类没有声明任何构造函数时,编译器才会自动生成默认构造函数,又被称为合成的默认构造函数。类通过默认构造函数执行默认初始化。不能依赖合成的默认构造函数,因为只有当类没有任何构造函数时才会合成默认构造函数,并且某些情况编译器无法合成默认构造函数,或合成的默认构造函数不能正确的初始化成员。

    在构造函数的参数列表后面写上=default来要求编译器生成默认构造函数。在构造函数的参数列表后面跟冒号,冒号与函数体之间的部分称为构造函数初始值列表,负责为对象的成员赋初值。

    在类内使用vector或string可以避免分配和释放内存带来的复杂性。当我们初始化变量或以值的方式传递对象或返回对象时将发生拷贝操作,当赋值时产生赋值操作。

    访问控制与封装

    定义在public之后的成员可以在程序内被访问,定义在private之后的成员只能在类中被访问。class和struct只是形式上的不同,唯一的区别是class和struct的默认访问权限不同,如果使用struct,定义在第一个访问说明符之前的成员是public,如果使用class,则定义在第一个访问说明符之前的成员是private。

    类可以允许其他类或函数访问它的非公有成员,如果想让某个函数称为友元,只需使用关键字friend声明函数。友元必须在类内部声明,并且不受访问修饰符的限制,因此最好在类的开始或结束为止统一声明友元。友元声明只是指定了访问权限而不是通常意义的函数声明。为了使友元对类的用户可见,友元函数的声明通常放置在类的头文件中,最好在声明友元之前声明友元函数。

    类的其他特性

    除了定义数据和函数成员之外,类中还可以定义某种数据成员在类中的别名,由类定义的类型别名同样有public和private访问限制,可以使用typedef和using关键字定义,类型成员需要先定义后使用。

    可以通过inline关键字把成员函数显示的定义为内联函数,在函数的返回值类型前添加inline关键字。inline函数也应该在相应的类的头文件中定义。

    成员函数和非成员函数一样可以重载。

    一个可变数据成员永远不会是const,即使它是const对象的成员,因此一个const成员函数可以改变一个可变成员的值。通过关键字mutable声明可变成员。

    当我们使用类内初始化时,必须使用等号的形式或花括号括起来的直接初始化形式。

    通过返回*this的成员函数可以把对成员函数的调用串联起来,如obj.move().set().display()。注意函数返回的类型一定要是引用类型否则将返回对象的副本,这将不能达到串联对象成员的效果。一个const成员函数如果以引用的形式返回*this,那么它的返回类型将是常量引用。

    基于区分成员函数是否是const,我们可以对其进行重载,这样可以实现常量对象调用常量成员,非常量对象调用非常量成员。当一个成员调用另外一个成员时,this指针隐式的传递,并且可以隐式的从非常量指针转换成指向常量的指针。

    每个类定义了唯一的类型,即使他们的成员完全一样,这两个类也是两个不同的类型。使用类类型时可以在类名前添加关键字也可以不加。

    可以紧声明类而不定义它,如class Screen;这种声明称为前向声明,声明之后定义之前被称为不完全类型,不完全类型只能在有限的情景下使用,只能定义这种类型的指针或引用,也可以定义包含这种类型的参数或返回值的函数。

    注意一个类的成员类型不能是其自己,但是允许包含指向自身的指针或引用。

    类可以把其他的类定义成友元,也可以把其他类的成员函数定义成友元。把类指定为友元的形式为friend class window_mgr;友元可以访问类的所有成员包括私有成员。特别注意友元不存在传递性,每个类控制自己的友元类和友元函数。可以紧把某个成员函数指定为类的友元,如friend void window_mgr::clear();另某个成员函数做为友元时,一定要组织好程序的声明顺序。

    尽管重载函数的名字相同,但是他们并不是相同的函数,声明友元时需要分别声明。

    友元声明只是说明访问权限而不是真正的声明。当进行友元声明时我们假定友元声明的名字在当前作用域中可见。

    类的作用域

    每个类都会定义它自己的作用域,在类作用域之外,必须通过对象,引用或指针或域运算符访问类的成员。因此在类的外部定义成员函数时一定要使用类名指定成员函数所属的类,当出现类名后,函数的参数列表和函数体将默认使用类的作用域,但函数的返回类型还是在类的作用域之外,因此如果返回类型需要访问类内成员需要使用类名与域运算符访问,如window_mgr::ScreenIndex window_mgr::addScreen();其中返回类型为类内定义的类型。

    类成员中声明中使用的名字,如返回类型和参数列表中使用的名字都必须在使用前确保可见。类的类型名通常定义在类的开始处,这样可以保证后面的成员能使用该名字。在类内不能再定义与外层作用域中类型相同的名字。

    成员函数中名字的查找,首先在函数内查找,未找到时在类作用域中查找,类作用域之后在外围作用域查找。如果类的成员隐藏了外层作用域的名字,则可以通过作用域运算符引用外层作用域如::height。名字查找还包括成员函数定义之前的作用域。

    构造函数在探

    构造函数初始值列表用来初始化数据成员,建议使用初始值列表而不使用赋值形式。某些情况必须使用构造函数初始值列表初始化成员,如果成员是const或引用或某种未提供默认构造函数的类类型则必须使用初始化列表。

    成员初始化的顺序与初始值列表的顺序无关,初始化顺序按照成员声明的顺序进行。

    如果一个构造函数为所有参数都提供了默认值,则它实际上也定义了默认构造函数。

    委托构造函数使用其他构造函数执行一部分初始化工作或全部工作,委托构造函数在参数列表后面添加冒号后直接调用其他构造函数。调用委托构造函数时先执行被委托的构造函数初始值列表和函数体,然后执行委托构造函数的函数体。

    当对象被默认初始化或值初始化时自动执行默认构造函数。想显式使用默认构造函数初始化对象时,不需要写对象后面的小括号,否则会变成一个函数声明,如Sales_Data obj;而不是Sales_Data obj();。

    如果构造函数只接受一个参数,则它实际上定义了转换为此类类型的隐式转换机制,我们把这种构造函数称为转换构造函数。转换构造函数定义了从参数类型到类类型的隐式转换规则。隐式转换只允许一次转换。

    通过给构造函数添加explicit关键字可以抑制构造函数的隐式转换。explicit构造函数只能用于直接初始化。不过可以显示的调用explicit构造函数进行对象初始化。

    当一个类所有成员都是public,没有定义任何构造函数,没有类内初始值,没有基类,也没有virtual函数,我们称这样的类为聚合类,可以提供一个花括号括起来的成员初始值列表来初始化聚合类,如Data value={“Jeff”,“23”};,列表的顺序必须与成员声明的顺序一致。

    数据成员都是字面值的聚合类是字面值常量类。如果类包含至少一个constexpr构造函数并且数据成员全部为字面值类型并且如果数据成员含有类内初始值则初始值必须是一条常量表达式,则该类也为字面值常量类。

    类的静态成员

    静态成员与类本身关联而不与类的对象关联,静态函数不与任何对象绑定到一起,不包含this指针。使用作用域运算符通过类名直接访问静态成员。虽然静态成员不属于类的某个对象,但是我们仍然可以通过对象,引用,指针访问静态成员。成员函数不用通过作用域运算符就能直接访问静态成员。

    既可以在类的内部也可以在类的外部定义静态成员函数,当在类的外部定义静态成员时不能重复static关键字,static只能在类的内部出现。

    因为静态数据成员不属于任何一个对象,因此静态数据成员不是通过构造函数初始化,一般来说我们不能通过在类的内部初始化静态成员,必须在类的外部定义和初始化每个静态数据成员。定义并初始化一个静态数据成员:double Account::interestRate=initRate();从类名开始,这条语句的剩余部分就都在类的作用域之内了。

    可以通过常量表达式在类内初始化静态数据成员,即使已经在类内初始化了静态数据成员,也应该在类的外部定义一下该成员。

    静态数据成员的类型可以是它所属的类型,静态数据成员可以作为函数成员的默认实参,而非静态数据成员不能。

  • 相关阅读:
    EF6 在原有数据库中使用 CodeFirst 总复习(三、重建迁移)
    EF6 在原有数据库中使用 CodeFirst 总复习(四、新建实体对象)
    EF6 在原有数据库中使用 CodeFirst 总复习(五、生成发帖页面)
    实体框架 (EF) 入门 => 一、我该用哪个工作流?
    实体框架 (EF) 入门 => 二、在全新的数据库中使用 Code First
    asp.net core 2.0 webapi集成signalr
    实体框架 (EF) 入门 => 三、CodeFirst 支持的完整特性列表
    ORM框架之------Dapper,Net下无敌的ORM
    Dapper Helper
    .NET平台微服务项目汇集
  • 原文地址:https://www.cnblogs.com/jefflee/p/6142379.html
Copyright © 2020-2023  润新知