C++大学基础教程笔记(二)
33.动态内存的初始化:double *ptr = new double( 3.14159 ); Time *timePtr = new Time( 12, 45, 0 );
34.动态分配数组int *gradeArray = new int[10]; 当动态分配一个对象数组时,程序员无法将参数传递给每个对象的构造函数。相反,每个在数组里的对象由自身的默认构造函数初始化。对于基本数据类型,每个元素都被初始化为0,或与0相等的值(如char被初始化 为'\0')。
35.如果delete [] gradesArray;的指针指向一个对象数组,那么语句首先调用数组中的每个对象的析构函数,然后再回收空间。如果前
述的语句不包含方括号并且gradesArray指向一个对象的数组,那么只有数组中的第一个对象接受析构函数调用。对空指针的delete操
作并无任何效果。
36.复制构造函数:
声明:Array( const Array & );
定义:Array::Array( const Array &arrayToCopy ): size( arrayToCopy.size ){ ptr = new int[ size ]; for( int i = 0; i < size; i++) { ptr[ i ] = arrayToCopy.ptr[i];} }
使用:Array integers3( integers1 );
如果使用默认的复制构造函数,那么只是把源对象中的指针复制到目标对象的指针,那么两个对象都会指向同一块动态分配的内存。
复制构造函数的参数应当是一个const引用,以允许复制const对象。
请注意:复制构造函数必须按引用接收参数,而非按值。否则,复制构造函数的调用会导致无穷递归。因为按值接收对象时,需要复
制构造函数生成实参对象的副本。回想一下,无论何时需要对象的副本,都会调用类的复制构造函数。
37.在string类中,重载的[]运算符并不进行任何边界检查。因此,程序员必须保证使用标准string类中重载的[]运算符的操作,不会意
外的操作string对象有效范围外的元素。标准string类的确在其成员函数at中提供了范围检查。
38.任何但参数的构造函数都可以被编译器用来执行隐式转换,即构造函数接收的类型会转换成定义了该构造函数的类的对象。C++提供
了关键字explicit,用于禁止不应该允许的由转换构造函数完成的隐式转换。即声明成explicit的构造函数不能在隐式转换中使用。
39.定义:public: explicit Array(int = 10); private: int size;
调用:outputArray(3); 这样做编译器会产生一条错误信息,指出传递给outputArray的整数值无法转换成const Array &。
正确的方法:outputArray( Array(3) );
40.要对类的对象使用运算符,除了运算符赋值(=)、取址(&)、和逗号(,)以外,其他的运算符都必须重载。
41.基类的protected成员既可以被基类的成员和友元访问,又可以被由基类派生的任何类的成员和友元访问。
42.double BasePlusCommissionEmployee::earnings() const{ return getBaseSalary() + CommissionEmployee::earnings(); } 请注意:由已重新定义基类成员函数的派生类调用基类的函数时的语法格式,即在基类成员函数名前加基类名和二元作用域分辨符 (::)。
43.假设我们创建一个派生类的对象,这个派生类及其基类中都包含其他类的对象。当这个派生类的对象被创建时,首先执行基类成员对
象的构造函数,然后执行基类的构造函数,接着执行派生类成员对象的构造函数,最后执行派生类的构造函数。派生类对象析构函数
的调用顺序,与相应的构造函数的调用顺序相反。
44.多态性:同样的消息在发送给各种不同的对象时会产生多种形式的结果。
45.基类的指针可以指向派生类对象,但调用的仍然是基类的成员函数。这说明,被调用的功能取决于用来调用函数的句柄(如指针或引 用)类型,而不是句柄所指向的对象类型。可是,如果利用virtual函数,调用哪个版本的virtual函数就由句柄所指向的对象的类型来决定,而非句柄类型。在执行时(而不是在编译时)选择合适的调用函数称为动态绑定。
46.经证实,C++编译器确实允许通过指向派生类对象的基类指针访问只在派生类中拥有的成员,只要显示地把这样的基类指针强制转换 为派生类指针,这就是向下强制类型转换。
47.通过声明类的一个或多个virtual函数为纯virtual函数,可以使一个类成为抽象类。一个纯virtual函数是在声明时“初始化值为0” 的函数,如下所示:virtual void draw() const = 0; “=0”称为纯指示符。还有:virtual函数不一定非要声明为const.
48.virtual函数和纯virtual函数的区别:virtual函数有函数的实现,并且提供派生类是否重写这些函数的选择权。相反,纯virtual函 数并不提供函数的实现。
49.试图实例化抽象类的对象将导致编译错误。
50.抽象类至少含有一个纯virtual函数。抽象类也可以有数据成员和具体的函数(也包括构造函数和析构函数),它们被派生类继承时都符合继承的一般规则。
51.多态性、virtual函数和动态绑定的底层实现机制:多态性通过三级指针来实现
1.当C++编译含有一个或多个virtual函数的类时,它为这个类创建了一个virtual函数表(简称vtable)。每次调用该类的virtual函
数时,运行程序都会利用virtual函数表选择正确的函数实现。
2.第二级指针,无论何时当实例化具有一个或多个virtual函数的类的对象时,编译器给这个对象附上一个指针,指向对象所属类的
virtual函数表。
3.第三极指针,仅仅包含接收virtual函数调用的对象句柄。
每次virtual函数调用时发生的指针间接引用操作和内存访问,都需要增加程序执行时间。而virtual函数表和加入对象的vtable指针
也要占用额外的空间。
52.virtual析构函数:
如果要删除一个具有非虚析构函数的派生类对象,却显示地通过指向该对象的一个基类指针,对它应用delete运算符,那么C++标准 会指出这一行为未定义。解决这一问题的方法就是在基类中创建一个virtual析构函数(也就是声明析构函数时使用关键字virtual)。现在,如果对一个基类指针用delete运算符来显示删除它所指的类层次中的某个对象,那么系统会根据该指针所指对象调用相应类的析构函数。
53.运算符typeid返回一个对type_info对象的引用。
例:cout << "delete object of" << typeid( *employees[j] ).name() << endl;
type_info的成员函数name返回一个基于指针的字符串,它包含传递给typeid实参的类型名称(例如“class BasePlusCommission- Employee”)。使用typeid必须包含头文件<typeinfo>。
53.向下强制类型转换:
// downcast pointer
TwoDimensionalShape *twoDimensionalShapePtr =
dynamic_cast < TwoDimensionalShape * > ( shapes[ i ] );
if ( twoDimensionalShapePtr != 0 )
cout << "Area: " << twoDimensionalShapePtr->getArea() << endl;
54.函数模板 程序员只需要写一个单函数模板的定义,而编译器产生不同的目标代码函数(也就是函数模板特化)来适当的处理每个函数调用。
55.模板定义
每个表示类型的模板形参前面必须加上关键字class或者typename,如:template< typename T > 或 template< class T > 或 template< typename BorderType,typename FillType >
函数模板定义中的摸板形参类型是用来指明函数实参的类型、函数返回值类型及声明函数中的变量的。
如果T是用户自定义的类型,则每种类型都必须有一个重载的流插入运算符。
56.模板和继承的注意事项
类模板可以从类模板特化派生得到、类模板可以从非类模板特化派生得到
类模板特化可以从类模板特化派生得到、非类模板可以从类模板特化派生得到
57.每一个由相同类模板实例化产生的类模板特化都有他自己的类模板静态数据成员的副本。一个模板类特化产生的所有对象共享一个静 态数据成员。
58.假设我们想打印一个表示字符串的char *的值(也就是字符串第一个字符的内存地址)。然而,<<运算符已被重载为将char *数据类
型作为以空字符结尾的字符串来打印。解决的方法就是将char *强制转化为void *类型(事实上,如果程序员想输出一个地址,那么
都应该对指针变量进行这样的转换)。
59.流提取运算符通常跳过输入流中的空白字符(例如空格、制表符和换行符)。在每个输入操作之后,流提取运算符给接收到所提取的
信息的流对象返回一个引用(例如在表达式cin >> grade中的cin)。如果引用被用作判断条件,那么将隐式调用流重载的void *强
制转换运算符函数(将流转换为指针),根据最后输入操作的成功与否将引用转换为非空指针或者空指针值。非空指针转化为bool值
true,表示输入操作成功;空指针转化为bool值false。当试图越过流的末尾进行读取操作时,流重载的void *强制转化运算符返回一
个空指针,表示已经读到文件的末尾。
60.输入流成员函数getline从流中移除分隔符(也就是读取该字符然后丢弃),没有将其放在字符数组内存储。
61.重载的<<和>>可以接收各种指定类型的数据项,如果遇到意料之外的数据类型,各种相应的错误位就会被设置,用户可以通过检测错 误位来判断I/O操作是否成功。例如,当输入错误的数据类型时,流提取的failbit状态位被设置;当操作失败时,流的badbit位被设置。
62.流的格式状态和流操纵符
skipws 跳过输入流的空白字符。使用noskipws重置设定
left 域的输出左对齐
right 域的输出右对齐
dec 整数要以十进制显示
oct 整数要以八进制显示
hex 整数要以十六进制显示
showpoint 指明浮点数必须显示小数点。通常使用fixed流操纵符来确保小数点右边数字的位数,即使全部为零。可以使用操
纵符noshowpoint重置该设定
uppercase 指明当显示十六进制数时使用大写字母(也就是X和A~F),并且在科学计数法表示浮点数时使用大写字母E。可以使用
用流操纵符nouppercase重置该设定
showpos 在正数前显示加号(+)。可以使用流操纵符noshowpos重置该设定
scientific 以科学计数法输出显示浮点数
fixed 以定点小数形式显示浮点数,并指定小数点右边的位数。
浮点数的默认精度为6.
63.C++提供成员函数tie来使istream和ostream操作同步,确保输出在接下来的输入操作之前被显示。函数调用
cin.tie(&cout);
从输出流上解除对输入流inputStream的连接
inputStream.tie(0);
64.通过成员函数flags设置和重置格式状态
ios_base::fmtflags originalFormat = cout.flags();
cout.flag(originalFormat);
没有参数的流成员函数flags以fmtflags数据类型返回当前的格式设定;拥有fmtflags参数的flags流成员函数设置flags状态变量并
返回先前的状态设置。
65.实例
#include <string>
string input;
char buf[10];
cin >> input; //遇到空白字符就会结束,可使用skipws来跳过空白字符。
cin >> buf; //接收输入的所有字符,但是不能超过数组长度。