1 | Polynomial& Polynomial::operator=(const Polynomial& p){ |
多项式类的赋值
要考虑 a = b = c 这种情况,所以返回reference类型
1 | Polynomial& Polynomial::operator=(const Polynomial& p){ |
多项式类的赋值
要考虑 a = b = c 这种情况,所以返回reference类型
1 | class Person |
C++类里面的哪些成员函数是内联函数?_操作符函数是内联的吗-CSDN博客
友元关系是单向的,不具有交换性。
友元关系不能传递
友元关系不能被继承,但对已有的方法来说访问权限不改变。
类A把类B声明为友元类,在前面要有前置声明
类A把类B中的函数声明为友元函数,在类A之前必须有类B的完整定义
1 | class Date; // 前置声明 |
重载输出运算符设置为全局函数
为什么输出运算符重载不能是一个成员函数?而非得声明为友元?
原因如下:
1 | 返回值 operator运算符(参数列表){} |
重载运算符时,函数声明在类内和类外是有区别的,比方说 + - * / 等需要2个操作数的运算符,当声明在类的外部时,则参数列表为2个参数,第一个参数为运算符左边的操作数,而第二个参数为操作符右边的操作数:如下
1 | classType operator+(classType& left, classType& right); |
而当函数声明在类的内部时,即为类的成员函数时,
1 | classType operator+(classType& right ); |
而第一个操作数就是调用该操作符的对象的引用,第二个操作数是传进来的参数,所以,如果要重载<<运算符,一般写法是这样的
1 | ostream& operator<<(ostream& os, const classType& obj); |
则第一个参数是该运算符的第一个操作数,然而,却不是类对象,
所以当该类运算符重载时写在类的内部时,又为了访问类内除public外的其它变量或函数
在C++中,private和protected是两种不同的访问修饰符,它们控制类成员的访问权限。
private:私有成员只能被该类的成员函数和友元函数访问,不能被该类的对象或者任何其他类访问。protected:受保护成员可以被该类的成员函数、该类派生出的子类的成员函数以及友元函数访问,但不能被该类的对象访问。在你的代码中,object类的成员a是私有的,所以它只能被object类的成员函数和友元函数访问。如果你想让a能被object类派生出的子类访问,你应该将a声明为受保护的,例如:
1 | class object { |
在这个例子中,a是受保护的,所以它可以被object类的成员函数、object类派生出的子类的成员函数以及友元函数访问。
this指针来访问对象的状态和虚函数表。this指针,不能访问对象的非静态成员或虚函数表,因为它们在类层次上调用,而不是通过对象。由于虚函数依赖于对象的上下文,而静态成员函数没有对象上下文,因此它们不能结合在一起使用。虚函数需要对象实例和虚函数表,而静态成员函数不具备这些特性,因此它们是互斥的。
当声明一个非静态成员函数为const时,对this指针会有影响。对于一个Test类中的const修饰的成员函数,this指针相当于Test const *, 而对于非const成员函数,this指针相当于Test *. 而static成员函数没有this指针,所以使用const来修饰static成员函数没有任何意义。 volatile的道理也是如此。
new运算符做的三件事:获得一块内存空间、调用构造函数、返回正确的指针
1、new() :分配这种类型的一个大小的内存空间,并以括号中的值来初始化这个变量;
2、 new[] :分配这种类型的n个大小的内存空间,并用默认构造函数来初始化这些变量;
char* p=new char[6]; strcpy(p,”Hello”);
3、当使用new运算符定义一个多维数组变量或数组对象时,它产生一个指向数组第一个元素的指针,返回的类型保持了除最左边维数外的所有维数。例如:
1 | int *p1 = new int[10]; |
4、创建类对象
1)new创建对象,pTest用来接收对象指针。new申请的对象,则只有调用到delete时才会执行析构函数,如果程序退出而没有执行delete则会造成内存泄漏:
CTest* pTest = new CTest(); delete pTest;
2)不用new,直接使用类定义申明,使用完后不需要手动释放,该类析构函数会自动执行:
CTest mTest;
3)使用普通方式创建的类对象,在创建之初就已经分配了内存空间。而类指针,如果未经过对象初始化,则不需要delete释放:
CTest* pTest = NULL;
::是运算符中等级最高的,它分为三种:全局作用域符,类作用域符,命名空间作用域符
全局作用域符号:当全局变量在局部函数中与其中某个变量重名,那么就可以用::来区分如:
1 | char zhou; //全局变量 |
作用域符号::的前面一般是类名称,后面一般是该类的成员名称,C++为了避免不同的类有名称相同的成员而采用作用域的方式进行区分
如:A,B表示两个类,在A,B中都有成员member。那么
A::member就表示类A中的成员member
B::member就表示类B中的成员member
“::”是作用域限定符或者称作用域运算符或者作用域操作符(scope operator).例如命名空间
“::”作用:
1 | namespace::name |
直接用在全局函数前,表示是全局函数。
在C++中,函数形参的缺省值(默认值)有以下规则:
缺省值必须从右向左连续设定。也就是说,如果一个参数有缺省值,那么它右边的所有参数都必须有缺省值。例如,以下函数声明是合法的:
1 | void fun(int x, int y = 1, int z = 2); |
但是,以下函数声明是非法的,因为y有缺省值,但是它右边的参数z没有缺省值:
1 | void fun(int x, int y = 1, int z); // 非法 |
缺省值只能在函数声明时设定,不能在函数定义时设定。例如,以下代码是合法的:
1 | // 在函数声明时设定缺省值 |
如果函数在同一作用域内多次声明,那么它的每个参数的缺省值最多只能设定一次。但是,如果函数在不同的作用域内声明,那么在不同的作用域内可以给同一个参数设定不同的缺省值。
动态多态
运行时的多态存在于继承类中,通过虚函数现动态选择调用。
静态多态
静态多态是发生在编译时期的,通过模板和函数重载实现,相比动态多态不需派生关系。
1、要打开一个输入文件流,需要定义一个 ifstream类型的对象。->Input-stream
2、要打开一个输出文件流,需要定义一个 ofstream类型的对象。->Output-stream
3、如果要打开输入输出文件流,则要定义一个 fstream类型的对象。->File-stream
**这3种类型都定义在头文件 **fstream里
1 | ofstream ofs; //2、打开一个相应的文件流 |
因为ifstream、ofstream和fstream这3个类都具有自动打开文件的构造函数,而这个构造函数就具有 open() 的功能。
因此,我们可以在创建流对象的时候就可以关联文件:ofstream myStream("myText.txt");
open函数的原型如下:
1 | void open(char const *,int filemode,int =filebuf::openprot); |
| ios::in | 打开文件进行读操作,这种方式可避免删除现存文件的内容 |
|---|---|
| ios::out | 打开文件进行写操作,这是默认模式 |
| ios::ate | 打开一个已有的输入或输出文件并查找到文件尾开始 |
| ios::app | 在文件尾追加方式写文件 |
| ios::binary | 指定文件以二进制方式打开,默认为文本方式 |
| ios::trunc | 如文件存在,将其长度截断为零并清除原有内容,如果文件存在先删除,再创建 |
公有继承时,对基类的公有成员和保护成员的访问属性不变,派生类的新增成员只能访问基类的公有成员和保护成员(都一样)。派生类的对象只能访问派生类的公有成员(包括继承的公有成员),访问不了保护成员和私有成员**(公有继承的对象多了个访问继承的公有成员)**。
保护继承中,基类的公有成员和保护成员被派生类继承后变成保护成员,派生类的新增成员只能访问基类的公有成员和保护成员(都一样),派生类的对象只能访问派生类的公有成员。
私有继承时,基类的公有成员和保护成员都被派生类继承下来之后变成私有成员,派生类的新增成员只能访问基类的公有成员和保护成员(都一样)。派生类的对象只能访问派生类的公有成员(这里和protected继承一样)。
C++多继承中的二义性问题_c++多重继承引起的二义性问题-CSDN博客
一个子类继承两个有同名数据成员的父类
一个子类继承两个父类,这两个父类又继承自同一个祖父类
collect采用gpt生成,适用于mosaic结果的分析
1 |
|
在class7的自旋锁多核启动失败,只能单cpu
解决方法
修改/home/cgz/work/nju-os-workbench/abstract-machine/scripts/platform/qemu.mk
1 |
|
让gdb启动时就打开tui
编写gdbinit文件

