学习笔记
取余运算
x%y的计算规律为:x-(x/y)*y, 操作数x,y只能是整数,浮点数不能参与运算
5%-3的值为2,-5%3的值为-2
增量运算 ++n,n++
int n=1;
计算++n时,首先递增n的值至2,然后把n的值2作为表达式++n的值。
计算n++时,首先把n值保存在一个临时变量t中,然后递增n的值至2,最后把临时变量t的值作为表达式n++的值。
字符串操作
include<string.h>
strpy | 字符串复制 |
strcat | 字符串连接 |
strlen | 求字符串长度 |
strcmp | 字符串比较 |
strrev | 反转字符串 |
strchr | 在字符串中查找字符 |
strstr | 在字符串中查找另一个字符串 |
strlwr | 将字符串中的大写字母转换为小写字母 |
strupr | 将字符串中的小写字母转换为大写字母 |
内联函数 inline
调用函数是需要一定的时间开销和空间开销,如果有的函数需要频繁地使用,所需要的时间会很长,从而降低了程序的执行效率。
内联函数在编译时将所调用的函数的代码直接嵌入到主调函数中,在执行时不需要将流程的控制转移到被调用函数的入口,可以起到节约时间开销的作用。
指定内联函数:inline 返回类型 函数名(参数表)
可以在生命和定义函数是同时写inline,也可以只在其中一处声明inline,效果相同。
有默认参数的函数
实参和形参的结合是从左到右进行的,所以指定默认值的参数必须放在形参列表的最右端。
注意:
1.若函数的定义在函数调用之前,则应在函数定义中给出默认值。
若函数的定义在函数调用之后,则在函数之前需要有函数声明,且此时必须在函数声明中给出默认值。在函数定义时可以不给出参数的默认值
2.一个函数不能急作为重载函数,有作为有默认参数的函数,因为当调用函数少写一个参数,系统无法判定是利用重载函数还是利用默认参数的函数。
函数重载
对一个函数名重新赋予它新的含义,使一个函数名可以多用。函数名相同,函数的参数个数或者参数类型不同的函数就是重载函数。
1. int fun(int ,double)
2. int fun (double, int)
3. void fun(int ,double)
1和2可以重载,2和3可以重载,但是1和3不可以重载(只有返回值类型不同不能重载,因为调用函数时无法区分调用的是哪个函数)
重载函数选择查找顺序:将实参类型无所有被调用的重载函数的类型以一比较:
先查找的时严格匹配的,在查找的时通过类型转换可以匹配的,最后是通过强制类型转换达到匹配的。 ????????P66
函数模板
定义方法: template<typename T> 或 template<class T>
返回类型 函数名(形参表)
其中返回类型和形参类型可以为模板类型T
还可以定义多个参数类型 template<typename T1,typename T2>
函数调用过程
1.建立被调用函数的栈空间
2.保存调用函数的运行状态和返回地址;
3.传递参数
4.把程序控制转交给被调用函数
变量的存储类型
自动存储类型(auto):只能是局部变量,不能为全局变量。生命局部变量时,默认为自动类型
寄存器类型(register):不分配内存空间,直接用CPU中的寄存器存储。用于不需长期保存的变量
静态存储类型(static):全局变量和静态类型变量。在程序开始运行时分配存储空间,程序结束时才释放空间。(必须要在程序运行到这个变量的定义语句时才分配空间:若在main后声明静态变量或全局变量,不能在main中使用)若没有赋初值,自动设为0。
外部类型(extern):必须为全局变量。两种使用情况:
1.同源程序文件中,在全局变量的定义之前使用该变量时,在使用前要对该变量进行外部类型声明。
int main(){
extern int a;
cout<<a<<endl;
}
int a=100
2.当一个源程序需要引用另一个源程序文件中的全局变量时,需要进行外部声明。
若一个文件中的全局变量不想被其他文件所调用,则必须声明为静态全局变量,即静态变量只能被他所在的文件调用。
编译预处理
c++提供的编译预处理功能主要有:文件包含、宏定义、条件编译
类与对象
结构变量 struct
定义 struct Date{
int year, month, day
};
赋值:
Date today={2018,11,28};
Date *p;p->year=2018;
Date today;today.year=2018
类的数据成员
1.在类定义体中的数据成员只是声明性质,在定义类时并没有对他们分配内存空间,因而不能在类定义体中初始化数据成员,只有当定义对象后,这些成员才随着对象的生成而存在,才可以初始化和赋值。
2.数据成员的数据类型可以是基本数据类型/指针类型,其他数据结构类型或者自身类的指针类型,但绝不能是自身类的类型
构造函数
定义方式:
类名(形参表){
}
与类名同名,但没有返回类型的函数
构造函数一般是共有的,否则对象不能调用,即不能构造对象
构造函数可以重载,也可以为参数提供默认值,系统会根据构造对象时所提供的实参信息自动匹配相应的构造函数
构造对象指针和对象引用时,不会调用构造函数,因为他们不会按照类的结构安排其内存布局,指针只存储所指向类的地址。
三个特殊的构造函数
1、默认构造函数:默认构造函数是指在调用时无需提供任何实参的构造函数,它表现为两种形式:构造函数本身不带任何参数;构造函数带若干个参数,但是每个参数都有默认值。
若类中没有定义构造函数,c++会为该类提供一份默认构造函数,该函数没有任何参数,而且函数体为空。
2、转换构造函数
转换构造函数是指能够把某种数据类型转换为类类型的构造函数,最简单情况下,使用一个构造函数就可以充当转换构造函数。如:
class Test{
double d;
public:
Test(){}
Test(double i){
d=i;
}
void myPrint(){
cout<<"Test class: "<<d<<endl;
}
};
int main(){
Test a;
a=20;
a.myPrint();
}
输出:
Test class: 20
3、复制构造函数:
形如 T( const T &)
特点为:只有一个参数;这个参数的类型必须是自身类的引用;为防止被复制对象被修改,通常将参数类型设置为常量引用。
三种情况下会被调用:
- 同类对象相互转化
- 传值调用时实参传给形参
- 值返回的方式返回对象
当类中没有定义复制构造函数时,系统会自动提供一个复制构造函数。
析构函数
定义方式:
~类名(){
}
构造函数没有返回类型也不能带参数,因而不能重载
构造函数一般是公有的,否则对象不能被撤销,即不能释放对象。
this指针的作用:
- 访问类的成员
- 防止子复制
- 防止自赋值
const 成员函数
声明方式:
返回值类型 函数名(参数列表) const{
//函数体
}
只有成员函数才有这张用法,全局函数和友元函数不可以有此种用法
友元函数和友元类
友元关系是不能传递的,单向的,不能被继承。
友元函数不是类的成员,因此对他的定义、访问都与成员不同,且友元函数不能限定为const成员函数,也不隐含this指针。
友元函数声明:
friend 类型 友元函数名 (参数列表)
在类体中声明时,在函数前加上friend关键字,在(类体外)定义时不需要且不能加friend关键字。友元函数也可以直接在类体内定义,但他不是类的成员函数。
友元函数可以直接访问类的实例中的私有成员
静态成员
类的静态成员是该类所有对象共享的数据成员,因而在整个类中只有一份,类的静态数据成员与类类型相关联,因此不管当前有没有对象生成,类的静态成员都是存在的,可以通过类名访问。
静态数据成员
不能在构造函数中初始化,否则会在程序连接时出现连接错误。
必须在类外初始化:
类型 类名::静态变量名(); 或者 类型 类名::静态变量名=值;
静态成员函数:
声明:
static 类型 函数名(形参列表)
若在类体外定义静态成员函数,则无需关键字static。静态成员函数至于类类型相关而不与对象相关联,因此静态成员函数没有this指针。静态成员函数也不能声明为const,因为把成员函数声明为const是为了保证该成员函数不修改调用他的对象,但是现在该成员函数已经不与对象相关联了。静态成员函数也不能声明为虚函数。
继承与派生
派生类型分为共有派生(public)、私有派生(private)、和保护派生(protected)
若不指定派生类型,则为私有派生。
私有成员不可继承,共有继承后共有成员还是公有成员,保护成员还是保护成员;保护继承后,公有成员和保护成员都变为保护成员;私有继承后,保护成员和公有成员都变为私有成员。
通过继承建立的派生类,只能将基类中的全部成员(包括数据成员和成员函数,但是不包括构造函数、析构函数和重载的赋值运算符)接收过来,积累的友元函数表明的是积累自己的关联,因此不能被继承,但是如果基类的某成员是其他某类的友元,则其被派生后,作为派生类的成员通过派生类使用时,仍然是该类的友元。
多继承声明格式:
class 派生类名:派生类型1 基类名1,派生类名2 基类名2,..., 派生类名n 基类名n
派生类的构造函数
单继承时的构造函数
派生类名(参数列表):基类名(参数列表),派生类中新增数据成员的初始化列表{
//派生类构造函数的其他操作;
}
注意:
- 如果基类定义了默认(无参数)构造函数,则派生类可以不对基类显示初始化;
- 如果基类没有定义默认构造函数,则派生类必须在构造函数的初始化列表中初始化基类,传递进去的参数个数与基类构造函数一致
- 基类的所有数据成员并不能单独出现在派生类的初始化列表中
派生类对象的构造次序:
- 调用各基类的构造函数,调用顺序与声明派生类时,基类在声明语句中的顺序从左向右
- 调用派生类中各对象成员的构造函数,调用顺序时按照他们在派生类中声明的顺序从上到下,从左到右;
- 初始化派生类中各新增的数据成员,同要是按照他们在派生类中生命的顺祖从上到下,从左至右;
- 执行派生类构造函数的函数体
派生类对象的析构函数的执行顺序是:
- 执行派生类析构函数的函数体(堆存储的数据,动态分配空间,需要手动释放空间)
- 清除派生类的各新增数据成员,顺序与他们在派生类中的声明顺序正好相反;(如int,string等基本数据类型)
- 调用派生类中的个对象成员的析构函数清除对象成员,顺序与他们在派生类中声明的顺序正好相反(其他类的对象/实例)
- 调用各基类的析构函数,调用顺序与声明派生类时基类的再声明语句中出现的顺序正好相反(父类中的数据)
成员覆盖:支配规则
当派生类中新增加的成员与基类的成员同名时,若直接通过派生对象使用该同名成员,不会产生二义性,此时派生类的同名成员有限。如果此时需要通过派生类对象使用基类被覆盖的同名成员,直接用成员名限定的方法来指定。
赋值兼容规则
在公有派生的前提下,在任何可以使用基类对象的地方都可以使用类有派生类的对象替代
限制:
- 如果派生类不是公有继承,则不能将派生类对象当作是基类的对象
- 基类对象不能当作是派生类的对象,既不能给派生类对象赋值,也不能将派生类指针指向基类对象,同样不能用基类对象来初始化派生类的引用。
虚基类
虚基类的声明:
class 派生类名:virtual 派生类型 基类名{
}
virtual是声明虚基类的关键字,代表派生类为虚基类,即使被某个派生类多次间接继承,派生类对象中依然只有一份本虚基类的数据成员,而且需要在基类第一次被派生时就声明;
虚基类的构造
c++规定,虚基类由最终派生类(D)来初始化,虽然最终派生类的所有基类(B,C)也初始化基类(A),但是最终派生类的所有基类中对该虚基类的初始化都被忽略,虚基类的构造函数因此只被调用一次。
因此,对于虚基类而言,如果定义有无参数的构造函数,则最终派生类可以不必显式初始化,但是如果没有定义无参数的构造函数,则从最终派生类沿着各种集成方式回溯到虚基类的路径上,至少有一条路径是完全的公有继承方式,不然最终派生类将无法构造虚基类。P272????protected不行??
派生类的初始化中,虚基类优于非基类构造,并且次序严格按照继承时虚基类声明的顺序,相应地,其析构次序则在非虚基类之后,虚基类的析构函数最好定义为虚函数。
虚函数与多态性
函数关联的两种方式
1、静态关联
在编译阶段完成的关联称为静态关联,由于联编过程是在程序运行前完成的,所以又称为早期联编,静态联编能够实现编译时的多态。
函数重载和运算符重载就是通过静态关联的方式实现的编译时多态
有点事函数调用速度快、效率高、缺点是编程不够灵活,无法适应未来的变化,如果需要对某些功能予以扩充而适应新的场合买就只能修改原始代码
2、动态关联
指只有在运行程序时在能够动态确定将要调用的函数无法像静态联编一样再便宜或者连接阶段解决标识符的关联。
动态关联对打的有点事提供了更好的编程灵活性,问题抽象性和程序易维护性,统一了编程接口,缺点是与静态关联相比,函数调用速度略慢。
多态性(Polymorphism)就是指不同类型的对象接收到相同的消息时,产生不同的相应动作,即对应相同的函数名,却执行了不同的函数体。
多态性的实现:
- 静态多态的实现方法——重载
- 动态多态的实现方法——覆盖
虚函数
虚函数时动态联编的基础
只有继承性时,如果基类指针指向(或基类引用绑定到)公有派生类对象,可以访问派生类从基类中集成的成员函数,即使派生类中重写了同名函数,其结果仍然是访问基类的同名函数,而并非派生类中重新定义的同名函数。
如果将基类中的某公有函数设为虚函数,在不违背赋值兼容规则的前提下,通过积累的指针或者引用,在运行国策灰姑娘中访问不同派生类重写的同名函数,就可以实现运行中的多态了。
定义:
virtual 函数类型 函数名称(形参表);
注意:
- 虚函数时非静态的成员函数:virtual声明只能出现在类的声明中,在类体外实现函数时,函数名前不能加上virtual。
- 虚函数具有自动继承性: 基类中定义的虚函数,无论经过多少层公有派生,无论是否前置virtual说明,派生类中相同原型的函数都自动为虚函数。如果派生类中实现的函数与虚函数同名,但是不同原型,则为普通重载函数,并不是虚函数。
- 虚函数是有层次的:通过基类引用或者之神绑定到派生类对象时,如果该派生类并未实现某虚函数,则从该派生类出发,沿着派生体系向顶层回溯,知道某层实现了改虚函数,此时调用该层重写的虚函数。
- 可以一直多态性:通过基类引用或者指针绑定到派生类对象时,如果不想让多态发生,或者享有选择地执行某一基类的某一函数,则需要采用类名与作用于运算符相结合——成员名西安鼎来显示调用,但是多继承时之恩那个调用从派生类开始,回溯到本基类的继承体系结构中间层的函数
- 虚函数实现的本质不是重载而是覆盖:在虚函数的实现中,类中所有的虚函数用一个函数指针数组(即虚函数表)来管理,也就是通过该数组做一个对虚函数的间接调用,每个类的对象都包含一个指向该对象所属类虚函数表的指针,一旦某派生类重写了某个虚函数,则虚函数表中的函数指针就修改为当前最新重写的函数了。
虚析构函数
作用:防止内存泄漏
定义一个基类的指针p,在delete p时,如果基类的析构函数是虚函数,这时只会看p所赋值的对象,如果p赋值的对象是派生类的对象,就会调用派生类的析构函数(毫无疑问,在这之前也会先调用基类的构造函数,在调用派生类的构造函数,然后调用派生类的析构函数,基类的析构函数,所谓先构造的后释放);如果p赋值的对象是基类的对象,就会调用基类的析构函数,这样就不会造成内存泄露。
如果基类的析构函数不是虚函数,在delete p时,调用析构函数时,只会看指针的数据类型,而不会去看赋值的对象,这样就会造成内存泄露。
基类的构造函数不能声明为虚函数,因为调用基类的构造函数时,派生类对象除静态成员外,尚未开始初始化,于是基类的构造函数,根本不可能调用被派生类覆盖的虚函数,只能调用基类自己的虚函数。
抽象类
又是类的抽象中基类有时会表示一些现实中不可能出现实物的概念,即不可以对该类实例化产生该类的对象/实例。这样的类就应该定义为抽象类,含有纯虚函数的类称为抽象类。
纯虚函数
当某个函数无法实现或者不必实现时,这样的虚函数就可以声明为纯虚函数
定义形式:
virtual 函数类型 函数名(参数表)=0;
抽象类的成员函数无法实现,他之恩那个用作其他类的基类,不能声明对象也不能用作传值的函数参数与返回值类型或者显示转换的类型,但是可以声明抽象类的指针或者引用,将抽象类的指针指向公有派生类对象或者引用绑定到抽象类的公有派生类后,进而就可以实现运行时的多态性。
如果派生类在向根回溯的路径上,各个基类并没有完全实现抽象基类的所有虚函数,则该派生类依然是抽象类,不能声明对象。