C++快速复习
1. 基本结构
C++语⾔由头⽂件与源⽂件组成。
头⽂件中包含:版权与版本信息,宏定义,函数声明和类的声明。宏定义为是了防⽌头⽂件被重复引⽤。<>引⽤头⽂件则直接在系统库中查头⽂件;“ ”引⽤头⽂件则先在当前⼯程⽬录下查,若⼯程⽬录中不存在然后在系统库中查。
源⽂件中包含:版权与版本信息,对头⽂件的引⽤,系统功能代码的实现。⽤ifndef/define/endif结构产⽣预处理的宏定义块。
类成员函数可以在类声明中被定义,并且⾃动⽣成内联函数,这虽然会使书写⽅便,但会造成风格上的不⼀致,因此建议头⽂件中只存放声明⽽不定义。
头⽂件的作⽤:1.很多时候不便公开头⽂件代码,⽤头⽂件来调⽤库功能,编译器从库中提取相应的代码;2.头⽂件能加强安全类型的检查,如果接⼝实现错误会报错,以便开发者调试。
2.C++基本要素
标识符:只能是字母、数字、下划线组成,且不能以数字开头。标识符在C++中区分⼤⼩写,长度是任意的,⼀般前1024个是有意义的。C语⾔中⼀般前16个是有效的。
常量:在运⾏时不能被改变,在定义常量时可以设置初始值。对于常量,编译器将其放置于⼀个只读的存储区域。
变量:在程序中可以被随意赋值,对于每⼀个变量都有两个属性:左值和右值。左值是指变量的地址值,即存储变量的内存地址;右值是变量的数据值,即内存地址中存储的数据。
变量初始化的⼏种形式:
3.变量的存储类型
变量的声明是告知编译器变量的名称和数据类型,变量的定义是为变量分配存储区域。使⽤extern只声明⽽不定义变量;若⼀个⽂件中声明定义⼀个全局变量int var=0; 在另⼀个⽂件中声明extern int var; 即可访问此变量;
Static存储类型表⽰变量在函数或⽂件内的“持久性”,也称为静态变量,静态变量可分为局部静态变量和全局静态变量。
局部静态变量:函数内的变量⽤static修饰时,将被分配在持久的存储区域,当函数调⽤结束后并不释放,保留其值以便下次调⽤。局部静态变量的作⽤域为当前函数,不能被外界函数和⽂件访问;
全局静态变量:作⽤域仅限于当前定义的⽂件,不能被其它⽂件使⽤extern关键字访问;
使⽤register关键字,表⽰变量将被放置在CPU寄存器中,访问register变量要⽐访问普通变量快得多,但register只能⽤于局部变量或作为函数的形式参数,不能⽤来定义全局变量。
变量有静态存储和动态存储,全局变量和静态变量是静态存储的,普通的局部变量是动态存储的,auto关键字表⽰变量动态存储,默认情况下,局部变量均属于auto变量。
4.数据类型
数值类型分为整型和实型,整型⼜分为有符号型和⽆符号型。
两个实数⽐较,尽量不要使⽤“==”或“!=”,⽽要使⽤“>=”或“<=”之类的运算符。因为⽤等于或不等于时,若程序精度要求较⾼,可能产⽣未知的错误,也可能产⽣未知的结果。通常在⽐较实数时要定义实数的精度,在这个精度内来确定两数的等或不等。
字符类型:C++中⽤单引号来确定字符变量,⽤双引号来确定字符串变量,字符是以ASCII的形式存储的,因此可以直接将整数赋值给字符变量。字符存储⽤⼀个字节。
数组类型:数组的初始化要注意⼀些细节,如下图。定义⼆维数组时,可以省略第⼀维的长度,但不可以省略第⼆维的长度。
布尔类型:布尔类型和整数类型可以相互赋值。
枚举类型:枚举类型是⽤int类型实现的,占⽤4个字节,定义格式为:enum<;枚举类型名>{常量1,常量2,…},定义枚举类型时可以为各常量提供⼀个整数值,默认情况第⼀个数为0,没有定义的值应为前⼀个值加1。在定义函数时将函数参数设置为枚举类型,这样可以限制调⽤函数必须提供枚举类型中的某个常量,⽽不能随意输⼊⼀个整数。
结构体类型:定义格式为struct<;结构体类型名>{结构体类型变量}; 若只要定义⼀次结构体类型变量,可以不写结构体类型名称。访问结构体成员⽤“.”,两个结构体变量可以直接赋值。
结构体空间分配问题:涉及字节对齐问题,即编译器在为结构体变量分配空间时,保证下⼀个成员的偏移量应为该成员数据类型长度的整数倍。在开发应⽤程序时,有时要⽤⼀个字节表⽰多项内容,这时就要⽤位域来访问⼀位数据,如下代码所⽰:
共⽤体提供了⼀种机制,使多个变量(共⽤体中的成员)可以共享同⼀个内存地址,各成员内存起始地址位置相同,每⼀瞬间只有⼀个成员起作⽤,起作⽤的成员是最后⼀次存放的成员。
指针是⽤来存放变量地址的。通过变量访问变量是直接访问,通过指针访问是间接访问。注意区分指针数组和数组指针。如下,
⽤const关键字来修饰指针的⼏种情况:
引⽤即是⽬标的⼀个别名,操作引⽤与操作实际的⽬标对象是相同的。引⽤的定义格式如下:数据类型&引⽤名称=⽬标对象。
⽤typedef关键字可以⾃定义数据类型,它不是创建⼀个新的数据类型,⽽是为已有数据类型创建⼀个新的名称,使⽤⾃定义类型可以提⾼程序的移植性。语法格式为:typedef 数据类型 新名称
5.运算符
异或有很强⼤的功能,通常⽤异或运算来实现⼆进位的反转,也可⽤来实现两个数的互换。
任何数与0进⾏按位异或运算,结果为数据本⾝;
变量与⾃⾝按位异或运算,结果为0;
按位异或运算具有交换性,a^b^c=a^c^b=b^a^c;
⽤异或运算来交换两个数的例⼦如下:
Sizeof()⽤于返回变量、对象或数据类型的字节长度,在32位系统中,指针的长度为4字节。
1.栈存储与堆存储的区别
C++程序编译占⽤的内存可以分为以下⼏个部分:
栈区:由编译器⾃动分配释放,存放函数的参数值、局部变量等,⽤于存放占⽤空间⼩、⽣命周期短的数据;
堆区:由⽤户分配释放,⽤于存储占⽤空间⼤、⽣命周期长的数据,如静态变量和全局变量。⼿动分配空间后⼀定要释放空间,否则会导致内存泄漏;
全局区:全局变量和静态变量是存储于在⼀起的,初始化的存储⼀块区域,未初始化的存储相邻区域;
⽂字常量区:常量字符串存放于此区,程序结束后由系统释放;
程序代码区:存放函数体的⼆进制代码;
堆与栈的区别可以分为三个⽅⾯:
堆栈在空间上的区别见上⾯;
堆栈在缓存⽅式上的区别:栈使⽤的是⼀级缓存,通常都是被调⽤时处于存储空间,调⽤完成⽴即释放;堆是存放在⼆级缓存中的,⽣命周期由虚拟机的垃圾回收算法来决定,故调⽤这些对象的速度相对较慢;
堆栈在数据结构上的区别:堆数据结构可以看成是⼀棵树,如堆排序;栈数据结构是⼀种先进后出的数据结构;
逗号表达式:它的运算优先级最低,逗号表达式取最右边表达式的值。
const与define的⽐较:
C++语⾔可以使⽤这两者来定义常量,但是使⽤const更好,理由如下:
const常量有数据类型,⽽宏常量没有数据类型,编译器可以对前者进⾏安全检查,⽽对后者只能进⾏字符替换,字符替换可能会发⽣意想不到的错误;
有些集成化调试⼯具可以对const常量进⾏调试,但不能对宏常量调试,此时只能使⽤const定义常量。更精确地说,const在程序运⾏时只有⼀份复制品,⽽define有多份复制品。
2.类中的常量问题
结构体数组不能作为参数传递给函数在类中定义常量,我们很⾃然地想到使⽤const,但是const数据成员只对某个对象⽣存周期内是常量,⽽对整个类⽽⾔是可变的,因为创建多个对象时,不同对象其const数据成员的值可以不同。不能在类声明中初始化const成员,其初始化只能在类的构造函数初始化列表中进⾏。因为类未被创建时,编译器不知道相应的常量是什么。
应该使⽤枚举常量来在类中建⽴⼀个恒定的常量,枚举常量不会占⽤对象的存储空间,在编译时全部求值。
3.C++相关语句
C++语句⼀般由表达式和分号构成的,只有空分号的语句为空语句,程序中允许有多条空语句,空语句不执⾏任何功能。{}是复合语句,也可⽤复合语句来代替空语句,但是括号后⾯没有分号。
多条if语句,程序会依次执⾏各条语句,从⽽条件判断较为复杂。对于if/else语句,程序只选择⼀个分⽀按条件执⾏。
return语句⽤来退出当前函数的执⾏,若函数没有返回值,则只使⽤return语句,后不加任何表达式。在使⽤return语句时,要注意如果代码之前在堆中分配了内存,则在return语句前要释放内存,以防⽌内存泄漏。
exit语句⽤于终⽌当前进程,通常⽤于结束当前的应⽤程序。exit包含⼀个整型参数,⽤于标识退出的代码,与return不同的是,return 语句只退出当前调⽤的函数,除⾮当前函数是主函数,return会结束当前进程,⽽exit会直接结束当前进程,⽆论当前函数是否是应⽤程序的主函数。
4.C++函数相关
在定义函数时,若函数返回类型不是void类型,⼀定要在函数中加⼊return语句;
若调⽤函数处于被调⽤函数下⽅,则要对被调⽤函数进⾏前置声明,声明也可放置于main函数内部。
若函数有多个参数,应保证默认参数出现在参数列表的最右⽅。
在定义数组参数、函数参数时,也可不指定数组的⼤⼩,在调⽤C++编译器时不对数组长度进⾏检查,只是将数组⾸地址传给函数,但是使⽤数组的引⽤会强制检查参数中元素的个数,这可以更好地确定程序的安全。
值传递:在函数调⽤时,将实际参数的值复制⼀份传递到调⽤函数中,实际参数值不受影响;
引⽤传递:即传址调⽤,按引⽤传递时,调⽤函数修改了参数值,实际参数值也会发⽣改变。
因此,通常在定义函数时,如果参数为数组、指针或引⽤类型,则⽤引⽤类型传递⽅式,否则⽤值传递⽅式。
内联函数:
内联函数是指对于程序中出现函数调⽤的地⽅,如果是内联函数,编译器直接将函数代码复制到函数调⽤的地⽅,省去了程序跳转的过程,⽤inline关键字标识内联函数。内联函数的⽬的是提⾼程序的执⾏效率。
对于使⽤inline关键字的内联函数,程序不⼀定将函数作为内联函数对待,这关键取决于编译器的优先机制。通常对于代码较少、频繁调⽤的函数,可以标识为内联函数,否则不使⽤内联函数。
函数的重载:
重载是多个函数具有相同的函数名称,⽽参数类型或参数个数不同,在调⽤时编译器正是通过参数类型和参数个数来区分调⽤哪个函数。
返回值不作为区分重载的⼀部分;对于参数来说,const关键字不作为区分,但若参数是指针或引⽤类型,const作为区分标志;参数默认值不作为区分标志;
注意:在局部域中定义声明函数,将使同名函数隐藏⽽不是重载,要访问其他域函数,⽤::域访问符。
函数指针:
在C++语⾔中,函数名实际上是指向函数的指针,定义有如下⼏种形式:
对于函数指针来说,它指向的函数必须与函数指针定义的形式相同,即返回类型、参数、参数个数均相同。在程序中使⽤函数指针可以极⼤的丰富程序的灵活性。
5.局部作⽤域和全局作⽤域
局部作⽤域描述的是函数体中变量、常量等对象的作⽤范围,对于处于同⼀个作⽤域的对象来说,对象不允许重名。当编译器在当前代码处发现变量名时,它将在当前局部作⽤域中搜索变量的定义,如果未定义则向外搜索,直到局部作⽤域完成;
全局作⽤域是函数、变量、常量对象作⽤范围是整个应⽤程序。对于全局变量,若未初始化,其存储区为0,对于局部变量,若未初始化,则其值是不可预见的。
6.定义和使⽤命名空间
在⼀个应⽤程序中的多个⽂件可能存在同名的全局对象,这样会导致应⽤程序的链接错误,使⽤命名空间是消除命名冲突的最佳⽅式。命名空间的定义格式如下:
若要使⽤命名空间中的对象,则要在对象前加上命名空间的名字,若要访问命名空间的多个对象,可以使⽤using命令,using的作⽤域是从当前引⽤处到当前作⽤域结束。
对于同⼀个命名空间,可以在多个⽂件中定义,此时命名空间中的内容是各个⽂件中内容之和。命名空间也可嵌套,访问内层函数时,可以⽤using namespace 外层名称::内层名称。
using命令⽤于引⽤命名空间中的全部空间对象,容易和正在编写的程序中的局部变量发⽣冲突,导致命名空间的变量被隐藏,故不建议过多使⽤此命令。不命名的空间中的对象只适⽤于当前⽂件,各⽂件中不能相互访问,在⼀个⽂件中访问空间中的对象与访问普通全局对象是相同的。
7.函数模板
函数模板提供了⼀种机制,使函数返回值、参数类型能够被参数化,⽽函数体保持不变,这极⼤的增强了函数的灵活性。定义格式如下:
函数模板也可以重载,要注意区分。
⾯向对象的基本任务是描述对象并对对象进⾏归类总结。类类型与int类型⼀样,也没有任何内存分配。类的属性和对外接⼝是类定义的重点和难点,原则是尽量让内部操作私有化,提供简单易⽤的接⼝函数。
1、类的相关问题
在定义类的数据成员时,不能像定义变量⼀样进⾏初始化,若在定义时未指明访问限定符,默认为private;
在定义类的⽅法时,若⽅法中不⽤修改类的数据成员,则最好在⽅法声明的最后使⽤const关键字,表⽰⽤户不能在此⽅法中修改类的数据成员。若类中包含指针成员,在const⽅法中不可以重新为指针赋值,但可以修改指针所指向的地址中的数据。
类中,除静态成员可以直接访问外,其它成员是通过对象来实现访问的。在定义类时并没有分配存储空间,只有当实例化对象时,才分配存储空间。也可以将类对象声明为⼀个指针,并使⽤ new运算符为其分配内存,如下所⽰:
若将类对象声明为常量指针,则只能调⽤类中的常量⽅法。
2、构造函数与析构函数
每个类都有构造函数与析构函数,构造函数在定义对象时被调⽤,析构函数在对象释放时被调⽤。构造函数负责类对象⽣成之前的初始化,析构函数负责对象销毁后的处理。
构造函数没有返回值,若⽤户没有提供构造函数和析构函数,则系统使⽤默认的构造析构函数。⼀个类可以包含多个构造函数,各函数通过重载来进⾏区分,下⾯是指针调⽤参数构造函数进⾏初始化:
类体中定义的数据成员不能初始化,故类中初始化只能借助构造函数的初始化列表实现。类成员函数,若在类体中定义,函数前即使没有使⽤inline,该成员函数也被认为是内联函数。
析构函数没有返回值,也没有参数,故不能重载。
3、复制构造函数
复制构造函数与类的其它函数构造类似,以类名作为函数的名称,第⼀个参数为该类的常量引⽤类型。
下⾯的三种情况要⽤到复制构造函数:
对象以值传递的⽅式传⼊函数参数;
对象以值传递的⽅式从函数返回;
对象需要通过另外⼀个对象进⾏初始化;
编译器有默认的拷贝构造函数,这个函数仅仅是使⽤⽼对象的数据成员的值对“新对象”数据成员⼀⼀进⾏赋值,这称为浅拷贝,即只是对象中数据成员进⾏简单的赋值,这种⽅式在对象中存在动态成员时则会出现问题,⽐如对象中的指针。浅拷贝只是使两个指针有相同的值,⽽不是我们需要的两块不同的地址。
深拷贝,对于对象中动态成员,不仅仅赋值,还重新动态分配空间,从⽽使得指针指向两块不同的内存,但内存中的值相同。
在编写函数时,尽量按引⽤⽅式传递参数,这样可以避免调⽤复制构造函数,可以极⼤地提⾼程序效率。也可以将拷贝函数放在private 中,从⽽防⽌默认拷贝的发⽣。
4、静态类成员
普通类成员只能通过实例化对象访问,静态类成员还可以通过类名直接访问,访问时⽤::域访问符。在定义静态数据成员时,要在类体外部对静态数据成员初始化。静态数据成员是被所有类对象共享的。
静态数据成员可以是当前的类型,⽽其他数据成员只能是当前类的指针或引⽤类型,如:
针对静态数据成员有如下⼏点:
静态数据成员可以作为成员函数的默认参数,但是普通成员不可以;
类的静态成员函数只能访问类的静态成员,不能访问普通数据成员;
静态成员函数末尾不能⽤const关键字修饰;
静态数据成员和静态成员函数在类体之外初始化或定义时,去掉static关键字;
5、运算符重载
运算符重载要⽤到operator关键字,它其实是函数重载的⼀种,因为运算符本来就是⼀个函数。对于重载的运算符,两个函数不能交换顺序,重载是什么顺序,只能⽤这各顺序调⽤。
对于++和--运算符,由于涉及前置和后置,故默认情况下,重载运算符没有参数,表⽰是前置运算;若⽤整型int作为参数,则表⽰后置运算。
并不是所有的运算符都可以重载,⼤多数是可以重载的,但是::,?,:,. 是不能重载的。
6、有关类的sizeof问题
空类也会被实例化,编译器会给类隐含添加⼀个字节,故空类的sizeof()结果为1;sizeof()⽤来计算字符串的长度时包含”\0”,strlen()统计的长度不包含”\0”。
构造函数、析构函数都不归⼊sizeof()统计范围之内。
虚函数由于要维护在虚函数表中的位置,故要占据⼀个指针的⼤⼩。
静态成员也不归⼊sizeof()统计范围。
总起来说,类的⼤⼩与⾮静态成员⼤⼩和虚函数有关,与其他普通成员函数⽆关。类的⼤⼩也遵守内存分配时的字节对齐规则:
7、友元类与友元⽅法
当⽤户希望另⼀个类可以访问当前类的私有成员时,可以在当前类中将另⼀个类作为⾃⼰的友元类。友元即朋友,私有成员只有朋友可以访问。
若只想让某个成员函数访问类的私有成员,则可以将此函数声明为类的友元函数,即在函数返回值前加上friend关键字。友元函数不仅可以是类成员函数,也可以是全局变量函数。
8、引⽤和指针的区别
引⽤是⼀个变量的别名,引⽤被创建的同时必须被初始化,指针可以在任何时候被初始化;
不能有NULL引⽤,引⽤必须与合法的存储单元关联,指针则可以是NULL;
引⽤⼀旦被初始化,就不能改变引⽤关系,指针则可以随时改变所指的对象。
9、类的继承
继承是⾯向对象的主要特征之⼀,它使⼀个类可以从现有类中派⽣,⽽不必重新定义⼀个新类,类继承时使⽤“:”运算符。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论