所以先在home目录下设置gdbinit
然后执行
1 | source ~/.gdbinit |
hanoi
每个Frame(栈帧)都有自己的变量,pc是相互独立的,指示自身栈帧下一步要执行什么
每次call,入栈,把pc置为0,在while(1)开头再指向top
每次执行完case语句,都把pc加1,不一定是栈顶的
把递归的分解为6步,逐步执行,case3移动一个不需要保存,在case6加1就行
1 | int hanoi(int n, char from, char to, char via) { |
存在自旋,但是自旋仅在guard保护修改flag和队列中,比用户的临界区短
1 | 1 typedef struct __lock_t { |
guard的作用是保护flag和队列的修改,flag保护临界区
在unlock中,唤醒等待线程后,不需要设置flag=1,这样直接将锁从解锁的线程传给了被唤醒的线程。
是的,在原文的代码实现中,线程被唤醒后就直接进入临界区
存在的问题就是唤醒的丢失,因为A线程加入队列后如果切换到释放锁的线程B,线程B唤醒A后,A继续执行然后睡眠,但是此时等待队列已经没有A了,所以会造成死锁
使用setpark,如果setpark后切换到另一个线程,调用unpark释放后,返回lock函数,pack会直接返回而不再睡眠
1 | void lock(lock_t *m) { |
1.解析参数
2.创建根节点
3.遍历进程文件建树,用read_proc_info读取父进程,创建节点
1 | char name[100] = "\0"; |
本意是想取掉一个多余的0号进程,不打印
调试发现是出现了段错误,把print_tree改成打印table[0]就可以了
具体错误有待查询
打印节点
加前缀
1 | init(Ubuntu-22. |
分三种情况
1.没有父进程,不加前缀
2.有父进程,但没有兄弟进程或者是兄弟进程中的最后一个└────
3.有父进程,有兄弟进程,且不是最后一个├────
根据深度,加|或者空格
可以选择打印传入参数的子进程
修改printf函数之后就可以了
1 | void print_tree(ProcessNode* node, int level,char* prefix) { |
vptr和tbl,typedef
动态绑定
函数指针
1 |
|
1 | Fun pFun = NULL;:这里,pFun是一个Fun类型的指针,也就是一个函数指针。它被初始化为NULL,表示它不指向任何函数。 |
typedef
C/C++ typedef用法详解(真的很详细)-CSDN博客
1 | type (*)(....)函数指针 |
1 | 理解复杂声明可用的“右左法则”: |
1 | 1. 原声明:int *(*a[5])(int, char*); |
实现原子指令用到了什么,一小段的不可被打断的指令
自旋锁,把1交换出去,其他的线程只能交换出0,并不断循环交换,临界区结束了之后把1还回去
另一个线程就把锁换过来
如果lock unlock函数加上参数,就相当于可以设多个锁,试图得到同一把锁的线程就实现了互斥
lock(&status)
1 | bool holding(spinlock_t *lk) {//当前是有锁状态,且锁的拥有者是当前cpu |
要正确使用锁很难,要经常使用断言,检查中断是否符合预测assert,可以读读xv6源码
在用户态
用户程序sum++拥有锁,被操作系统中断了,切换到了其他程序,
其他各个线程都无法获得锁,要等操作系统切回去
在操作系统中
会中断来实现锁
在操作系统内核:
连续上两次锁,中断一次再上锁,无法获得锁,就发生死锁
正确性准则
单处理器上锁解锁前后,中断状态不可改变,原来是中断还是中断,原来不中断还是不中断
多处理器,
使用栈保存中断状态
为了实现自旋一定要中断吗?
在用户态
因为自旋锁资源浪费严重
互斥锁(mutex)的实现使用了syscall,具有较好的scalability
futex如果没有锁直接访问,fast path
如何唤醒被syscall挂起的线程?
自己思考一下,想各种情况,修改后可以立即用model checker来验证
关中断+自旋实现互斥
保存锁前中断状态