c++&c#
C++&C#
第一讲&第二讲:A Better C
刘禹老师的课真火喵~100人的课硬是来了150人左右~教室都坐不下了喵)🤣~
over loading 重载
允许定义多个参数不同的同名函数
1 | void MyPrint(int x){ |
但不允许定义返回值不同的同名函数(对于一些函数,返回值不重要 exp. scanf() )
default parameter 默认参数
允许在调用函数但是传入参数不够时,为参数置初值
1 | void fun(int a,int b,int c=3){ |
!!注意!!:默认参数放在后面,防止一会儿默认一会儿不默认 不能像python一样指名点姓地默认
占位参数
允许以下行为
1 | void fun(int){ |
一定要传递,但程序中不能加以调用(可以下个版本再用👌)
字节对齐
c++中会自动对变量进行字节对齐👈看这个
虽然方便程序控制,但是损耗了一些空间(大多数时候都忽略不计)
但是如果加入了此命令
1 |
c++就会以1为最小单位进行空间分配,即字节对齐
微信的空间压缩做得特别好,所以信号很差也能收到消息(忽略)
封装 分治
member中包括两部分:attribute data member & method function member
c++中将struct升级为class(来源Simula语言),既能封装数值又能封装函数
class类 vs. class a 对象
- 类是抽象,对象是客观存在
- 类是唯一的,对象是无穷的
- 对象就是一段连续的内存
Object Oriented(喜欢OO的没有坏人!!!)
正好老师提到了www(逃~)
access control
为安全需要,一些东西需要上锁
分为pritvate和public (和protected)
1 | class Student{ |
the hidden this pointer
在类的函数内部,调用类中变量时,默认加入this
即若age是Student中变量,在Init中被调用,则age等于this->age
但经我测试,这样的程序也是可以正常执行的
1 | class Student{ |
第三讲:封装
class与struct默认访问权限不同
如下:
1 | class Student{ |
constructor 构造函数
要求:与类同名,传递任意参数,没有返回值
1 | class Student{ |
与在class里自己写void InitStudent相比,设置构造函数可以要求程序员必要在创建对象时赋初值
同时,上一讲介绍的同名参数和默认参数也可以在这里使用
default constructor 默认构造
在类的内部一直存在一个默认的构造函数,它初始没有任何作用,直到你写一个自己的构造函数为止
例如,假如你已经写了一个构造函数,但新建对象时没有进行构造
1 | Student s0; |
此时便会报错
1 | no appropriate default constructor available |
即找不到适合的默认构造
此时可以定义并声明一个Student();来解决
1 | class Student{ |
特别注意:
1 | int main(){ |
char[]和char*比较
char*
- 优点:占用内存小,复制方便
- 缺点:很容易导致丢失,如
1 | int main(){ |
char[]
与char*相反~~(懒得写了)~~
类型也是类
我们所使用的int等类型也是类
所以此两语句等价
1 | int i=1; |
若以后遇到想要使用一个变量的地址,但又害怕该变量为动态变量,可以使用如下语句
1 | Test::Test(int ID){ |
destructor 析构函数(new delete)
不同于java会自动删除内存,c++的内存释放必须要自己进行
上个部分的例子中,new int会将ID创建在堆区,而Test创建的对象在栈区,导致内存泄漏(memory leak)
此时便可以用析构函数在Test释放时自动清楚内存
1 | class Test{ |
malloc/free vs. new/delete
-
malloc free是一对库函数
-
new delete是一对运算符
看以下代码
1 | Test *p = (Test*)malloc(sizeof(Test)); |
使用malloc中,p在栈区,生成的Test在堆区
使用new中,p在栈区,生成的Test也在堆区
总结:new = malloc + constructor
delete = deconstructor + free
handle 句柄
一个例子
1 | class Student{ |
第四讲:封装(第二部分)
reference 引用
指针的不安全处:
- fly pointer 野指针
指针允许初始化不赋值- 指针可以多次赋值
声明并定义一个引用
1 | int i = 10; |
引用的安全处:
- 引用必须赋初值
- 引用在全程不能改变(即例子中的r必须一直指向i)
引用是一个安全的指针!
address包括pointer和reference——引用是一个地址
所以可以这么写:
1 | void fun (int &a){ |
一个总结:
- 指针——复杂的、丑陋的
- 引用——简洁的、晦涩的
JAVA舍弃了指针和变量本身,一切东西都是引用(恍然大悟!!!)
小彩蛋:
1 | const int* const p |
这两个等价
copy constructor 拷贝构造函数
在C++中,我们允许一下操作
1 | class Student{ |
注意main中s2的初始化,直接将s1作为参数传入了构造函数。这个程序的目的是将s1拷贝一份到s2里。
此时的构造函数,称作copy constructor 拷贝构造函数
(在其它程序中,这个操作一般被称为clone)
注意!小心以下情况:
1 | class Student{ |
相较于上一个程序,这段代码的改动之处在于为Student类提供了一个int*类属性,此时初始化s2便会产生有两个指针同时指向一段相同的堆区地址的情况,导致出错(尤其是在析构的时候),出现十分诡异的情况
深拷贝(logical copy) vs 浅拷贝(bitwise copy)
顾名思义(指英文题目),深拷贝是将需要的部分以某种规则拷下来(logical),浅拷贝是默认的拷贝方式(bitwise)(如上一部分的拷贝构造)
借用上一部分的例子,将默认的修改方式由浅拷贝修改为深拷贝的方式为:
1 | class Student{ |
在这里,我们创建了一个新的构造方式,并通过自己写的逻辑方式规避了指针冲突的情况,所以是深拷贝
更进一步,如何保证自己使用的一定是深拷贝捏?或者怎么保证对象不被浅拷贝捏?
答案是使用私有拷贝构造
1 | class Test{ |
此时,只能通过私有拷贝函数来深拷贝
或者干脆拷贝函数留空,杜绝浅拷贝的可能性
pass by value vs. pass by address
方面 | value | address |
---|---|---|
性能 | sizeof(v) | sizrof(int) |
功能 | read only | read/return |
麻烦 | copy-structor | nothing |
总结:never pass by value!!!
static
1. static local variable 静态局部变量
无论何时创造,只在main后析构
2. static global function 静态全局函数
一旦函数被修饰为static,则该函数被限制为对外部透明,只在此文件可用
在多人开发项目时,可以将每个函数修饰static,不会影响其它项目部分
与此相反,extern指外连接,即“我有一个东西,在外部去找”,可以用于全局变量
3. static global variable 静态全局变量
只在本文件中可以使用,没有意义
4. static data member 静态类对象
当一个类中的对象被修饰为static,则该对象被认为是属于所有对象
注意:静态类对象不应该在任何对象中初始化(因为是共有的)
1 | class Student{ |
5. static function member 静态函数
1 | class MyTime { |
为函数修饰static后,该函数可以在没有对象的情况下直接被调用(即 MyTime::GetCurTime();)
同时,静态函数要求不能访问非静态属性,否则很明显会引发问题
收获满满的一节课喵~
第五讲:封装(第三部分)
design pattern 中的单件(单例)模式
design pattern指根据实际情况为代码确定的具体要求,共有二十多种场景
其中一个场景为单件(单例)模式,即在程序中只能实例化一个对象
1 | class Single{ |
在这个例子中,Single类将构造函数设置为了private,所以在程序执行过程中无法实例化新对象
同时,为了不让这个类与世隔绝,设置了一个静态方法GetInstance(),当主程序执行这个方法时,会实例一个静态对象self,并传回引用,保证了单例模式的实现
const
最大作用:修饰函数参数
1. const parameter
防止传入的地址变量被修改
1 | void fun(const int *p){ |
凡是const修饰的一定是in,反之一定是out
2. const data member
没别的意思,就是不许变
老师顺便扯到了enum,狠狠学习了www~
3. const function
1 | class Test{ |
只有不修改类内变量的函数才能被const修饰
(若const放在方法前,则为修饰返回值)
运算符重载
1 | class Test { |
老师还教了前++和后++,但太麻烦了,不想学了喵~
1 | Test& operator ++();//前++ |
可以卷其它课了喵www~
第六讲:继承 与 多态
性能问题出现之前不考虑性能问题
——刘禹
继承
1 | class People{ |
继承是特性与个性的划分
很容易理解
子类构造
构造子类构造,必调用父类构造
先调父类构造,再调子类构造
1 | Dervied::Dervied(int ai,int aj) : Base(1) |
👆:在子类构造中调用父类构造
多重继承
1 | class a : public b, public c { |
多态
1 | class Pet { |
多态:在类型传递过程中,保证类型行为正确
upcasting 向上类型转换
子类可以当作父类处理或传递,父类不能被视为子类
1 | class Pet { |
注意点:
- Pet类中的speak函数前有virtual关键字(虚函数)
- 传入函数的为引用,而非值
原理为每个实例都有一个虚指针,表示该实例的类型
父类有虚指针索引,记录所有的子类
当传值时,子类会调用父类的拷贝构造,导致出错
局部析构
1 | class base{ |
pure virtual 纯虚函数
1 | virtual void speak() = 0; |
若一个类含有至少一个纯虚函数,则该类被称为抽象类
abstract class 抽象类
-
提纲挈领
规定一个类中的接口——抽象类确实和接口差不多,但是可以实例化
-
串联不同家族的共性行为
更像接口了()但是可以总结不同家族的特点
原来如此:C++抽象类=Java接口
补充:python——接口型的多态
鸭子类型
不纠结是否确实是类型上的多态,只考虑行为一致
补充:C++ vs. Java/C#
c++为了做类库,专门做了个stl
standard template librery
动态增长的万能容器
性能问题出现之前不考虑性能问题