C++学习笔记

这篇C++学习笔记涵盖了基础概念,如字符常量、宽字符型和字符串处理,强调了构造函数和析构函数在对象生命周期中的作用,以及如何使用友元函数实现类间访问。此外,还探讨了内联函数、多态性和客户/服务器模型在面向对象编程中的应用。
摘要由CSDN通过智能技术生成

前言

目前还处于学习阶段,比较混乱。

参考资料

  • 《C++ Primer Plus》(第6版)
  • 《C++程序设计精要教程》 马光志
  • 菜鸟教程-CPP

基础部分

字符常量

字符常量是括在单引号中。如果常量以 L(仅当大写时)开头,则表示它是一个宽字符常量(例如 L’x’),此时它必须存储在 wchar_t 类型的变量中。否则,它就是一个窄字符常量(例如 ‘x’),此时它可以存储在 char 类型的简单变量中。

字符常量可以是一个普通的字符(例如 ‘x’)、一个转义序列(例如 ‘\t’),或一个通用的字符(例如 ‘\u02C0’)。

在 C++ 中,有一些特定的字符,当它们前面有反斜杠时,它们就具有特殊的含义,被用来表示如换行符(\n)或制表符(\t)等。

宽字符型wchar_t的定义

typedef short int wchar_t

字符串分行

可以使用\做分隔符,把一个很长的字符串常量进行分行。

string greeting = "hello, \
				   runoob";

输出:hello, runoob

用条件编译来实现注释和代码测试

#if 0 ... #endif 

此条语句即为条件编译,0即为参数。此外,我们还可以使用#if 0 ... #endif来实现注释,且可以嵌套使用,格式为:

#if 0
	code
#endif

你可以把#if 0改成#if 1来执行code的代码。

这种形式对程序调试也是有帮助的,测试时使用#if 1来执行测试代码,发布后使用#if 0来屏蔽测试代码。

#if后可以是任意的条件语句。
下面的代码如果condition条件为true,执行code1,否则执行code2.

#if condition
	code1
#else
	code2
#endif

良好编程风格

  • 把常量定义为全大写字母形式
    #define MAX 1024
    const int MAX = 1024;

  • 类是一种数据结构,而对象则代表一段内存,存储了数据结构的值。
  • 对象必须进行初始化且仅能初始化一次;而构造函数就是用来初始化对象的。
  • 构造函数为对象申请各种资源,用于初始化对象的数据成员。
  • 如果类没有自定义任何构造函数,则C++编译程序可能会生成默认的构造函数,默认构造函数是参数表无参的函数,它会为维护多态或晚期绑定进行必要的初始化。
  • 类的类型的常量被称为常量对象,类的常量对象的作用域在表达式内。对于同一表达式两个值相同的的常量对象,它们将被视为不同的实例对象。它们将根据表达式计算的先后顺序分别构造,然后在表达式结束时按照相反顺序析构。但若常量对象被无址引用变量引用,则其析构将延迟到引用变量的生命期结束
  • 面向对象的作用域属于面向过程的作用域,当类的函数成员访问的变量没有在类中定义时,可以在更大的作用域(即面向过程的作用域)中寻找访问优先级更低的同名变量进行访问。

命名空间

  • 访问命名空间的方式有三种:
    • 直接访问特定成员,std::cout
    • 声明使用特定成员,using std::cout;,声明使用特定成员会将该特定成员加入当前作用域,相当于在当前作用域声明定义了一个变量,因此不能在同一作用域内定义和该成员同名的变量或函数。
    • 使用命名空间,using namespace std;,在使用命名空间后,不会将命名空间的任何变量或函数加入当前作用域,因此在同一作用域内还可定义和名字空间成员同名的变量和函数。
  • 当命名空间中的成员和程序的全局变量、模块变量和函数名同名时,可以通过单目运算符“::”限定要访问的程序全局变量、模块变量或函数名。如果ALPHA是全局命名空间的名字,则可在ALPHA前面加上单目运算符“::”,即using namespace ALPHAusing namespace ::ALPHA等价,同理,在直接访问命名空间的成员时,ALPHA::f()::ALPHA::f()等价。
  • 当命名空间的成员与程序的函数参数或局部变量同名时,使用非限定名优先访问的是函数参数或局部变量。
  • 匿名命名空间:在当前“.cpp”代码文件内可定义匿名命名空间,同一个“.cpp”代码文件内可多次定义匿名命名空间,并且匿名命名空间定义后即默认被自动使用,相当于using namespace <该匿名命名空间>,所以还可在当前作用域定义同名的全局变量、函数或类型成员,而不会引起二义性。

