C++&C#

第一讲&第二讲:A Better C

刘禹老师的课真火喵~100人的课硬是来了150人左右~教室都坐不下了喵)🤣~

over loading 重载

允许定义多个参数不同的同名函数

1
2
3
4
5
6
void MyPrint(int x){

}
void MyPrint(char* p){

}

但不允许定义返回值不同的同名函数(对于一些函数,返回值不重要 exp. scanf() )

default parameter 默认参数

允许在调用函数但是传入参数不够时,为参数置初值

1
2
3
4
5
6
7
8
void fun(int a,int b,int c=3){

}

int main(int argc,char* argv[]){
fun(1,2);
return 0;
}

!!注意!!:默认参数放在后面,防止一会儿默认一会儿不默认 不能像python一样指名点姓地默认

占位参数

允许以下行为

1
2
3
4
5
6
7
void fun(int){

}

int main(int argc,char *argv[]){
fun(1);
}

一定要传递,但程序中不能加以调用(可以下个版本再用👌)

字节对齐

c++中会自动对变量进行字节对齐👈看这个

虽然方便程序控制,但是损耗了一些空间(大多数时候都忽略不计)

但是如果加入了此命令

1
#pragma pack(1)

c++就会以1为最小单位进行空间分配,即字节对齐

微信的空间压缩做得特别好,所以信号很差也能收到消息(忽略)

封装 分治

member中包括两部分:attribute data member & method function member

c++中将struct升级为class(来源Simula语言),既能封装数值又能封装函数

class类 vs. class a 对象

  • 类是抽象,对象是客观存在
  • 类是唯一的,对象是无穷的
  • 对象就是一段连续的内存

Object Oriented(喜欢OO的没有坏人!!!)

正好老师提到了www(逃~)

access control

为安全需要,一些东西需要上锁

分为pritvatepublic (和protected)

1
2
3
4
5
6
7
class Student{
private:
string id;
double score;
public:
void Init(string a,double s);
}

the hidden this pointer

在类的函数内部,调用类中变量时,默认加入this

即若age是Student中变量,在Init中被调用,则age等于this->age

但经我测试,这样的程序也是可以正常执行的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Student{
private:
string id;
double score;
public:
void Init(string,double);
string getId(){return id;};
double getScore(){return score;};
};
void Student::Init(string id,double score){
this->id=id;
this->score=score;
}
int main(){
Student x;
cout<<x.getId()<<' '<<x.getScore()<<endl;
x.Init("abc",99.9);
cout<<x.getId()<<' '<<x.getScore()<<endl;
return 0;
}

第三讲:封装

class与struct默认访问权限不同

如下:

1
2
3
4
5
6
7
8
9
class Student{
//默认为pravite
int ID;
}

struct Teacher{
//默认为public
int ID;
}

constructor 构造函数

要求:与类同名,传递任意参数,没有返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Student{
char *name;
int age;
public:
Student(char*,int);
};

Student::Student(char *aname,int aage){
this->name=aname;
this->age=aage;
}

int main(int argc, char* argv[]){
Student s1((char*)"ZhangSan",18);
return 0;
}

与在class里自己写void InitStudent相比,设置构造函数可以要求程序员必要在创建对象时赋初值

同时,上一讲介绍的同名参数和默认参数也可以在这里使用


default constructor 默认构造

在类的内部一直存在一个默认的构造函数,它初始没有任何作用,直到你写一个自己的构造函数为止

例如,假如你已经写了一个构造函数,但新建对象时没有进行构造

1
Student s0;

此时便会报错

1
no appropriate default constructor available

即找不到适合的默认构造

此时可以定义并声明一个Student();来解决

1
2
3
4
5
6
7
8
9
10
11
12
class Student{
int ID;
public:
Student();
};

Student::Student(){}

int main(){
Student s0;
return 0;
}

特别注意

1
2
3
4
int main(){
Student s2();
//这句话问题很大,可能被编译器认为是声明了一个函数,导致出错(而且编译不会报错)
}

char[]和char*比较

char*

  • 优点:占用内存小,复制方便
  • 缺点:很容易导致丢失,如
1
2
3
4
5
6
7
8
int main(){
Student s1;
if(1){
char *p="name";
s1->name=p;
}
//此时p作为动态空间已经被释放了,导致s1中name出错
}

char[]

与char*相反~~(懒得写了)~~


类型也是类

我们所使用的int等类型也是类

所以此两语句等价

1
2
int i=1;
int j(1);

若以后遇到想要使用一个变量的地址,但又害怕该变量为动态变量,可以使用如下语句

1
2
3
4
5
Test::Test(int ID){
//Test中有int* p
this->p = new int(ID);
//创建新整型,并传回地址
}

