清华大学的C++设计课程(郭炜/刘家瑛老师)整理笔记
清华大学的C++
设计1
1 C到C++
1.1 动态内存分配
p = new T;
delete p;
1.2 内联函数与重载
2 面向对象设计
2.1 构造函数
为什么需要构造函数:
- 构造函数执行必要的初始化工作,有了构造函数,就不必专门再写初始化函数,也不用担心忘记调用初始化函数。
2.2 复制构造函数/拷贝构造函数
func()函数为什么完成了复制构造函数的工作?
在每次调用class 进行初始化的过程中,是不是必须要进行“复制构造函数”的工作?
下面两个是一致的。
下图的语法的理解
下图。对于b进行构造函数初始化就可以了,为什么还需要“复制构造函数”?
编译器会自动生成两个构造函数:无参构造函数与复制构造函数。 如下:
做了复制的工作。
为什么需要写复制构造函数?需要进行初始化的赋值操作。
初始化的赋值操作有时不在我们写的构造函数中,是在“复制构造函数”中进行的。比如以下,在我们没写复制构造函数时,系统自动生成的复制构造函数会会启动。
#include <iostream>
using namespace std;
class test{
public:
test(int a = 0,int b = 0){
this->a = a;//this is a primer
this->b = b;
cout<<"构造函数"<<endl;
}
test(const test& another){
cout<<"copy constructor"<<endl;
if(this != &another){
a = another.a;
b = another.b;
}
}
void print(){
cout<<"fuck you"<<endl;
cout<<a<<endl;
cout<<b<<endl;
}
private:
int a;
int b;
};
void print(const test & obj){
const_cast<test&>(obj).print();
}
test func(const test & obj){
return const_cast<test &>(obj);
}
int main(){
test test1(1,2);
func(test1);
cout<<"end"<<endl;
return 0;
}
2.3 类型转换构造函数
为什么需要类型转换函数?
在上面的例子中,c1与9并不是一种类型,因此需要“类型构造函数”。
需要定义一个临时的对象demo,但是当调用结束、对象消亡时,这个临时对象也需要被析构掉。两个操作才完成了赋值的操作。
2.4 析构函数
编译器自动生成缺省析构函数,下图完整解释了析构函数的作用。
下面解释了什么时候调用:构造函数与析构函数。以及其作用范围。
#include <iostream>
using namespace std;
class demo{
int id;
public:
demo(int i){//构造函数
id = i;
cout<<id<<" 构造函数"<<endl;
}
~demo(){//类型转换析构函数?
cout<<id<<" 析构函数"<<endl;
}
};
demo d1(1);
void func(){
static demo de(2);
demo d3(3);
cout<<"func"<<endl;
}
int main(){
demo d4(4);
d4 = 6;//这里赋值操作仿照“类型转换构造函数”,在结束时需要析构
cout<<"mian"<<endl;
{
demo d5(5);//这里有作用域,标志这个对象在这个作用域结束时需要消亡掉
}
func();
cout<<"main end"<<endl;
return 0;
}
结果:
main end
6析构函数//临时
2析构函数//静态
1析构函数//全局
2.5 静态成员变量与静态成员函数
特点:(貌似在python中采用大量类似的方法,创造共有的静态的成员函数)
- 静态的成员变量被所有的对象所共享
- sizeof不会计算静态成员变量的字节数
- 不具体作用于某个对象,如下:
目的:将某些与该类的全局变量紧密联系的变量写进去,别的类就没办法去访问该全局变量。
要进行初始化:静态成员变量(static)初始化的问题,必须要在主函数中进行全局变量的初始化。
静态成员函数中,不能访问非静态成员变量,因为说不清楚具体是哪个对象的变量。
但是静态成员变量和静态成员函数怎么去定义呢?
2.6 成员对象和封闭类
封闭类:一个类的成员变量时另一个类的对象。
初始化方式:(初始化列表)
对于生成封闭类,必须要对对象中的成员对象进行初始化。这就用到“初始化列表”,这里可以是复杂的表达式。
下面给一个给“封闭类”的例子。
2.7 友元
一个类的友元函数可以访问该类的私有成员。
将成员函数定义为另一个类的友元。例子如下:
#include <iostream>
using namespace std;
class car{
private:
int price;
friend class driver;
};
class driver{
public:
car mycar;
void modifycar(){
mycar.price = mycar.price + 100;
}
};
int main(){
return 0;
}
2.8 this指针
涉及到C+与C的翻译。
非静态的成员函数中可以直接使用this代表指向该函数作用的对象的指针。
class complex{
public:
double real,imag;
void Print(){cout<<real<<"\t"<<imag<<endl;}
complex(double r, double i):real(r),imag(i){ }//可以直接赋值
complex Addone(){
this->real++;
this->Print();
return *this;
}
};
int main(){
complex c1(1,1),c2(0,0);
c1 = c2.Addone();//所以在这里c1的值就不用管了?难么问题就在于这是用了哪个功能
//输出结果进行观察
return 0;
}
或者如下:
#include <iostream>
using namespace std;
class A{
int i;
public:
void hello(){cout<<"fuck you"<<endl;}
};
int main(){
A* p = NULL;
p->hello();
return 0;
}
上面可以解释如下:
2.9 常量对象、常量成员函数、常引用
常量成员函数执行期间不应修改其所作用的对象因此,在常量成员函数中不能修改成员变量的值(静态成员变量除外) ,也不能调用同类的非常量成员函数(静态成员函数除外)。
**对象作为函数的参数时,生成该参数需要调用复制构造函数,效率比较低。**用指针作参数,代码又不好看,如何解决?使用常引用。
但是具体的作用是什么?这一节在程序中经常能看到,可以多注意一下。
3 运算符的重载
3.1 运算符的重载
对已有的运算符赋予多重的含义,使同意运算符作用域不同类型的数据
(暂时先不关注)
4 继承和派生
4.1 继承
- 基类与派生类
派生类是通过对基类进行修改和扩充得到的。在派生类中,可以扩充新的成员变量和成员函数。派生类一经定义后,可以独立使用,不依赖于基类。
派生类对象的体积,等于基类对象的体积,再加上派生类对象自己的成员变量的体积。在派生类对象中,包含着基类对象,而且基类对象的存储位置位于派生类对象新增的成员变量之前。(关于体积)
下面的程序存在一定的问题,不好定位原因。
#include <iostream>
#include <string.h>
using namespace std;
class student{
private:
string name;
string id;
char gender;
int age;
public:
void printinfo();
void setinfo(const string &name_ , const string & id_,int age_ , char gender_){
name = name_;
id = id_;
age = age_;
gender = gender_;
};
string getname(){ return name;}
};
class understandstudent:public student{
private:
string department;
public:
void printinfo(){
student::printinfo();//覆盖
cout<<"depantment"<<department<<endl;
}
void setinfo(const string & name_ , const string & id_ ,
int age_ , char gender_ , const string & department_){
student::setinfo(name_,id_,age_,gender_);
department = department_;
}
};
int main(){
understandstudent s2;
s2.setinfo("sm" , "12",12,'m',"science");
cout<<s2.getname()<<endl;
cout<<"end"<<endl;
return 0;
}
4.2 复合关系与继承关系
一个学生派生出中学生,这样比较合理的,这是简单的继承。但是同样存在复合继承关系,复合继承关系内部需要“友元”模块的存在。
4.3 基类-派生类同名成员和protected的用法
基类和派生类有同名的现象。
T obj;
obj.i = 1;
obj.base::i = 10;
问题是基类的private的成员不能被派生类访问,只有下面可以访问:
- 基类的成员函数
- 友元函数
protected成员,可以被其他访问:
- 友元函数
- 派生类的成员函数可以访问当前对象的基类的保护成员
- 基类的成员函数
- 友元函数
构造函数中,首先是基类的构造函数,然后是派生类的构造函数。在析构函数中,先是派生类的,再是基类的。
- 友元函数
- 基类的成员函数
4.4 派生类的构造函数
创建派生类的对象时,需要调用基类的构造函数:初始化派生类对象中从基类继承的成员在执行一个派生类的构造函数之前,总是先执行基类的构造函数。
显示表达如下:
derived::derived(arg_derived-list):base(arg_base-list)
4.5 public继承的赋值兼容规则
5 虚函数与多态
vitrual关键字构建虚函数,虚函数可以参与多态。
class base{
virtual int get();//虚函数
}
int base::get(){};
派生类的指针可以赋给基类指针。通过基类指针调用基类和派生类中的同名虚函数时:
(1) 若该指针指向一个基类的对象,那么被调用是基类的虚函数;
(2) 若该指针指向一个派生类的对象,那么被调用的是派生类的虚函数。
这种机制就叫做“多态”。
输出结果是派生的vitrualfunction
说白了就是调用哪个虚函数就看r引用的是哪个类。
5.1 使用多态的游戏程序示例
代码如下:
#include <iostream>
using namespace std;
//基类
class moster{
protected:
int live;
int attacknum;
};
//派生类
class dog:public moster{
public:
int livenow;
int fightbacknum;
//构造函数,在这里基类的构造函数是自动生成的
dog(int _live = 100 , int _attacknum = 14, int _fightbacknum = 11){
live = _live;
attacknum = _attacknum;
fightbacknum = _fightbacknum;
livenow = live;
};
void gethurt(int hurtnum){
livenow = livenow - hurtnum;
};
void attack(dog *tmp){
tmp->gethurt(attacknum);
tmp->fightback(this);
};
void fightback(dog *tmp){
tmp->gethurt(fightbacknum);
};
};
int main(){
dog A(100,10),B(120);//这样初始化不行
A.attack(&B);//一个轮回
B.attack(&A);//又一个轮回
cout<<A.livenow<<endl;
cout<<B.livenow<<endl;
cout<<"end"<<endl;
}
多态在提升程序的可扩充性方面有着很大的作用。虚函数和多态是绑定的。
#include <iostream>
using namespace std;
class base{
public:
void fun1(){this->fun2();}//this是基类,调用虚函数就是多态
virtual void fun2(){cout<<"base::fun2()"<<endl;}
};
class derived:public base{
public:
virtual void fun2(){cout<<"dervited::fun2()"<<endl;}
};
int main(){
derived d;
base * pbase = &d;
derived * pder = &d;
pbase->fun1();
pder->fun1();
return 0;
}
jieguo:
dervited::fun2()
dervited::fun2()
在非构造函数,非析构成员函数中调用虚函数,就是多态。
多态的实现原理
6 范型程序设计
6.1 C+中文件的IO
6.2 范型程序设计/函数模版
算法实现时不指定具体要操作的数据的类型。算法实现一遍,针对多种数据结构。
示例如下:
#include <iostream>
using namespace std;
template<class T>
void swap1(T &a,T &b){
T tmp = a;
a = b;
b = tmp;
}
int main(){
int a = 4;
int b = 3;
swap1(a,b);
cout<<a<<"\t"<<b<<endl;
}
6.3 类模版
编译器由类模板生成类的过程叫类模板的实例化编译器自动用具体的数据类型。替換类模板中的类型参数,生成模板类的代码。由类模板实例化得到的类叫模板类。为类型参数指定的数据类型不同,得到的模板类不同。
代码如下:``
#include <string>
#include <iostream>
#include <ios>
using namespace std;
template <class T1, class T2>
class Pair{
public:
T1 key; //关键字
T2 value; //值
Pair(T1 k,T2 v):key(k),value(v) {};//构造函数
bool operator < (const Pair<T1,T2> & p) const;//运算符重载
};
template<class T1,class T2>
bool Pair<T1,T2>::operator<( const Pair<T1, T2> & p) const
//Pair的成员函数 operator <
{ return key < p.key; }
// 类模板的定义
//Pair类模板的使用:
int main(){
Pair<string, double > student("Tom",19);
//实例化出一个类 Pair<string, int>
cout<<student.key << " " << student.value<<endl;
cout<<"end"<<endl;
return 0;
}
下面是模版函数与模版类:
#include <string>
#include <iostream>
#include <ios>
using namespace std;
template <class T, int size>//模版类,另外还有非类型参数
class A{
public:
int tmp[size];
template <class T2 >//模版函数
void func(T2 t){
cout<<t<<endl;
}
};
int main(){
A<int,4> tp;
tp.func(234);
return 0;
}
另外,还可以在类模版的额参数声明中包含非类型参数。如果传递的参数类型不一样,那么两者之间是不一样的,是不能进行相互赋值的。
6.4 string类
就是模版类,是由类模版实例化得到的。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jc3PDNYu-1586427427409)(DraggedImage-29.png width=555)]
不提供以字符和整数为参数的构造函数。
简单输入代码如下:
int x;
while (cin>>x){
cout<<x<<endl;
}
getline(char *buf,int bufsize);//这个函数需要注意
-
郭炜/刘家瑛老师 ↩︎
上一篇: linux 文件系统损坏修复方式
下一篇: 为什么HashMap是线程不安全的?