友元函数

  • 友元函数分为普通友元和成员友元:
    • 普通友元是将非成员函数定义为某个类的友元。
    • 成员友元是将某个类的函数成员定义为另一个类的友元。
  • 因为友元函数不是类的成员函数,因此友元函数可以被声明在该类的任何访问权限下而不受访问权限的限制。

成员友元

  • 为什么要使用成员友元函数?
    • 可以不通过公开访问接口函数而直接访问该类的所有成员,从而大大提高访问该类成员的效率。
  • 不能将尚未完整声明的类(前向声明)作为函数参数,因为此时该类的字节数或者类型大小尚不确定。但相互依赖的类可以使用类的引用或类的指针作为参数,因为引用式指针都会被编译为汇编语言的指针类型,而存储地址的指针变量所需要的字节数总是固定的。

普通友元

  • 任何普通函数,即包括main()在内的非成员函数,都可以声明为类的普通友元。这类成员也被称为非成员友元,声明其为友元的类被称为宿主类。
  • 将非成员函数定义为类的友元时,可以在类中同时定义该友元的函数体。由于函数体是在类体中定义的,因此该友元函数将自动成员inline()函数,该内联函数的存储位置特性默认为static,即该内联函数的作用域局限于当前代码文件。(注意,在非成员函数中定义局部类时该局部的友元函数的函数体不能被同时定义。)
  • 由于全局函数main()的存储位置特性默认为extern,因此不能在类体中定义全局函数main()的函数体。即全局函数main()可以定义为类的普通友元函数,但必须在类体外定义它的函数体。
  • 建议在类体外定义友元的函数体,此时如果希望友元具有局部作用域,可在函数前加上inline或static。
  • 在非成员函数(如main()函数)中定义局部类时,该局部类可以说明某个函数为友元,但不能同时定义这个友元的函数体。但是,在全局类的嵌套定义友元函数时,可以同时定义该友元的函数体。

覆盖与隐藏

  • 隐藏:指当基类成员与派生类成员同名时,通过派生类对象只能访问到派生类成员,而无法访问到其基类的同名成员,则称派生类成员隐藏了同名的基类成员。
  • 覆盖:而如果通过派生类对象还能访问到基类的同名成员,则称派生类成员覆盖了基类同名成员。
  • using不能把一个命名空间成员引入作为类的成员,也不能把一个类的成员引入作为命名空间的成员。在一个派生类中,不能用using多次声明同一个基类成员,因为派生类里的“using 特定成员”带有一定的定义性质。例如,在“using 特定基类数据成员”后,派生类不能自定义同名的数据成员;但是在“using 特定基类函数成员”后,派生类还能自定义同名函数成员。
  • 基类成员继承到派生类时访问权限可以通过using修改,在一个using后不允许列出多个基类成员。

内联

  • 内联函数要求在每个使用它们的文件中都对其进行定义;确保内联定义对多文件程序中的所有文件都可用的最简单的方法是:将内联定义放在定义类的头文件中(有些开发系统包含智能链接程序,允许将内联定义放在一个独立的实现文件)。
  • 定义于类声明中的函数都将自动成为内联函数。类声明常将短小的成员函数作为内联函数。

构造函数

构造函数的意义

  • 使让使用类对象就像使用标准类型一样。然而想要可以像标准类型一样初始化变量的话,还需要构造函数;原因在于,程序只能通过成员函数来访问数据成员,因此需要设计合适的成员函数,才能成功地将对象初始化;如果使数据成员成为公有,而不是私有,就可以在没有构造函数的情况下像标准类型一样进行初始化,然而,使数据成为公有违背了类的一个主要初衷:数据隐藏)。
  • 此外,我们一般遵循“在创建对象时对它进行初始化”;如果没有初始化地声明,在之后赋值可能存在对象实例的一些成员是没有值的。为了避免这种问题,方法之一就是在创建对象时,利用构造函数自动进行初始化,专门构造新对象、将值赋给他们的数据成员。更准确地说,C++为这些成员函数提供了名称和使用语法,而程序员需要提供方法定义。
  • 注意,构造函数没有返回类型。
  • 程序声明对象时,将自动调用构建函数。
  • 一般在类数据成员名中使用m_前缀或者使用后缀_