destructor 析构函数(new delete)

不同于java会自动删除内存,c++的内存释放必须要自己进行

上个部分的例子中,new int会将ID创建在堆区,而Test创建的对象在栈区,导致内存泄漏(memory leak)

此时便可以用析构函数在Test释放时自动清楚内存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Test{
int i;
int *p;
public:
Test(int,int);
~Test();
//析构函数
};

Test::Test(int ai,int ID){
this->i = ai;
this->p = new int(ID);
}

Test::~Test(){
delete p;
}

int main(){
Test t1(1,3);
//自动调用~Test();
return 0;
}

malloc/free vs. new/delete

  • malloc free是一对库函数

  • new delete是一对运算符

看以下代码

1
2
Test *p = (Test*)malloc(sizeof(Test));
Test *p = new Test(1);

使用malloc中,p在栈区,生成的Test在堆区
使用new中,p在栈区,生成的Test也在堆区
总结:new = malloc + constructor
delete = deconstructor + free


handle 句柄

一个例子

1
2
3
4
5
class Student{
char * name;
int age;
char * handle;//句柄
};

第四讲:封装(第二部分)


reference 引用

指针的不安全处:

  1. fly pointer 野指针
    指针允许初始化不赋值
  2. 指针可以多次赋值

声明并定义一个引用

1
2
3
4
int i = 10;
int $r = i;
r = 30;
cout << r; //控制台输出30

引用的安全处:

  1. 引用必须赋初值
  2. 引用在全程不能改变(即例子中的r必须一直指向i)

引用是一个安全的指针!

address包括pointer和reference——引用是一个地址

所以可以这么写:

1
2
3
4
5
6
7
8
9
void fun (int &a){
a++; //注意:访存不需要加地址符号
}

int main(int argc , char* argv[]) {
int a;
int &r = a;
fun(r);
}

一个总结:

  • 指针——复杂的、丑陋的
  • 引用——简洁的、晦涩的

JAVA舍弃了指针和变量本身,一切东西都是引用(恍然大悟!!!)

小彩蛋:

1
2
const int* const p 
const int& r

这两个等价


copy constructor 拷贝构造函数

在C++中,我们允许一下操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Student{
string name;
int id;
public:
Student(string,int);
};
Student::Student(string name,int id){
name=name;
id=id;
}
int main(){
Student s1("abc",100);
Student s2(s1);
return 0;
}

注意main中s2的初始化,直接将s1作为参数传入了构造函数。这个程序的目的是将s1拷贝一份到s2里。

此时的构造函数,称作copy constructor 拷贝构造函数

(在其它程序中,这个操作一般被称为clone)

注意!小心以下情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Student{
string name;
int id;
int *url;
public:
Student(string,int,int*);
};
Student::Student(string name,int id,int p){
name=name;
id=id;
url=*p;
}
int main(){
Student s1("abc",100,1);
Student s2(s1);
return 0;
}

相较于上一个程序,这段代码的改动之处在于为Student类提供了一个int*类属性,此时初始化s2便会产生有两个指针同时指向一段相同的堆区地址的情况,导致出错(尤其是在析构的时候),出现十分诡异的情况


深拷贝(logical copy) vs 浅拷贝(bitwise copy)

顾名思义(指英文题目),深拷贝是将需要的部分以某种规则拷下来(logical),浅拷贝是默认的拷贝方式(bitwise)(如上一部分的拷贝构造)

借用上一部分的例子,将默认的修改方式由浅拷贝修改为深拷贝的方式为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Student{
string name;
int id;
int *url;
public:
Student(string,int,int*);
Student(Student &s);
};
Student::Student(string name,int id,int p){
name=name;
id=id;
url=*p;
}
Student::Student(Student &s){ //这里一定是s的引用
name=s.name;
id=s.name;
url=new int(*s.url);
//为s的url在堆区创建一个值相同的不同地址
}
int main(){
Student s1("abc",100,1);
Student s2(s1);
return 0;
}

在这里,我们创建了一个新的构造方式,并通过自己写的逻辑方式规避了指针冲突的情况,所以是深拷贝

更进一步,如何保证自己使用的一定是深拷贝捏?或者怎么保证对象不被浅拷贝捏?

答案是使用私有拷贝构造

