c++期末复习小笔记

重载=符号 是this!=&s,不是this!=s,要记得加取地址符

想清楚是while(temp)还是while(!temp)

new的左边必须有个next,否则这个new出来的内存空间不会和原本链表头相连!!

除非是链表头!!!

链表的节点不只是要delete 还要=null!!!!而且是要->next=NULL!

引用

1.定义的同时初始化

2.引用对象从一而终

3.作为函数返回值时不能返回局部对象 一般与引用+形参搭配使用,返回形参

4.不能绑定在临时对象:

int *p1 = &(m + n);

int &r1 = m + n;

int &r4 = 50;

int &r5 = func_int();

bool isOdd(int &n)…isOdd(a + 9);

new的意义

为什么要给指针new内存,因为本身指针所存储的就是某一个数据类型的内存地址,如果一开始没有专门定一个int变量,又需要用到int *来操作int ,就需要new int,这个其实就省去了另外int 一个变量赋给int *

对象

1.static

static 成员变量的内存既不是在声明类时分配,也不是在创建对象时分配,而是在(类外)初始化时分配。

static 成员变量必须在类声明的外部初始化!静态成员变量在初始化时不能再加 static

初始化时可以赋初值,也可以不赋值。如果不赋值,那么会被默认初始化为 0。

int Student::m_total = 0;

2.friend “she is my friend, she can use my pens” friend是单向的而不是双向的。

friend void show(Student *pstu);

friend void Student::show(Address *addr);

friend class Student;

friend函数不能直接调用类成员,要借助对象,也就是以对象做friend函数的形参

const

const+指针:    const int* a //a不能修改  int* const a//指针不能修改

const+引用:编译器会为 const 引用创建临时变量 (是const引用不是const对象)

const int &r1 = m + n

bool isOdd(const int &n) …isOdd(a + 9); //改为常引用

并使得引用可以进行简单的数据类型转换:

int n = 100;

const float &r2 = n; //正确

const+类:

常成员变量:必须用初始化列表的方式初始化!!!!!!!

常成员变量:必须用初始化列表的方式初始化!!!!!!!

常成员变量:必须用初始化列表的方式初始化!!!!!!!

常成员函数:

1.必须在声明和定义处都在后面加上const

void forward(double * const input, double * output) const{}

2.只能用常成员变量X可以用全部成员变量但是不能修改其值√       

常对象(const对象)只能调用const成员(变量和函数)√    

继承与派生

三种继承是指从基类继承过来的变量所能达到的等级约束,public:public->public

Protected:public->protected

基类成员函数和派生类成员函数不会构成重载,区分重载和覆盖

如果派生类有同名函数(只需函数名相同不需要函数类型相同),那么就会遮蔽基类中的所有同名函数,不管它们的参数是否一样

如果说有重载关系,那么也是 Base 类的两个 func 构成重载,而 Derive 类的两个 func 构成另外的重载。

基类的private成员变量在派生类中可以用基类构造函数间接初始化(列表初始化),不过!!也是只能以列表初始化的方法!!!构造函数是无法被继承的

先调用基类构造函数,再调用派生类构造函数 以此可以联系上那些要求以此输出句子的题目。

还有一点要注意,派生类构造函数中只能调用直接基类的构造函数,不能调用间接基类的。

事实上,通过派生类创建对象时必须要调用基类的构造函数,这是语法规定。换句话说,定义派生类构造函数时最好指明基类构造函数;如果不指明,就调用基类的默认构造函数(不带参数的构造函数);如果没有默认构造函数,那么编译失败。(还是那句话,构造函数要么不写,要么写两个或以上,不然默认构造函数没了很容易出错)

和构造函数类似,析构函数也不能被继承。与构造函数不同的是,在派生类的析构函数中不用(也不能)显式地调用基类的析构函数

销毁派生类对象时,析构函数的执行顺序和继承顺序相反,即先执行派生类析构函数,再执行基类析构函数。

多继承中(多个基类)基类构造函数的调用顺序和和它们在派生类构造函数中出现的顺序无关,而是和声明派生类时基类出现的顺序相同(用输出语句来考)。这个调用顺序只是指时间上先后顺序,不是指位置顺序,所以