使用构造函数来初始化对象的方式

每次创建类对象(甚至使用new动态分配内存时),C++都使用类构造函数。

  • 显式调用构造函数
Stock food = Stock("World Cabbage", 250, 1.25);
  • 隐式调用构造函数(更为紧凑,和显式调用等价)
Stock food("World Cabbage", 250, 1.25);
  • 将构造函数与new一起使用
Stock *pfood = new Stock("World Cabbage", 250, 1.25);
  • 一般来说,使用对象来调用方法;但不能使用对象来调用构造函数,因为在构造函数构造出对象之前,对象是不存在的。因此构造函数被用来创建对象,而不能通过对象来调用。

默认构造函数

  • 如果没有提供任何构造函数,则C++将自动提供不接受任何参数的默认构造函数。默认构造函数没有参数,因此只创建对象,而不做初始化等其他任何工作。这和int x;一样,只创建了x这个变量但不进行任何初始化。
  • 定义默认构造函数的两种方式:
    • 给已有构造函数的所有参数提供默认值。
    • 通过函数重载来定义另一个构造函数——一个没有参数的构造函数。
  • 由于只能有一个默认构造函数,因此不要同时采用以上两种方式。

析构函数

析构函数的意义

  • 用构造函数创建对象后,程序负责跟踪该对象,直到其过期为止。对象过期时,程序将调用特殊的成员函数——析构函数。析构函数完成清理工作。

析构函数的形式

  • 析构函数和构造函数都没有返回值和声明类型,而构造函数还没有参数,所以构造函数的原型必须是以下形式:
Stock::~Stock();
  • 如果析构函数什么都不做,则其定义如以下形式:
Stock::~Stock()
{
}

析构函数的调用

  • 什么时候调用析构函数由编译器决定,通常不应在代码中显示地调用析构函数。

this指针

虚函数

  • 重载函数是一种静态多态函数,虚函数是一种动态多态函数。
  • 在编译时,早期绑定完成重载函数的调用;在运行时,晚期绑定完成虚函数的调用。
  • 虚函数到对象的函数成员的映射是通过存储在对象之中的一个指针完成的,因此,晚期绑定的效率非常高,仅比早期绑定多一次指针访问。
  • 通常只需在基类中定义虚函数,派生类中原型相容的实例函数成员将自动成为虚函数。不管进行了多少级派生,虚函数的这一特性将一直传递延续。
  • 实例成员函数相容
    • 该实例函数与基类的虚函数同名,并且除隐含参数的类型不同外,两个函数的所有显式参数都相同;
    • 若基类虚函数的返回类型是基类指针p或引用r,或者是可向p或r赋值的派生类指针或引用,否则两个实例函数成员的返回类型必须相同。
  • 虚函数具有隐藏参数this,所以虚函数不能声明为没有this的静态函数成员。
  • 构造函数虽然有隐藏参数this,但要构造的对象类型明确且无须表现多态,构造函数不需要虚函数的多态特性,所以C++不允许将构造函数定义为虚函数或纯虚函数。
  • 根据对象的真实类型不同而调用不同的实例函数,由此展现出的多种行为被称为虚函数的多态性。
  • 虚函数是类自身的实例函数成员,而friend说明的函数不是当前类的成员,因此不能同时用vitual定义friend函数。
  • 虚函数不能定义为constexpr函数,不能接受类似inline的优化而丧失虚函数入口,虚函数入口用于填写虚函数入口地址表,该地址表的首址将成为对象存储的一个内部指针。

思想

客户/服务器模型

OOP程序员常依照客户/服务器模型来讨论程序设计。在这个概念中,客户是使用类的程序。类声明(包括类方法)构成了服务器,它是程序可以使用的资源。客户只能通过以公有方式定义的接口使用服务器,这意味着客户(客户程序员)唯一的责任是了解该接口。服务器(服务器设计人员)的责任是确保服务器根据该接口可靠并准确地执行。服务器设计人员只能修改类设计的实现细节,而不能修改接口。这样程序员独立地对客户和服务器进行改进,对服务器的修改不会对客户的行为造成意外的影响。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值