1
2
3
4
5
class Test{
Test(Test &t);
public:
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
2
3
4
5
6
7
8
class Student{
int id;
static cnt;
public:
Student(int);
}
int Student::j=100;
//......

5. static function member 静态函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class MyTime {
public :
static long GetCurTime();
}

long MyTime::GetCurTime() {
return 1;
}

int main(int argc,char *argv[]) {
MyTime t1;
t1.GetCurTime();
MyTime::GetCurTime();//注意这行
return 0;
}

为函数修饰static后,该函数可以在没有对象的情况下直接被调用(即 MyTime::GetCurTime();)

同时,静态函数要求不能访问非静态属性,否则很明显会引发问题

收获满满的一节课喵~


第五讲:封装(第三部分)


design pattern 中的单件(单例)模式

design pattern指根据实际情况为代码确定的具体要求,共有二十多种场景

其中一个场景为单件(单例)模式,即在程序中只能实例化一个对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Single{
Single();
static Single* self;
public:
static Single* GetInstance();
};
Single* Single::self = NULL;
Single::Single(){}
Single* Single::GetInstance(){
if(self==NULL)self=new Single();
return self;
}

int main(int argc,char *argv[]) {
Single *s=Single::GetInstance();
return 0;
}

在这个例子中,Single类将构造函数设置为了private,所以在程序执行过程中无法实例化新对象

同时,为了不让这个类与世隔绝,设置了一个静态方法GetInstance(),当主程序执行这个方法时,会实例一个静态对象self,并传回引用,保证了单例模式的实现


const

最大作用修饰函数参数

1. const parameter

防止传入的地址变量被修改

1
2
3
void fun(const int *p){
*p = 10;
}

凡是const修饰的一定是in,反之一定是out

2. const data member

没别的意思,就是不许变

老师顺便扯到了enum,狠狠学习了www~

3. const function

1
2
3
4
5
6
7
8
9
10
11
12
13
class Test{
public:
void fun() const;
};
void Test::fun() const{

}

int main(int argc,char *argv[]) {
const Test t;
t.fun();
return 0;
}

只有不修改类内变量的函数才能被const修饰

(若const放在方法前,则为修饰返回值)


运算符重载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Test {
int cnt;
public:
Test();
Test operator + (int m){
this->cnt += m;
return *this;
}
int getCnt(){
return this->cnt;
}
};
Test::Test(){
this->cnt=0;
}
int main(int argc,char *argv[]) {
Test t0=Test();
cout<<t0.getCnt();
t0=t0+1;
cout<<t0.getCnt();
return 0;
}

老师还教了前++和后++,但太麻烦了,不想学了喵~

1
2
Test& operator ++();//前++
Test operstor ++(int);//后++

可以卷其它课了喵www~


第六讲:继承 与 多态

性能问题出现之前不考虑性能问题

​ ——刘禹


继承

1
2
3
4
5
6
7
8
9
10
class People{
int id;
public :
int getId(){
return this->id;
}
};
class Student : People{
int score;
};

继承是特性与个性的划分

很容易理解


子类构造

构造子类构造,必调用父类构造

先调父类构造,再调子类构造

1
Dervied::Dervied(int ai,int aj) : Base(1)

👆:在子类构造中调用父类构造


多重继承

1
2
3
class a : public b, public c {

};

多态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Pet {
int age;
public :
virtual void speak();
};
void Pet::speak(){
cout<<"Pet::speak"<<endl;
}
class Cat : public Pet {

public:
void speak();
};
void Cat::speak(){
cout<<"miao~"<<endl;
}
class Dog : public Pet {

public:
void speak();
};
void Dog::speak(){
cout<<"wang!"<<endl;
}

多态:在类型传递过程中,保证类型行为正确


upcasting 向上类型转换

子类可以当作父类处理或传递,父类不能被视为子类

1
2
3
4
5
6
7
8
class Pet {
int age;
public:
virtual void speak();
}
void needle(Pet &pet){
pet.speak();
}

注意点:

  1. Pet类中的speak函数前有virtual关键字(虚函数)
  2. 传入函数的为引用,而非值

原理为每个实例都有一个虚指针,表示该实例的类型

父类有虚指针索引,记录所有的子类

当传值时,子类会调用父类的拷贝构造,导致出错


局部析构

1
2
3
4
5
6
7
8
9
10
11
12
class base{  
public:
virtual ~base();
//若不加virtual,则认为析构函数非多态
};
class derived : public base {

};
void fun(){
base *p = new derived;
//正确析构
}

pure virtual 纯虚函数

1
virtual void speak() = 0;

若一个类含有至少一个纯虚函数,则该类被称为抽象类


abstract class 抽象类

  1. 提纲挈领

    规定一个类中的接口——抽象类确实和接口差不多,但是可以实例化

  2. 串联不同家族的共性行为

    更像接口了()但是可以总结不同家族的特点

原来如此:C++抽象类=Java接口


补充:python——接口型的多态

鸭子类型

不纠结是否确实是类型上的多态,只考虑行为一致


补充:C++ vs. Java/C#

c++为了做类库,专门做了个stl

standard template librery

动态增长的万能容器

性能问题出现之前不考虑性能问题