class Derived: public BaseA, public BaseB{…

Derived::Derived(int a, int b, int c, int d, int e): BaseB(c, d),BaseA(a, b), m_e(e){…

也是对的(一开始以为是错的)

继承派生内存模型:即使派生类名字遮蔽了基类,其基类成员变量依旧存在,可以用baseA::m_A准确调用,其分别占用不同的内存,其内存地址排列由上至下依次是基类的声明顺序和派生类。

为了解决多继承时的命名冲突和冗余数据问题, C++ 提出了虚继承,使得在派生类中只保留一份间接基类的成员。(应对菱形继承)

必须在虚派生的真实需求出现前就已经完成虚派生的操作。

换个角度讲,虚派生只影响从指定了虚基类的派生类中进一步派生出来的类,它不会影响派生类本身

中间层级类如果以同名成员覆盖了基类:

以图 2 中的菱形继承为例,假设 A 定义了一个名为 x 的成员变量,当我们在 D 中直接访问 x 时,会有三种可能性:

 如果 B 和 C 中都没有 x 的定义,那么 x 将被解析为 A 的成员,此时不存在二义性。

 如果 B 或 C 其中的一个类定义了 x,也不会有二义性,派生类的 x 比虚基类的 x 优先级更高。

 如果 B 和 C 中都定义了 x,那么直接访问 x 将产生二义性问题。

虚继承的构造与普通继承的构造不同:

在虚继承中,虚基类是由最终的派生类初始化的,换句话说,最终派生类的构造函数必须要调用虚基类的构造函数(也同时要调用直接基类函数,A,B,C,都要)。对最终的派生类来说,虚基类是间接基类,而不是直接基类。这跟普通继承不同,在普通继承中,派生类构造函数中只能调用直接基类的构造函数,不能调用间接基类的

虚基类 A 在最终派生类 D 中只保留了一份成员变量 m_a,如果由 B 和 C 初始化 m_a,那么 B 和 C 在调用 A 的构造函数时很有可能给出不同的实参,这个时候编译器就会犯迷糊,不知道使用哪个实参初始化 m_a。为了避免出现这种矛盾的情况, C++ 干脆规定必须由最终的派生类 D 来初始化虚基类 A,直接派生类 B 和 C对 A 的构造函数的调用是无效的(只是对m_a无效,他本身初始化B和C中的m_b,m_c是有效的,不会报错)。

虚继承时构造函数的执行顺序与普通继承时不同:在最终派生类的构

造函数调用列表中,不管各个构造函数出现的顺序如何,编译器总是先调用虚基类的构造函数,再按照出现的顺序调用其他的构造函数;而对于普通继承,就是按照构造函数出现的顺序依次调用的。

将派生类赋值给基类(向上转型)(可能会考判断题):

1.直接对象之间赋值(类似把float赋给int,直接去掉小数点后的数字)

2.派生类指针赋值给基类指针

回顾函数编译原理:

通过对象调用成员函数时,不是通过对象找函数,而是通过函数找对象。

使用 obj.display()调用函数时,会被编译成类似下面的形式:new_function_name(&obj);隐式的传入当前对象指针

So:

pa 本来是基类 A 的指针,现在指向了派生类 D 的对象,这使得隐式指针 this 发生了变化,也指向了 D 类的对象,所以最终在 display() 内部使用的是 D 类对象的成员变量

编译器通过指针的指向(内存空间)来访问成员变量,通过指针的类型()来访问成员函数。对于 pa,它的类型是 A,不管它指向哪个对象,使用的都是 A 类的成员函数,指针属于哪个类的类型就使用哪个类的函数。

多态(虚函数):

就是为了解决前面的函数由指针类型决定的问题。将基类的函数声明为虚函数,然后定义一个基类指针,指针指向派生类时就能使用派生类的函数(与基类函数重名),而不只是派生类的成员变量。

总结构成多态的条件:

必须存在继承关系;

继承关系中必须有同名的虚函数,并且它们是覆盖关系(函数原型相同,不只是函数名)。换句话说,基类指针的函数使用范围只在基类的成员函数内,派生类要体现多态则必须在基类成员函数基础上变化。

存在基类的指针,通过该指针调用虚函数。

Tips

1.声明处加virtual,定义随便

2. 只将基类中的函数声明为虚函数,这样所有派生类中具有遮蔽关系的同型函数都将自动成为虚函数。

只有派生类的虚函数覆盖基类的虚函数(函数原型相同:函数名,函数形参,函数返回值)才能构成多态(通过基类指针访问派生类函数)。

例如基类虚函数的原型为 virtual void func();,派生类虚函数的原型为 virtual void func(int);,那么当基类指针 p 指向派生类对象时,语句 p -> func(100);将会出错(此时按照原有逻辑运作,即只能用基类的函数,而基类中没有该函数类型),而语句 p -> func();将调用基类的函数

普通函数只需函数名相同就形成覆盖,虚函数需要函数类型相同才能形成覆盖

3.构造函数不能是虚函数(编译出现问题同时也无意义)

大部分情况下都应该将基类的析构函数声明为虚函数,因为用基类指针new派生类对象之后再delete,基类指针没有权限调用派生类指针

如果是派生类指针new派生类对象再delete,在调用完派生类析构函数后会自动逐层向上(基类)调用析构函数,这是编译器自带的

纯虚函数和抽象类virtual float area() = 0;

Line (定义了纯虚函数area()和volume())--> Rec长方形(先实现了area()) --> Cuboid长方体 --> Cube正方体(和长方体一起实现了volume(),正方体覆盖了长方体的volume()函数,体现多态)

在基类中:virtual float area() = 0;其派生类包括其本身在未完成全部纯虚函数前都无法实例化。

Type_id()返回一个type _info类去存储该变量or表达式or类or数据类型的数据信息

常用来判别两者类型是否相同

动态绑定:编译器在编译期间不能确定指针指向哪个对象,只能等到程序运行后根据具体的情况

再决定。

运算符重载

1.做成员函数时可以少一个参数,做全局函数时必须是完整参数数目。例如二元运算符就要用俩参数。

2.作为全局函数时,其中一个参数必须是自己定义的类的对象,不能两个都是系统自带数据类型。否则就和原本的默认运算冲突了,我们只有拓展的权限。

3.作为全局函数时记得声明为友元函数

4. 箭头运算符->、下标运算符[ ]、函数调用运算符( )、赋值运算符=只能以成员函数的形式重载

5.重载+和+=分别以complex和complex &作为函数返回类型

6.转移构造函数使得complex类型和double类型可以直接做运算:

Complex(double real): m_real(real), m_imag(0.0){ } //转换构造函数

作为普通构造函数的同时能够把double转换成一个匿名的complex对象,从而能够运算

7. 为什么要以全局函数的形式重载 +?, 这样做是为了保证 + 运算符的操作数能够被对称的处理;否则Complex c3 = 28.23 + c1;就会报错,而Complex c2 = c1 + 15.6;不会报错

C++ 只会对成员函数的参数进行类型转换,而不会对调用成员函数的对象进行类型转换。

意思是在类里重载+运算符就是算作一个成员函数,c1+28.23就是等于c1.+(28.23),他可以对28.23类型转换,但不可能对c1进行类型转换

8.[]

返回值类型 & operator[ ] (参数);

或者:

const 返回值类型 & operator[ ] (参数);

返回值类型 & operator[ ] (参数) const;

使用第一种声明方式, [ ]不仅可以访问元素,还可以修改元素。

使用第二种声明方式, [ ]只能访问而不能修改元素。比如不能b[1]=a;

使用第三种声明方式,常成员函数,函数内不能修改任何成员变量,同时const对象只能用常成员函数和常成员变量

在实际开发中,我们应该同时提供以上第一和第三种形式,这样做是为了适应 const 对象,因为通过 const 对象只能调用 const 成员函数,如果不提供第二种形式,那么将无法访问 const 对象的任何元素。

9. stopwatch operator++(); //++i,前置形式

 stopwatch operator++(int n); //i++,后置形式

在这个函数中参数 n 是没有任何意义的,它的存在只是为了区分是前置形式还是后置形式

10. operator double() { return real; } //重载强制类型转换运算符 double

有了这个就可以实现转移构造函数的类似作用,把complex类型转为double,那么在运算时可以double n=2+c,c是一个complex对象。不过这个应该不能和刚才的转移构造函数同时出现,否则会有二义性

模板(函数模板、类模板、)

1.template<class T> void swap(T &a,T &b) class和typename都行

2.模板类

a.在类外定义成员函数时仍需要带上模板头

b.类名后仍要加<T1,T2>类型参数,但是不用加typename

c.类模板创建对象时要指明类型参数的数据类型Point<int,float>

Point<float, float> *p1 = new Point<float, float>(10.6, 109.3);

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值