两周的c++学习,花了两天的时间整理了一下在中国Mooc,北大面向对象程序设计课程的内容。
目录:
点击下列蓝色字体,可转到相应位置:
1.引用
2.函数重载
3.类和对象
4.类和对象的提高
5.运算符重载
6.继承
7.多态
8.函数模板
9.输入与输出
引用
- 定义引用时一定要将其初始化成引用某个变量;
- 初始化后,它就一直引用该变量,不会再引用别的变量了;
- 引用只能引用变量,不能引用常量和表达式;
- 不能通过常引用去修改其引用的内容;
- T & 类型的引用或T类型的变量可以用来初始化 const T & 类型的引用;
- 不可通过常量指针修改其指向的内容,可以进行强制转换来赋值;但指针的指向可以发生变化;
函数重载
- 一个或多个函数,名字相同,然而参数个数或参数类 型不相同,这叫做函数的重载。;
- C++中,定义函数的时候可以让最右边的连续若干个参 数有缺省值,那么调用函数的时候,若相应位置不写参 数,参数就是缺省值;
- 函数参数可缺省的目的在于提高程序的可扩充性;
- 内联函数只需要在函数声明及定义前添加
inline
类和对象
- 在
class
中无关键字时,默认为private成员;而在struct
无关键字时,默认是public成员; - 在类的成员函数内部,能够访问:
– 当前对象的全部属性、函数;
– 同类其它对象的全部属性、函数。 - 在类的成员函数以外的地方,只能够访问该类对象的 公有成员;
- 使用缺省参数要注意避免有函数重载时的二义性;
- 构造函数可默认生成以及自定义;
- 对象生成时构造函数自动被调用;对象一旦生成,就再也不能在 其上执行构造函数;
- 在数组中的调用赋值:
class Test { public:
Test( int n) { }//(1)
Test( int n, int m) { } //(2)
Test() { } //(3)
};
Test array1[3] = { 1, Test(1,2) }; // 三个元素分别用(1),(2),(3)初始化
Test array2[3] = { Test(2,3), Test(1,2) , 1}; // 三个元素分别用(2),(2),(1)初始化
Test * pArray[3] = { new Test(4), new Test(1,2) }; //两个元素分别用(1),(2) 初始化
- 构造函数在构造数组对象时,数组有多少个元素就调用多少次构造函数;但是如果在数组容量大于所给的赋值情况,那么如:
A * arr[4] = { new A(), NULL,new A() };
只有两个对象生成,需要明确地址; - 在复制构造函数中,只有一个参数,即类对象的引用;
- 复制构造函数可以默认生成或者自定义;
- 不允许有形如
类名::类名( 类名 )
的构造函数; - 复制构造函数起作用的三种情况:
1)当用一个对象去初始化同类的另一个对象时
2)如果某函数有一个参数是类A 的对象, 那么该函数被调用时,类A的复制构造函数将被调用;
如果函数的返回值是类A的对象时,则函数返回时, A的复制构造函数被调用; - 对象间赋值并不导致复制构造函数被调用,初始化除外;因为赋值是用来修改一个已经存在的对象的值,此时没有任何新对象被创建;
- 最好使用常量引用参数(加const),因为可以减少时间开销;避免复制构造函数的大量调用;
- 类型转换构造函数就是建立一个无名的临时对象(或临时变量);
- 一个类最多有一个析构函数,而可以有不止一个的构造函数;
- 对象数组生命期结束时,对象数组的每个元素的析构函数都会被调用;
delete
运算导致析构函数调用;- 若new一个对象数组,那么用delete释放时应该写 []。否则只delete一个对 象(调用一次析构函数);
- 析构函数在对象作为函数返回值返回后被调用,即被使用过后,临时对象就消亡;
- 本地静态对象具有存在范围直到程序的生命周期;
类和对象的提高
this
指针其作用就是指向成员函数所作用 的对象;- 非静态成员函数中可以直接使用this来代表指向该函数作用的对象的指针;
- 并不是每一个对象都有一个
this
指针,而是每一个非静态的成员函数具有一个this
指针; - 普通成员变量每个对象有各自的一份,而静态成员变量一共就一份,为所有对象共享,因此静态成员不需要通过对象就能访问,本质上是全局变量,而静态成员函数本质上是全局函数,但是借助对象访问也无妨;
- 当计算类的大小时,sizeof 运算符不会计算静态成员变量;
- 如何访问静态成员,见链接:
- 必须在定义类的文件中对静态成员变量进行一次说明或初始化。否则编译能通过,链接不能通过;
- 在静态成员函数中,不能访问非静态成员变量, 也不能调用非静态成员函数;
- 注意在某些时候复制构造函数与析构函数对程序的隐秘影响;
- 有成员对象的类叫 封闭(enclosing)类;
- 任何生成封闭类对象的语句,都要让编译器明白,对象中的成员对象,是如何初始化的;那么一般情况下通过封闭类的构造函数的初始化列表进行初始化;
- 注意封闭类构造函数和析构函数的执行顺序。构造函数是先内部再外部整体(封闭类);析构函数是先外部整体(封闭类),再内部对象的析构函数;两者的次序相反;
- 对象成员的构造函数调用次序和对象成员在类中的说明次序一致 ,与它们在成员初始化列表中出现的次序无关;
- 封闭类的对象,如果是用默认复制构造函数初始化的,那么它里面包含的成员对象,也会用复制构造函数初始化;
- 一个类的友元函数可以访问该类的私有成员;该函数可以是另一个类的内部成员函数,也可以是全局的成员函数;
- 如果A是B的友元类,那么A的成员函数可以访问B的私有成员;
- 友元类之间的关系不能传递,不能继承;
- 常量成员函数内部不能改变属性的值,也不能调用非常量成员函数;注意在定义常量成员函数和声明常量成员函数时都应该使用const关键字;
- 在类的成员函数说明后面可以加const关键字,则该成员函数成为常量 成员函数;
- 如果一个成员函数中没有调用非常量成员函数,也没有修改成员变量的值,那么最好将其 写成常量成员函数;
- 两个函数,名字和参数表都一样,但是一个const,一个不是,是重载;
- mutable - 容许在即便包含它的对象被声明为 const 时仍可修改声明为 mutable 的类成员;
运算符重载
- 运算符重载使得参与运算的内容不再局限于整型、实型、字符型、逻辑型等等;
- 运算符重载的目的是:扩展C++中提供的运算符的适用范围,使之 能作用于对象;
- 运算符重载的实质是函数重载;可以重载为普通函数,也可以重载为成员函数;
- 重载为成员函数时,参数个数为运算符目数减一;重载为普通函数时,参数个数为运算符目数;
- 赋值运算符“=”只能重载为成员函数;
- 初始化语句,要用到构造函数,而不是赋值运算符
=
;非初始化语句一般会用到重载; - 如果没有经过重载,
=
的作用就是把左边的对象的每个成员变量都变得和右边的对象相等,即执行逐个字节拷贝的工作,这种拷贝叫作“浅拷贝”,即两者的指向地址相同,就是同一个地址; - 将一个对象中指针成员变量指向的内容复制到另一个对象中指针成员变量指向的地方。这样的拷贝就叫“深拷贝",但是两者的地址仍然不一样;
- operator = 返回值类型一般是引用;
- 一般情况下,将运算符重载为类的成员函数,是较好的选择;
- 有时候,重载为成员函数不能满足使用要求,重载为普通函数,又不能访问类的私有 成员,所以需要将运算符重载为友元;
- 注意
c = c.operator +(5);
相当于c = c + 5
,但是不能使用c = 5 + c
会出现编译错误;只有将operator+重载为普通函数才能完成交换后还能运算,如Complex operator+ (double r,const Complex & c)
,并且在类中声明为友元函数即可,即可访问类的是私有成员; - cout 是在 iostream 中定义的,ostream 类 的对象;
- “<<” 能用在cout 上是因为,在iostream 里对 “<<” 进行了重载;
- 重载成 ostream类的成员函数:
ostream &ostream::operator<<(int n) {
…… //输出n的代码 return * this;
}
- string字符串是常量;
- 前置运算符作为一元运算符重载;后置运算符作为二元运算符重载,多写一个没用的参数;
- 重载运算符()、[]、->或者赋值运算符=时,运算符重载函数必须声明为 类的成员函数;
- 重载强制类型转换运算符时,不需要指定返回值类型,因为返回值类型是确定的,就是运算符本身代表的类;
继承
- 继承:在定义一个新的类B时,如果该类与某 个已有的类A相似(指的是B拥有A的全部特点), 那么就可以把A作为一个基类,而把B作为基 类的一个派生类(也称子类);
- 派生类拥有基类的全部成员函数和成员变 量,不论是private、protected、public;
- 注意:每一个“继承方式”,只用于限制对紧随其后之基类的继承;
- 派生类对象的体积,等于基类对象的体积,再加上派 生类对象自己的成员变量的体积。在派生类对象中,包 含着基类对象,而且基类对象的存储位置位于派生类对 象新增的成员变量之前;
- 在派生类中使用基类的函数时,需要加上
基类类名::function
; - 基类A,B是基类A的派生类,即一个B对象也是一个A对象;
- 继承是“是”的关系;复合是“有”的关系;
- 派生类可以定义一个和基类成员同名的成员,这叫 覆盖。在派生类中访问这类成员时,缺省的情况是 访问派生类中定义的成员。要在派生类中访问由基 类定义的同名成员时,要使用作用域符号::。
-
基类的private成员:可被基类的成员函数和基类的友元函数访问; 基类的public成员都可访问; 基类的protected成员:可被基类的成员函数,基类的友元函数和派生类的当前对象的成员函数;
class Son :public Father{
void AccessFather () {
nPublic = 1; // ok;
nPrivate = 1; // wrong
nProtected = 1; // OK,访问从基类继承的protected成员
Son f;
f.nProtected = 1; //wrong ,f不是当前对象
}
};
-
在创建派生类的对象时:
先执行基类的构造函数,用以初始化派生类对象中从基类 继承的成员; 再执行成员对象类的构造函数,用以初始化派生类对象中成员对象; 最后执行派生类自己的构造函数;
-
派生类的析构函数被执行时,执行完派生类的析构函数后,自动调用基类的析构函数;
派生类的对象可以赋值给基类对象;
派生类对象可以初始化基类引用;
派生类对象的地址可以赋值给基类指针;
protected继承时,基类的public成员和protected成员成为派生类的protected成员;
private继承时,基类的public成员成为派生类的private成员,基类的protected成员成 为派生类的不可访问成员;
protected和private继承不是“是”的关系。
-
派生类的成员包括:
派生类自己定义的成员; 直接基类中的所有成员; 所有间接基类的全部成员;
多态
-
在类的定义中,前面有 virtual 关键字的成员函 数就是虚函数;
-
virtual 关键字只用在类定义里的函数声明中, 写函数体时不用;
-
通过基类指针调用基类和派生类中的同名虚函数时:
(1)若该指针指向一个基类的对象,那么被调用是 基类的虚函数; (2)若该指针指向一个派生类的对象,那么被调用 的是派生类的虚函数;
这种机制就叫做“多态”;
4. 通过基类引用调用基类和派生类中的同名虚函数时:
(1)若该引用引用的是一个基类的对象,那么被调 用是基类的虚函数;
(2)若该引用引用的是一个派生类的对象,那么被 调用的是派生类的虚函数;
这种机制也叫做“多态”。
- 在面向对象的程序设计中使用多态,能够增强 程序的可扩充性,即程序需要修改或增加功能 的时候,需要改动和增加的代码较少;
- 用基类指针数组存放指向各种派生类对象的指 针,然后遍历该数组,就能对各个派生类对象 做各种操作,是很常用的做法;
- 在构造函数和析构函数中调用虚函数,不是多态;即在非构造函数,非析构函数的成员 函数中调用虚函数,是多态;
- 派生类中和基类中虚函数同名同参数表的函数,不加virtual也自动成为虚函数;
- 注意如果两个函数在基类和派生类中都是虚函数,并且在基类的虚函数是
public
类型,派生类的虚函数是private
时,仍然可以通过基类指向的派生类的对象的指针访问派生类的私有虚函数; - 多态”的关键在于通过基类指针或引用调用 一个虚函数时,编译时不确定到底调用的是基类还 是派生类的函数,运行时才确定 ---- 这叫“动态联编”;
- 通过基类的指针删除派生类对象时,通常情况下只调用基类的析构函数;但是,删除一个派生类的对象时,应该先调用派生类的析构函数,然后调用基类的析构函数;解决办法: 把基类的析构函数声明为virtual 派生类的析构函数可以virtual不进行声明 通过基类的指针删除派生类对象时,首先调用派生类的析构函 数,然后调用基类的析构函数;
- 一般来说,一个类如果定义了虚函数,则应该将析构函数也定义成虚函数。或者,一个类打算作为基类使用,也应该将析构函数定义 成虚函数;
- 不允许以虚函数作为构造函数;
- 纯虚函数:没有函数体的虚函数;
- 抽象类只能作为基类来派生新类使用,不能创建抽象类的对象;
- 抽象类的指针和引用可以指向由抽象类派生出来的类的对象;简单说来,就是只能做基类;但相比基类,缺少了定义类的性质,应该就是基础了,不能成类了;
- 在抽象类的成员函数内可以调用纯虚函数,但是在构造函数或析构函数内部 不能调用纯虚函数;
- 如果一个类从抽象类派生而来,那么当且仅当它实现了基类中的所有纯虚函 数,它才能成为非抽象类;
函数模板
-
函数模板中可以有不止一个类型参数;
-
不通过参数实例化函数模板;
-
函数模板可以重载,只要它们的形参表或类型参数表不同即可;
-
在有多个函数和函数模板名字相同的情况下,编译器如下处理一 条函数调用语句:
1) 先找参数完全匹配的普通函数(非由模板实例化而得的函数)。 2) 再找参数完全匹配的模板函数。 3) 再找实参数经过自动类型转换后能够匹配的普通函数。 4) 上面的都找不到,则报错。
-
类模板:在定义类的时候,加上一个/多个类型参数。在使用类模板时,指 定类型参数应该如何替换成具体类型,编译器据此生成相应的模板类;
-
编译器由类模板生成类的过程叫类模板的实例化。由类 模板实例化得到的类,叫模板类;
-
类模板的“<类型参数表>”中可以出现非类型参数;
-
类模板与派生的关系:
• 类模板从类模板派生 • 类模板从模板类派生 • 类模板从普通类派生 • 普通类从模板类派生
输入与输出
istream是用于输入的流类,cin就是该类的对象;
ostream是用于输出的流类,cout就是该类的对象;
ifstream是用于从文件读取数据的类;
ofstream是用于向文件写入数据的类;
iostream是既能用于输入,又能用于输出的类;
fstream 是既能从文件读取数据,又能向文件写入数据的类;
- cerr和clog的区别在于cerr不使用缓冲区,直接向显示器输出信 息;而输出到clog中的信息先会被存放在缓冲区,缓冲区满或者 刷新时才输出到屏幕;
istream & getline(char * buf, int bufSize);
和istream & getline(char * buf, int bufSize,char delim);
两个函数都会自动在buf中读入数据的结尾添加\0’。‘\n’
或delim
都不会被读入buf
,但会被从输入流中取走。如果输入流中‘\n’
或delim
之前的字符个数达到或超过了bufSize
个,就导致读 入出错,其结果就是:虽然本次读入已经完成,但是之后的读入就 都会失败了;- 可以用
if(!cin.getline(…))
判断输入是否结束; int peek();
返回下一个字符,但不从流中去掉;hex
16进,dec
10进制,oct
8进制;width
成员函数宽度设置有效性是一次性的,在每次读入和 输出之前都要设置宽度;