Coder Social home page Coder Social logo

oop-thu's Introduction

OOP-THU

OOP Material & QA

已更新:

  • L2-编程环境-2020.pptx
  • L1-绪论-2020_0216.pptx

oop-thu's People

Contributors

jayzzhou-thu avatar yedeming avatar zibuyu avatar zzy14 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

oop-thu's Issues

类B 中含有另一类A 对象的初始化问题

类A定义(想要同时保留两个构造函数)
class A {
string x ;//*?
public:
A(string p){
x = p ;
}
A(){
}
~A(){
}
};

类B定义
class B {
private:
static A data1;
string y ;
//A data2;
public:
B(){
//A data2(y+"->data2") ;
}
~B(){
}
};

想对B中的 A data2 进行初始化:
如果将第一处注释 A data2 改为 A data2("string") 则出现编译错误。
如果将第一处注释保留,加上第二处注释以初始化,那么将会构建两个A,并析构两次。
那么如何只构建一次并完成初始化呢orz

计算图大作业相关问题

1.输入情况会不会出现环,比如a=sin b, b=sin c, c=sin a这样的情况?
2.如果一次输入造成多次Error,比如同时造成了分母为零和占位符未赋值的情况,应该输出所有Error信息还是仅仅输出一个?

往年题

请问老师上课说的往年的题可以再分享一次吗?

关于第二次作业B题的一个疑问

第二次作业的B题(引用?复制?II)的第五份代码如下:

const Test &F(Test a)
{
    Test &b = a;
    return b;
}
int main()
{
    Test a;
    Test A = F(a);
    return 0;
}

我运行后输出如下:

Test(): this->buf @ 0x7fc649c02ae0                //构造主函数中a
Test(const Test&) called. this->buf @ 0x7fc649c02af0           //构造函数F中a(临时变量)
Test(const Test&) called. this->buf @ 0x7fc649c02b00          //构造A
~Test(): this->buf @ 0x7fc649c02af0                           //销毁函数F中a(临时变量)
~Test(): this->buf @ 0x7fc649c02b00                     //销毁A
~Test(): this->buf @ 0x7fc649c02ae0                   //销毁主函数中a

“Test a”先调用无参数的构造函数构造Test类的对象a,并通过拷贝构造函数构建函数F的参数a。函数中“Test &b = a”定义了一个对a的Test类的引用变量,并以常引用的形式返回,答案认为存在潜在风险的原因似乎就是在这里函数结束后,临时变量被销毁,这里的返回值便成为空引用,但是从输出其实可以看出函数中临时变量a的销毁实际上是在完成A的赋值之后进行的,所以A其实是有存在真实内容的,请问这也算潜在风险吗…或者请问我的分析是否在哪里出错。

#include相关

关于#include

#include其实并不是一个非常聪明的机制——直接全文复制,也不管包含了多少用不着的代码;你也不甚清楚你包含的代码中有什么牛鬼蛇神,会不会碰巧撞上了math.h中的y1;假如处理不当,还可能惹来重复定义等令人头秃的麻烦……

在此,我列举一些初次深入了解#include时可能遇到的困扰,并加以说明。


套娃

事情开始于这样的代码:

// A.h
#pragma once
#include "B.h"

class A {
    B b;
};
// B.h
#pragma once
#include "A.h"

class B {
    A* a;
};
// main.cpp
#include "A.h"

// Do your thing...

我们在类A中设置了B类型的成员变量,因此需要#include "B.h"然而,出于某种需求,我们还希望在类B中保留对应的A的指针,因此还需#include "A.h" 看起来顺理成章。

可是,当我们编译时,g++报了错:

B.h:6:5:error: 'A' does not name a type
     A* a;
     ^

是在B.h中报了找不到类型A的错。

奇怪,我们明明在B.h中包含了A.h啊……


探因

我们将目光聚焦到A.h上——原来,A.h标上了#pragma once。也就是说,假如A.h之前已经被包含过了,那么这次就不会再包含它了。再一看main.cpp,确实,A.h早已被包含过了。

破案了!

好,我们将A.h中的#pragma once去掉总行了吧?还不行,这次又报找不到类型B了。

那就把B.h中的#pragma once也去掉吧……停下来!不然那编译器的报错……太美……

不过,至此,这背后的原因已可见端倪——套娃include。C++的include最忌讳的就是套娃了。如果不加#pragma once等处理,则头文件就会永无止境地包含下去;如果加了,那你写代码时可能以为自己include过了,实际上却被编译器拦下了。

总之,这种循环包含的行为是不可取的,在实际编程中应当避免。


解决

那么,应当如何修改代码,才能既满足需求,又不出现套娃的现象呢?

在动手之前,先想想,是否真的需要在B中保留A的指针。 因为,这种情况的发生,很有可能意味着你的代码设计时耦合度有些高,才会剪不断理还乱。如果能重新设计代码,让B干脆不依赖A,那是最好的。

不过,如果这一需求不可避免呢?那也有办法:

// B.h
#pragma once

class A;  // 声明类A
class B {
    A* a;
};

我们在B.h中不去#include "A.h",而是声明class A,供B使用,具体的细节则在A.h中给出。这样,既免去了循环包含,又能够在类B中用到类A

至此,“套娃”的问题暂告一段落。下面,再简单提一下#pragma once#ifndef...的事。


重复定义

我们知道,在C++中,对同一个名称,声明可以多次,但定义只能一次。为此,我们需要引入一些保证单次包含的机制,来防止因多次包含同一头文件而造成的重复定义。

#pragma once#ifndef...的用法,在课件上都有写到。这里,对使用过程中可能遇到的疑惑和误区简单说明一下。

#ifndef XXX含义的理解

#ifndef XXX#endif配套,可以理解为if not defined XXX,则……,end if。而在解析……所示的代码之前,需要先#define XXX,从而下次解析到这一头文件时,因为宏定义过XXX了,ifndef条件不满足,就不再解析……部分的代码了,从而保证了单次包含。

#ifndef XXX插入的位置

合理的使用方法,应当是#ifndef XXX#define XXX置于文件的开头而#endif置于文件的末尾,这样才能保证整个文件只被包含一次。

我之前见到过这样的写法:

#ifndef __HEADER__
#define __HEADER__

#include <iostream>
#include <algorithm>

#endif

class Test {
    // ...
};

这就违背保证整个文件只被包含一次的初衷了。假如这一头文件被包含多次,那也会造成Test的重复定义。

(当然,我个人以为出现这样的错误也与课件上只给了用法没给示例有关。)

#pragma once#ifndef...的区别

#pragma once可以简单快捷地保证物理上的这一文件只被包含一次,不过缺点在于一些编译器可能不支持。(当然,越来越多的编译器已经支持这一功能了。)

#ifndef XXX则是从代码层面保证单次包含,且类似写法可以在其它场合有一些灵活的运用。缺点在于你需要保证不同头文件的XXX不要撞车,否则也会导致预期之外的结果。(当然,许多IDE会为新建的.h文件自动加上#ifndef...等语句,可以省去不少麻烦。)


写在最后

读到这里,或许你对#include的机制更加不理解了还有一些困惑。也许,你很想亲自看到,编译器对这些带#的语句到底做了些什么。

这时,我们来了解一下g++的预编译指令。例如:

g++ -E main.cpp -o main.i

-E表示当前的任务是对main.cpp进行预编译。预编译的一个任务就是将这些带#的宏命令进行处理,比如#include的内容会在预编译时展开。这时,你就能看到那些头文件到底是谁先谁后了。

头文件中的宏定义

宏定义的好处之一:宏定义的使用可以增加预编译指令,防止多次包含同一头文件时出现编译错误。

// func.h
int ADD(int a, int b);

//以下为增加宏定义的写法
#ifndef FUNC_H
#define FUNC_H
int ADD(int a, int b);
#endif

结论:在头文件中使用宏定义,可以使得头文件在一个源文件中被多次include时,同一段代码只被编译一次。一个头文件在不同的源文件中,依旧会被编译多次。因此若在头文件中定义函数或变量,即使头文件中使用了宏定义,头文件中的代码在多个源文件中依旧会被编译多次,导致链接时出错。

我们从下面的样例中进行分析:

func1.h
int add(int x, int y){
  return x + y;
}
func2.h
#include "func1.h"
int add_2(int x, int y){
  return add(x, y) + 1;
}
main.cpp
#include <iostream>
#include "func1.h"
#include "func2.h"

using namespace std;
int main(){
  int x = 2, y = 3;
  cout << add_2(x, y) << endl;
}

执行编译命令:g++ main.cpp -o main,编译出错:

In file included from main.cpp:2:
In file included from ./func2.h:1:
./func1.h:1:5: error: redefinition of 'add'
int add(int x, int y){
    ^
./func1.h:1:5: note: previous definition is here
int add(int x, int y){
    ^
1 error generated.

原理:#include 指令将被包含的文件代码直接复制到当前文件,即 main.cpp 中的 #include "func1.h"#include "func2.h"使得 main.cpp 等价为如下代码:

#include <iostream>

// #include "func1.h"
int add(int x, int y){
  return x + y;
}

// #include "func2.h"
int add(int x, int y){
  return x + y;
}

int add_2(int x, int y){
  return add(x, y) + 1;
}

// main.cpp
using namespace std;
int main(){
  int x = 2, y = 3;
  cout << add_2(x, y) << endl;
}

因此,func1.h 在main.cpp中被重复包含,若无宏定义,则该段代码编译出错。宏定义即可解决该点问题,使得在main.cpp中func1.h的代码只被编译一次。即将func1.h修改为如下情况,则编译可以顺利通过

#ifndef FUNC1_H
#define FUNC1_H
int add(int x, int y){
  return x + y;
}
#endif

多个源文件包含func1.h:虽然宏定义解决上面单个文件中多次包含的问题,但依旧需要避免在头文件中进行变量定义及函数定义。

保留上述func1.h的实现不变,定义func1.cpp并修改main.cpp如下:

func1.h

#ifndef FUNC1_H
#define FUNC1_H
int add(int x, int y){
  return x + y;
}
#endif

func1.cpp

#include "func1.h"
int add_2(int x, int y){
  return add(x, y) + 1;
}

main.cpp

#include "func1.h"
#include <iostream>
using namespace std;
int add_2(int x, int y);
int main(){
  int x = 2, y = 3;
  cout << add_2(x, y) << endl;
}

执行编译命令g++ func1.cpp main.cpp -o main,编译报错如下

duplicate symbol 'add(int, int)' in:
    /var/folders/2k/q001fz5n70j17wpl5v5lltxw0000gn/T/func1-3e6d02.o
    /var/folders/2k/q001fz5n70j17wpl5v5lltxw0000gn/T/main-80fcd6.o
ld: 1 duplicate symbol for architecture x86_64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

报错原因就是,在func1.cpp与main.cpp中,add函数被重复定义。宏定义作用范围仅为当前文件,无法跨文件起作用。即func1.cpp中的#define FUNC1_H与main.cpp中的#define FUNC1_H不相关,分别仅在func1.cpp与main.cpp中有效。

因此,宏定义可以解决单个源文件中某段代码被重复定义带来的错误。无法解决在多个源文件中重复include的问题。

Makefile流程控制相关

Makefile中if判断

条件表达式

书写规则

<条件判断>
<makefile或shell命令1>
else
<makefile或shell命令2>
endif

其中shell命令仅当整个条件表达式位于某一目标下,并在行首有<tab>制表符才能正常被使用。而条件判断语句有以下四个:

条件判断1:ifeq

判断两字符串是否相等,相等则执行命令1,否则执行命令2

ifeq (<参数1>,<参数2>)

或者

ifeq "<参数1>" "<参数2>"

或者

ifeq '<参数1>' '<参数2>'

条件判断2:ifneq

ifeq的否定,判断两字符串是否相等,不相等则执行命令1,否则执行命令2

条件判断3:ifdef

判断字符串是否为空,如果不为空则执行命令1,否则执行命令2

ifdef <参数>

条件判断4:ifndef

ifdef的否定,判断字符串(的定义)是否为空,如果为空则执行命令1,否则执行命令2

注意:
ifdef判断的是变量的定义而非实际值
如:

var = 
ifdef var
result = True
else
result = False

其中$(result)False,但

pre = 
var = $(pre)
ifdef var
result = True
else
result = False

其中$(result)True

利用以上条件表达式,以及变量赋值规则即可完成本次作业中Makefile相关题目。
但假设题目B中有f1.cpp, f1.h, ..., fn.cpp, fn.h,那么仅使用条件表达式不足以完成题目要求,自然想到利用循环解决问题。
题目中给出的阮一峰的博客中所提到的for循环,实质为shell脚本,仅能在目标内调用,笔者并不很熟悉,大家可以自行深入了解。
另外,条件判断式在Makefile文件读取时计算,在使用如$@, $<等,在运行时定义的自动化变量时,可能出现错误。
下从Makefile提供的内置函数出发,通过变量实现循环与条件判断的流程控制。

Makefile中内置函数

书写规则

$(<函数名> <参数1>(,<参数2>,<...>))

if函数

$(if <条件参数>,<返回值1>)

或者

$(if <条件参数>,<返回值1>,<返回值2>)

判断条件参数(的值)是否为空,为空则函数值为返回值1,否则函数值为返回值2,无返回值2参数则为空串。

foreach函数

($foreach <中间变量名>,<列表参数>,<表达式>)

类比c++中的foreach,此函数的作用是将列表参数中每个以空格分隔的字符串取出,赋值到中间变量,再计算表达式的值。将所得值以空格分隔组成新的字符串作为函数值。
例:

($foreach i,one two three,$(i).o)

上一语句的值为one.o two.o three.o

其他辅助函数

($findstring <子串>,<原串>)

原串中匹配到子串则函数值为字串,否则为空。

($patsubst <模式串>,<替换模式串>,<字典串>)

字典串中有复数个以空格(或<tab>,回车)分隔的子串,在字典串中匹配符合模式串模式的子串,并用替换模式串替换,作为函数值。
例:

($patsubst %.o,%.cpp,one.o two.o three.o)

函数值为one.cpp two.cpp three.cpp

利用上述内置函数,可以在变量定义时进行控制,从而完成对任意多个源文件的条件编译,样例如下:

F1 = True
F2 = False
F3 = True
F4 = False
F5 = True
F6 = False
F7 = True

LIST := 1 2 3 4 5 6 7
FILEO := $(foreach i,$(LIST),$(if $(findstring TRUE,$(F$(i))),f$(i).o))
FILEH := $(patsubst %.o,%.h,$(FILEO))
DEF := $(patsubst f%.o,-D __F%__,$(FILEO))  

其中$(FILEO)f1.o f3.o f5.o f7.o$(FILEH)f1.h f3.h f5.h f7.h$(DEF)-D __F1__ -D __F3__ -D __F5__ -D __F7__

f%.o:f%.cpp f%.h
g++ -c $< -o $@ $(patsubst f%.o,-D __F%__,$@)

以上代码可以完成f*.cpp的编译,与对应的宏定义,说明能够利用运行时定义的自动化变量。
再添加相关目标(main, main.o, clean)等,即可完成题目要求。

【HW1】作业第四题Computer类的name, num, price等数据成员的属性(访问权限)问题

首先要考虑的就是,设置成private好,还是public好?
我觉得从这些数据的意义看应该是private比较合适,因为电脑的型号、库存与价格都是相对确定的,不是轻易允许访问和更改的;然后它又必须允许更改,所以不可以是const;并且是每个对象独有的,写成static没有意义,所以它们就是一些普通的private成员。
我想问的就是,如何通过某个Computer类对象的指针去访问、显示甚至更改这些信息?还是说有更好的实现方式?

oop本学期主要笔记整理

大家好,下方的清华云盘链接是我和三位同学一同整理的oop本学期的笔记,从L1绪论直到L8虚函数为五一前整理,共7篇,并在五一进行了重写审查。L9之后的内容为五一后整理,共3篇。
https://cloud.tsinghua.edu.cn/d/ccefd5d38f8645208637/

近期不断更新六次选择题的解析,也在该文件夹内。

在课件的基础上,我们尽力做到了对于大多PPT有全面的解读,同时联系前后课程的内容与课程作业,对于课件中的一些操作也有一定的扩展。如果有部分内容存在错误,敬请联系微信:18015766633

可以复制的代码块如下:
https://www.kdocs.cn/l/cckVHQBYqHTo
L1、L2·绪论与编程环境

https://www.kdocs.cn/l/crWXG24bdHPr
L3·封装与接口

https://www.kdocs.cn/l/coTCBYsG5Ne8
L4·创建与销毁·一

https://www.kdocs.cn/l/clSikmcvwx2T
L5·创建与销毁·二

https://www.kdocs.cn/l/cdRSPkfeGGn3
L6·引用与复制

https://www.kdocs.cn/l/cnwLdu940n5H
L7·组合与继承

https://www.kdocs.cn/l/chcvAifQ6lxu
L8·虚函数

https://www.kdocs.cn/l/cmtYpWYM8yho
l9 多态与模板

https://www.kdocs.cn/l/cjl5CLaRJhNf
L10 模板与STL初步

https://www.kdocs.cn/l/cgwBOfWZC3Rq
L12 函数对象和智能指针

每一节的目录
L1、L2 绪论与编程环境
一、 绪论 ........................................................................................................................................2
1.1 面向对象程序的可靠性....................................................................................................2
1.2 对象的性质 .......................................................................................................................2
二、 源程序的结构、编译、链接.................................................................................................2
2.1 源程序的结构....................................................................................................................2
2.2 预编译指令 .......................................................................................................................2
2.2.1 定义 ........................................................................................................................2
2.2.2 文件包含.................................................................................................................3
2.2.3 宏替换.....................................................................................................................3
2.2.4 条件编译指令.........................................................................................................5
2.2.5 其他预处理指令.....................................................................................................6
2.3 标识符 ...............................................................................................................................7
2.3.1 定义 ........................................................................................................................7
2.3.2 组成 ........................................................................................................................7
2.3.3 定义规则.................................................................................................................7
2.3.4 命名规范(非强制).............................................................................................7
2.4 编译与链接 .......................................................................................................................8
2.4.1 过程 ........................................................................................................................8
2.4.2 编译指令.................................................................................................................8
2.4.3 链接 ........................................................................................................................8
2.5 头文件 ...............................................................................................................................8
2.5.1 意义 ........................................................................................................................8
2.5.2 例子 ........................................................................................................................8
2.6 函数的声明与定义............................................................................................................9
2.6.1 概念 ........................................................................................................................9
2.6.2 变量的声明与定义.................................................................................................9
2.6.3 extern 关键字...........................................................................................................9
2.6.4 结论 ........................................................................................................................9

L3 封装与接口
一、 函数重载 ................................................................................................................................2
1.1 定义与意义 .......................................................................................................................2
1.2 区别方法 ...........................................................................................................................2
1.3 自动类型转换....................................................................................................................2
1.3.1 定义与性质.............................................................................................................2
1.3.2 优先匹配调用.........................................................................................................2
二、 参数缺省值 ............................................................................................................................3
2.1 定义 ...................................................................................................................................3
2.2 语法 ...................................................................................................................................4
2.3 缺省值保护 .......................................................................................................................4
三、 auto 关键字与 decltype ..........................................................................................................5
3.1 作用与意义 .......................................................................................................................5
3.1.1 自动确定变量的类型..............................................................................................5
3.1.2 追踪返回类型的函数.............................................................................................5
3.1.3 auto 的进一步阐述..................................................................................................6
3.2 auto 其他性质.....................................................................................................................6
3.3 decltype...............................................................................................................................6
3.4 auto 的优势.........................................................................................................................7
5.5 auto 字符例题.....................................................................................................................8
四、封装与内联函数.......................................................................................................................9
4.1 private 与 overload 的先后.................................................................................................9
4.2 内联函数 .........................................................................................................................10
4.2.1 定义与意义...........................................................................................................10
4.2.2 内联函数和宏定义的区别...................................................................................10
4.2.3 内联函数的注意事项...........................................................................................11

创建与销毁·一
创建与销毁·一 ..........................................................................................................................................3
1.1.4 初始化列表和构造函数体的基本区别..........................................................................................3
1.2 委派构造函数实例.............................................................................................................................3
3.2 返回静态局部对象的引用.................................................................................................................3
一、 构造函数与析构函数......................................................................................................................3
1.0 面向对象程序的可靠性 ............................................................................................................3
1.1 构造函数 ....................................................................................................................................3
1.1.1 意义.................................................................................................................................3
1.1.2 语法.................................................................................................................................3
1.1.3 初始化列表.....................................................................................................................4
1.1.4 初始化列表和构造函数体的基本区别.........................................................................4
1.1.5 初始化列表的初始顺序.................................................................................................4
1.2 委派构造函数 ............................................................................................................................5
1.2.1 定义.................................................................................................................................5
1.2.2 意义.................................................................................................................................5
1.2.3 实例.................................................................................................................................5
1.3 就地初始化 .................................................................................................................................6
1.4 默认构造函数 ............................................................................................................................7
1.4.1 定义.................................................................................................................................7
1.4.2 语法.................................................................................................................................7
1.4.3 编译器的额外操作.........................................................................................................7
1.4.4 隐式定义的默认构造函数.............................................................................................7
1.4.5 显式声明默认构造函数.................................................................................................9
1.4.6 显式删除危险构造函数.................................................................................................9
1.5 对象数组的初始化(在 main 中) ........................................................................................10
1.6 析构函数 ..................................................................................................................................10
1.6.1 概述...............................................................................................................................10
1.6.2 语法...............................................................................................................................11
1.6.3 析构顺序.......................................................................................................................11
1.6.4 默认析构函数...............................................................................................................11
二、 对象的析构与构造........................................................................................................................12
2.1 局部对象的构造与析构 ..........................................................................................................12
2.1.1 定义...............................................................................................................................12
2.2.2 全局变量的局限性.......................................................................................................12
三、 引用................................................................................................................................................13
3.1 定义与语法 ..............................................................................................................................13
3.2 结合函数使用 ..........................................................................................................................13
3.3 引用的其他特点 ......................................................................................................................16
四、 运算符重载....................................................................................................................................17
4.1 意义 ..........................................................................................................................................17
4.2 语法 ..........................................................................................................................................17
4.3 具体的重载实例 ......................................................................................................................18
4.3.1 可重载类型...................................................................................................................18
4.3.2 前后缀重载...................................................................................................................19
4.3.3 函数运算符()重载........................................................................................................23
4.3.4 数组下标重载...............................................................................................................24
4.3.5 只能成员函数型重载的运算符...................................................................................26
4.3.6 流运算符重载...............................................................................................................26

创建与销毁·二
一、 变量与静态变量......................................................................................................................2
1.1 四类变量的区别.................................................................................................................2
1.2 静态变量.............................................................................................................................4
1.2.1 定义..........................................................................................................................4
1.2.2 初始化......................................................................................................................4
1.2.3 静态局部变量..........................................................................................................4
1.2.4 静态全局变量..........................................................................................................4
1.2.5 全局变量与局部变量..............................................................................................4
1.2.6 内部可链接与外部可链接......................................................................................4
1.3 Static 数据成员(类变量)................................................................................................5
1.3.0 声明、定义..............................................................................................................5
1.3.1 静态数据成员定义与基本性质..............................................................................6
1.3.2 例子..........................................................................................................................6
1.4 Static 成员函数....................................................................................................................7
1.4.1 定义与基本性质......................................................................................................7
二、 常量数据成员与函数..............................................................................................................7
2.1 常量......................................................................................................................................7
2.1.1 常量的定义..............................................................................................................7
2.1.2 常量的性质..............................................................................................................7
2.2 常量数据成员......................................................................................................................7
2.2.1 常量数据成员的定义..............................................................................................7
2.3 常量成员函数......................................................................................................................8
2.3.1 常量成员函数的定义..............................................................................................8
2.3.2 意义..........................................................................................................................8
2.3.3 写法..........................................................................................................................8
2.4 常量对象.............................................................................................................................8
2.5 常量静态变量.....................................................................................................................8
2.5.1 常量静态变量的意义..............................................................................................8
2.5.2 定义方法..................................................................................................................8
2.5.3 访问权限..................................................................................................................9
2.6 常量静态函数不存在.........................................................................................................9
2.7 重载匹配性.........................................................................................................................9
三、 构造与析构..............................................................................................................................9
3.1 常量对象的析构和构造......................................................................................................9
3.2 静态对象的构造与析构...................................................................................................10
3.3 类静态对象构造与析构...................................................................................................10
3.4 参数对象构造与析构.......................................................................................................11
3.4.1 传递形参................................................................................................................11
3.4.2 传递引用与指针....................................................................................................11
3.4.3 类成员含有指针....................................................................................................12
3.4.4 传入引用的优点....................................................................................................12
四、 对象的 new 和 delete ............................................................................................................12
4.1 概述...................................................................................................................................12
4.2 图示...................................................................................................................................12
4.3 匹配性...............................................................................................................................13
4.3.1 搭配使用................................................................................................................13
4.3.2 搭配不当................................................................................................................13
4.4.3 例子........................................................................................................................13
五、 友元........................................................................................................................................14
5.1 定义与基本性质...............................................................................................................14
5.2 跨类友元...........................................................................................................................14
5.2.1 定义........................................................................................................................14
5.2.2 区域无关性............................................................................................................15
5.2.3 不冲突性................................................................................................................15
5.3 友元类...............................................................................................................................15
5.4 友元的注意事项...............................................................................................................15
5.4.1 非对称性................................................................................................................15
5.4.2 非传递性................................................................................................................15
5.4.3 不可继承................................................................................................................15
5.4.4 不可定义................................................................................................................15

引用与复制
一、 常量引用..................................................................................................................................3
1.1 常量引用的意义.................................................................................................................3
二、 拷贝构造函数..........................................................................................................................3
2.1 定义与语法规则..................................................................................................................3
2.2 调用时机..............................................................................................................................4
2.3 隐式拷贝构造函数.............................................................................................................4
2.4 执行顺序.............................................................................................................................4
2.4.1 基本的执行顺序......................................................................................................5
2.4.2 例子..........................................................................................................................5
2.5 拷贝构造函数的缺陷.........................................................................................................6
2.6 解决方法.............................................................................................................................7
三、 移动构造函数..........................................................................................................................7
3.1 右值与右值引用.................................................................................................................7
3.2 移动构造函数.....................................................................................................................9
3.3 移动语句...........................................................................................................................14
3.4 两类构造函数的调用时机...............................................................................................16
3.4.1 判断依据................................................................................................................16
3.4.2 拷贝构造函数的常见调用时机............................................................................16
3.4.3 移动构造函数的常见调用时机............................................................................16
四、 拷贝与移动赋值运算符........................................................................................................17
4.1 拷贝赋值运算符定义与意义...........................................................................................17
4.2 移动赋值运算符作用与意义...........................................................................................18
4.3 调用时机...........................................................................................................................18
4.4 自动合成的函数与运算符...............................................................................................18
五、 返回值优化............................................................................................................................19
5.1 优化条件............................................................................................................................19
5.2 优化意义............................................................................................................................19
5.3 优化实例............................................................................................................................19
5.4 返回值构造.......................................................................................................................19
六、 delete 与检测.........................................................................................................................20
6.1 Delete 的意义 ....................................................................................................................20
6.2 Delete 的检测 ....................................................................................................................20
6.3 赋值的检测.......................................................................................................................20
七、 move 与类型转换..................................................................................................................21
该部分为第三次作业第二题的解析,建议结合阅读..................................................21
7.1 move 的意义......................................................................................................................21
7.2 例子...................................................................................................................................21
7.3 进一步讨论.......................................................................................................................21
八、 置空性讨论............................................................................................................................22
8.0 析构置空...........................................................................................................................22
8.1 移动置空...........................................................................................................................22
8.2 赋值置空............................................................................................................................23
九、 类型转换................................................................................................................................23
9.1 意义...................................................................................................................................24
9.2 语法...................................................................................................................................24
9.3 例子...................................................................................................................................25
9.4 禁止自动类型转换...........................................................................................................28
9.5 四类强制类型转换...........................................................................................................29

虚函数
虚函数 ..............................................................................................................................................2
readme ..............................................................................................................................................2
一、向上类型转换 ..........................................................................................................................2
1.1 定义 ...................................................................................................................................2
1.2 不允许非 public 继承的向上转换....................................................................................2
1.3 例子 ...................................................................................................................................3
二、对象切片 ..................................................................................................................................3
2.1 定义 ...................................................................................................................................3
2.2 例子 ...................................................................................................................................4
2.3 对象切片的理解................................................................................................................6
2.4 进一步讨论 .......................................................................................................................8
三、虚函数 ....................................................................................................................................11
3.1 函数调用绑定..................................................................................................................11
3.2 虚函数定义 .....................................................................................................................12
3.3 虚函数表 .........................................................................................................................13
3.3.1 概述 ......................................................................................................................13
3.3.2 类的虚表...............................................................................................................13
3.3.3 虚表指针...............................................................................................................14
3.3.4 动态绑定...............................................................................................................15
3.3.5 动态绑定的条件...................................................................................................18
3.3.6 虚指针大小...........................................................................................................18
3.3.7 总结 ......................................................................................................................19
3.4 虚函数与构造、析构......................................................................................................19
3.4.1 虚函数与构造函数...............................................................................................19
3.4.2 虚函数与析构函数...............................................................................................21
3.5 overload, override, redefining...........................................................................................22
3.6 override 关键字 ................................................................................................................25
3.7 final 关键字......................................................................................................................25

组合与继承
一、 组合 ........................................................................................................................................2
1.1 定义 ...................................................................................................................................2
1.2 两种实现方式....................................................................................................................2
1.2.1 共有成员.................................................................................................................2
1.2.2 私有成员.................................................................................................................2
1.2.3 public 接口访问 private 数据..................................................................................2
1.3 构造与析构 .......................................................................................................................5
1.3.1 子对象参数构造.....................................................................................................5
1.3.2 子对象默认构造.....................................................................................................5
1.3.3 构造次序.................................................................................................................5
1.3.4 析构次序.................................................................................................................5
1.3.5 例子 ........................................................................................................................5
二、 继承 ........................................................................................................................................9
2.1 定义 ...................................................................................................................................9
2.3 无法继承 ...........................................................................................................................9
2.4 构造与析构 .....................................................................................................................10
2.5 两类继承方式的选择......................................................................................................13
2.6 成员访问权限..................................................................................................................13
2.7 组合与继承的联系..........................................................................................................17
三、 重写隐藏与重载...................................................................................................................18
3.1 定义 .................................................................................................................................18
3.2 using 一并启用.................................................................................................................20
3.3 using 关键字作用总结.....................................................................................................21
3.3.1 继承基类构造函数、恢复被屏蔽的基类成员函数 ...........................................21
3.3.2 指示命名空间 using namespace std;....................................................................21
3.3.3 将另一个命名空间的成员引入当前命名空间....................................................21
3.3.4 定义类型别名,如:using a = int;......................................................................21
四、 多重继承 ..............................................................................................................................22
4.1 定义、意义与潜在风险..................................................................................................22
例二、 同名成员操作...........................................................................................................24
多态与模板
一、抽象类与纯虚函数............................................................................................................................1
1.1 定义 ............................................................................................................................................1
1.2 抽象类继承 ................................................................................................................................2
1.3 纯虚析构函数 ............................................................................................................................2
1.4 纯虚析构函数和其他纯虚函数的区别 ....................................................................................3
二、 向下类型转换..................................................................................................................................4
2.1 定义 ............................................................................................................................................4
2.2 转换方式 ....................................................................................................................................5
2.2.1 安全向下类型转换.........................................................................................................5
2.2.2 快速向下类型转换.........................................................................................................5
2.2.3 总结.................................................................................................................................7
三、 抽象类与纯虚函数........................................................................................................................11
3.1 多重继承 ..................................................................................................................................11
3.2 例子 ..........................................................................................................................................12
四、 多态 Polymorphism .......................................................................................................................12
4.1 定义 ..........................................................................................................................................12
4.2 优势 ..........................................................................................................................................13
五、 函数模板与类模板........................................................................................................................14
5.1 意义与定义 ..............................................................................................................................14
5.2 实例化与自动推导 ..................................................................................................................15
5.3 模板原理 ..................................................................................................................................18
六、 类模板............................................................................................................................................18
6.1 定义 ..........................................................................................................................................18
6.2 模板参数 ..................................................................................................................................19
6.3 模板与多态 ..............................................................................................................................21

类之间定义声明顺序相关

一个类作为其他类的数据成员

A 类中如果存在 B 类的对象作为数据成员,需要 B 类在 A 类之前定义,比如:

class A {};
class B
{
    A a;
};

但如果是存在 B 类的引用或者指针,则只需在 B 类定义之前声明 A 类,比如:

class A;
class B
{
    A &a;
    A *b;
};
class A {};

一个类的成员函数利用另一个类

B 类的一个成员函数中,如果参数中含有 A 类,则 B 类定义之前需要先声明 A 类。

如果参数中是 A 类的对象,则须在成员函数定义之前定义 A 类(但 A 类的成员函数定义可以放在之后),比如:

class A;
class B
{
    void func(A a);
};
class A
{
public:
    void func();
};
void B::func(A a)
{
    a.func();
}
void A::func() {}

如果参数中涉及到的是 A 类的引用或者指针,则需要根据是否在此函数中调用 A 类成员来决定是否需要在函数定义之前定义 A 类,比如:

class A;
class B
{
    void func(A* a);
};
void B::func(A* a)
{
}
class A
{
public:
    int data;
};

这里 B 类的成员函数 func() 虽然使用了 A 类指针,但没有利用其中的成员,所以可以将 func() 的定义置于 A 类定义之前。

一个类的成员函数作为另一个类的友元函数

如果类 B 含有友元函数 A::func(),则需要在 B 类定义之前定义 A 类,但 A::func() 的定义同样可以放在之后。

class A
{
public:
    void func();
};
class B
{
    friend void A::func();
};
void A::func() {}

一个类作为另一个类的友元类

如果 A 类是 B 类的友元类,无论 A 类在 B 类之前是否声明,都可以如此声明友元:

class B
{
    friend class A;
};
class A {};

而只有在 A 类在 B 类之前声明的情况下才能使用下面方法声明友元:

class A;
class B
{
    friend A;
};
class A {};

继承的定义顺序

B 类继承 A 类时,必须要在 B 类定义之前给出 A 类的定义,比如:

class A {};
class B : public A {};

因此类的继承关系必定是一个有向无环图。

L11笔记

纯属试水)

  • 命名空间

  • 定义了一堆内容(常量、变量、结构、类、函数……)的地方。

  • 定义一个命名空间,方法和定义一个类差不多,只不过不需要末尾分号。

  • 使用时:空间名::变量名。

  • using声明:

    • 使用整个:using namespace A;
    • 使用部分:using 空间名::变量名
  • STL初步

  • 标准模板库。含有算法、容器、函数、迭代器四个组件。

  • 模板编写,分离数据和操作。

  • 命名空间是std。可以用std::名字 来使用stl的函数或对象。

  • STL容器:简单容器

(1) Pair

  • 两个数据first&second,类型可以不一样。
  • Std::pair<int, double> t; 模板写法,创建实例对象前先说清参数类型
  • 获取数据/修改数据:通过first, second两个成员。
  • 创建:auto t = std::make_pair(1, 4); 用make_pair函数。(Make_pair 也是std里的一个函数)
  • 比较:先比first再比second。前提是类型可以比(两个pair的first是可比类型)

(2)tuple

  • 若干成员组成的元组,高级版pair。

  • 获取数据:get函数。v0 = std::get<0>(tuple1); 上面尖括号里面是下标,不可以是变量。

  • 下标需要在编译时确定:不能设定运行时可变的长度,不能当做数组。

  • 创建:make_tuple. 参数表里是n个数据。这个n决定了tuple的长度。auto t = std::make_tuple(“abc”, 7.8, 123, ‘3’);

  • 创建/修改数据:tie函数可以返回左值引用的元组。

std::string x; double y; int z;

std::tie(x, y, z) = std::make_tuple(“abc”, 7.8, 123);

//等价于 x = "abc"; y = 7.8; z = 123

std::tie(x, y, z) 就是tuple的左值引用。(x,y,z就是数据成员的左值引用)

这样可以给x,y,z赋值。改变他们时,tuple也就变了

  • P22示例:

在这样操作之后,xval和halfx的值就被更新了。

这里maketuple是作为了传递返回值的工具。

​ 额外示例:

#include<tuple>
#include <iostream>
int main(){
//创建元组
std::tuple<int, char, double>tp(2, 'b', 1.9);
auto data0 = std::get<0>(tp);//获得里面的元素
auto data1 = std::get<1>(tp);
auto data2 = std::get<2>(tp);
std::cout<<data0<<" "<<data1<<" "<<data2<<" "<<std::endl;

auto tup1 = std::make_tuple("hello", 'a', 1.3);
std::cout<<std::get<0>(tup1)<<" "<<std::get<1>(tup1)<<" "<<std::get<2>(tup1)<<std::endl;

const char * a;
char b;
double c;
std::tie(a, b, c) = tup1;
std::cout << a << "  " << b << "  " << c << std::endl;
std::tie(std::ignore, b, c) = tup1;
std::cout<<std::get<0>(tup1)<<" "<<std::get<1>(tup1)<<" "<<std::get<2>(tup1)<<std::endl;//这并不导致ignore的那个东西在源tuple里没有
// tie: 用于拆开tuple
// 如果不想要某一位的值,可以直接将其用ignore代替。

int aa = 5;
int &bb = aa;
char *p = "hello";
char *&pp = p;


auto tup2 = std::forward_as_tuple(bb, pp);
auto data5 = std::get<0>(tup2);
std::cout <<"****"<< data5 << std::endl;
// forward_as_tuple: 用于接受右值引用数据生成tuple

//上述代码创建了一个tuple<int &&, char (*&)>类型的元组。

//意思就是可以使用右值作为参数,而前面讨论的tie函数就只能接受左值

std::tuple<float, std::string> tup3(3.14, "pi");
std::tuple<int, char> tup4(10, 'a');

auto tup5 = tuple_cat(tup3, tup4);

//将tup1和tup2连起来就成了tup3

}
//结果:
2 b 1.9 
hello a 1.3
hello  a  1.3
hello a 1.3
****5
  • STL容器:序列容器

(1)vector

  • 自动扩展容量的数组。循序。

  • 可以替代原生数组。

  • 可以直接用下标访问。

  • 创建:和创建类模板的实例没有区别。在尖括号里指明数据的类型。std::vector x

  • 取长度:x.size();

  • 清空:x.clear();

  • 末尾加入元素:x.push_back(component);

  • 删除末尾元素:x.pop_back();

  • 在中间添加或删除:x.insert(x.begin()+1, 数据); x.erase(x.begin()+1);

    • 迭代器:一种遍历元素,检查元素的数据类型
    • 类似指针。
    • Begin()函数返回指向第一个元素的迭代器。
    • End()函数返回指向最后一个元素之后的位置的迭代器。
    • 下一/n个元素:++iter或iter++; iter+=n;
    • 上一/n个元素:--iter; iter-=n;
    • 访问/修改元素:*为解引用运算符(当成指针看待),他的特点是可以对元赋值(返回的是左值引用。)*iter=5; 把当前指向的数据改成5.
    • 虽然iter的类型不是int,但是两个iter的差是int。
    • 遍历vector:
      图片
      图片

    • **上面两种写法走的都是迭代器;但如果这么写走的就是元素了:(for auto i: vec)

      这样i就是元素的类型而不是迭代器。

  • erase函数:删除迭代器指向的元素而不是迭代器本身。例如:

​ auto it = vec.begin();

​ vec.erase(it);//删除它指向的元素,不是删除迭代器

  • 迭代器的失效:

    • 当迭代器不再指向本应指向的元素时,称此迭代器失效。
    • 调用insert/erase后,所修改位置之后的所有迭代器失效。(原先的内存空间存储的元素被改变)

​ 调用push_back等修改vector大小的方法时,可能会使所有迭代器失效(为什么?)(因为Push_back到了一定程度之后,可能会造成数组的整体移动,导致所有的内存地址发生改变。)

  • 因此在遍历过程中添加元素可能导致迭代器失效。其实就是,当size==capacity时,继续push_back会导致迭代器失效。

    vector<int> vec = {1,2,3,4,5};
       auto first = vec.begin();
       auto second = vec.begin() + 1;
       auto third = vec.begin() + 2;
       auto ret = vec.erase(second);
       //first指向1,second和third失效
       //ret指向3(当前在second的位置)
  • 上面的解释: erase的返回值也是迭代器。当删除second时,删除的不是那个叫做second的迭代器,而是它指向的元素2. 这时2后面的元素3顶上来,替代了2的位置。因此ret作为erase(second)的返回值,其实指向的就是原先second指向的位置,这个位置上现在是顶上来的3.

  • 在修改过容器后,不使用之前的迭代器。

(2)list 链表

  • 定义:std::list l; 跟前面都一样,尖括号用来实例化。

  • 操作:

    • 插入前端: push_front(数据);

    • 插入末端:push_back(数据);

    • 查询:find函数,返回迭代器。std::find(l.begin(), l.end(), 2);Find的第三个参数是什么意思?应该是从开头到结尾找值为2的元素。会返回查询结果的迭代器。

    • find的使用额外示例:

    • - **int** main(){
      
          list <**int**> L;
      
          L.push_back(1);
      
          L.push_front(0);
      
          L.push_back(2);
      
          list<**int**>::iterator iterPos=find(L.begin(), L.end(), 2); 
      
          *//在这个位置放入4*
      
          L.insert(iterPos, 4);
      
          for(**auto** i: L){
      
        ​    cout<<i<<" ";
      
          }
      
          *//4在2的前面。*
      
          return 0;
      
        }
      //结果:0 1 4 2 
      - 

      find感觉不是很好用,主要困难点在于返回的是迭代器。那我们在实际使用它的时候,一般会和那些接受迭代器做参数的函数一起。

    • 插入指定位置:insert( it(位置), 数据);

  • 不支持下标等随机访问,访问主要依靠迭代器。

  • 插入和删除操作不会导致迭代器失效**(除指向被删除的元素的迭代器外)**

  • STL容器:关联容器

(1)set 无序集合

  • 定义:std::set s;

  • 特点:不重复元素组成的集合。不保持插入顺序,内部按大小排序

  • 操作:

    • 插入(不允许出现重复元素):s.insert(val);
    • 查询值为val的元素:

​ s.find(val); //返回迭代器,这是find的特征。

  • 删除:

​ s.erase(s.find(val)); //导致迭代器失效

  • 统计值为val的元素的数目:返回数字。

      s.count(val);  //val的个数,总是0或1
    

(2)map 关联数组

  • 概念:每个元素由两个数据项组成,map将一个数据项映射到另一个数据项中。(形象的说就是“键值对”)

​ Map是个词典。存储键值对。

​ Key就像是查询项,如下标。称为键。

​ T则是内容。称为值。

  • 元素类型为pair<Key, T>。

  • 可以通过下标(也就是键key)(可以不是整数,可以是各种类型比如string)进行访问。如果元素不存在,则创建对应元素。

  • 可以用insert插入。

  • 其它操作:

    • 查询键为key的元素:

    s.find(key); // 返回迭代器

  • 统计键为key的元素个数:

    s.count(key); // 返回0或1

  • 删除:

    s.erase(s.find(key)); //导致被删元素的迭代器失效

  • STL容器:总结

​ 序列容器与关联容器的区别:

序列容器中的元素有顺序,可以按顺序访问。

关联容器中的元素无顺序,可以按数值(大小)访问。

vector中插入删除操作会使操作位置之后全部的迭代器失效。

函数返回类的return对象是否析构

hw2第三题的一段代码
A f1(A b) {
cout << "------after call------" << endl;
A f;
cout << "------before return------" << endl;
return f;
}
…………………………………………
auto testA = f1(b);
有的编译器析构f,有的不析构,求问是什么原因

如何理解最后一行代码

struct A { double x; };
const A* a;

decltype(a->x) y; // type of y is double (declared type)
decltype((a->x)) z = y; // type of z is const double& (lvalue expression)

为什么加了一层括号后类型改变了

类的访问控制相关

C++中类的访问控制

访问说明符

在课上,我们学习了类中的三种访问说明符publicprotectedprivate。这三种访问说明符被用于访问控制。

public

能被本类的成员函数、友元函数、本类的对象、其派生类的成员函数等访问。

protected

能被本类的成员函数、友元函数、其派生类的成员函数访问;

private

能被本类的成员函数、友元函数访问。

可以看到,类的访问控制体现了面向对象编程的封装性。

C++中访问控制的实现

这些访问说明符不会影响类的结构和对象的创建,在编译期间,所有的访问说明信息会消失。因此,可以看成,访问说明符是让编译器检查程序员是否进行了不应该的访问操作,而非严格禁止了这些访问操作。但编译器必须明确知道程序员在进行权限外的访问操作,才会产生访问权限错误。
因此,我们可以解释课上的题目:

#include <iostream>
using namespace std;
class A {
private:
    int a;
    void f(int i=2) { a = i; }
public:
    void f(int i, int j=2) { a = i + j; }
    int get_a() { return a; }
};

int main() {
    A aa;
    aa.f(1);//A中函数重载冲突
    cout << aa.get_a() << endl;
    return 0;
}

进一步了解,编译器是通过限制“名称”的使用来进行访问控制的,无论是对成员变量、成员函数,甚至成员结构、成员类等,都是如此。

#include <iostream>
using namespace std;
class A
{
private:
   struct B
   {
       int c;
       B(int d)
       {
           c = d;
       } 
   };
public:
   B* b;
   A() {}
   ~A() {}
};
int main()
{
   A a1;
//  正确!并没有在类的外部使用私有成员结构的名称。编译器是通过类型推理得知n1的类型的。
   auto *n1 = a1.b;
//  错误!不能在类的外部使用私有成员的名称。
//  Link::Node * n1 = a1.b;
   return 0;
}

所以,这样的操作也能实现。

调用aa.f(1)时,先会发生函数重载冲突问题,该程序无法通过编译。
之后,我们很容易会想到,通过指针等方法可以强制访问private成员。

#include <iostream>
using namespace std;
class  {
public:
    A() {
        a2 = 3;
        a3 = 4;
    }
    int* fun3() {  
        cout << "a3=" << a3 << endl;  
        return &a3;
    }
    int* fun2() {   
        cout << "a2=" << a2 << endl;
        return &a2;
    }
protected:
    int a2;
private:
    int a3;
};
int main() {
    A itema;
    int* mytest;
    //不能直接获取
    //cout << "直接获取private值a3=" << itema.a3 << endl;
    mytest = itema.fun3();
    cout << "通过指针获取private值a3=" << *mytest << endl;
    *mytest = 1;
    cout << "对private值进行修改:";
    itema.fun3();

    mytest = itema.fun2();
    cout << "通过指针获取protected值a2=" << *mytest << endl;
    *mytest = 1;
    cout << "对protected值进行修改:";
    itema.fun2();
    return 0;
}

输出为:

a3=4
通过指针获取private值a3=4
对private值进行修改:a3=1
a2=3
通过指针获取protected值a2=3
对protected值进行修改:a2=1

不过这么做显然不合类的创建者本意,对大多数程序也是有害的。

访问privateprotected成员

getter和setter方法

class A
{
    int value;
public:
    A(int n = 0) : value(n) {}
    int GetValue()
    {
        return value;
    }
    void SetValue(int n)
    {
        value = n;
    }
};

当然,本例中也可以

A a;
*((int *)&a) = 100;

用这种方式修改avalue的值为100。

在类中,被访问说明符限定的变量通常会在内存中顺序存放,而不同的访问说明符限定的变量存放位置可能不同。因此,通过获得对象地址,并直接计算出 privateprotected成员的地址,来进行访问的操作并不总有效。

std::move()的原理

std::move()的原理

你真的了解std::move()吗?(我知道我不

我们都知道std::move()可以把左值转换为右值,然后就可以方便地使用move类的东西了。但是有时候std::move()的行为好像比较奇怪。以第三次作业第二题(引用?复制?)为例:

class Test {
    int *buf;
public:
    Test() {
        buf = new int(0);
        cout << "Test(): this->buf @ " << hex << buf << endl;
    }
    Test(int val) {
        buf = new int(val);
        cout << "Test(int): this->buf @ " << hex << buf << endl;
    }
    ~Test() {
        cout << "~Test(): this->buf @ " << hex << buf << endl;
        if (buf) delete buf;
    }
    Test(const Test& t) : buf(new int(*t.buf)) {
        cout << "Test(const Test&) called. this->buf @ "
            << hex << buf << endl;
    }
    Test(Test&& t) : buf(t.buf) {
        cout << "Test(Test&&) called. this->buf @ "
            << hex << buf << endl;
        t.buf = nullptr;
    }
    Test& operator= (const Test& right) {
        cout << "operator=(const Test&) called. this->buf @ "
            << hex << buf << endl;
        if (this != &right){
            if(buf) delete buf;
            buf = new int(*right.buf);
        }
        return *this;
    }
    Test& operator= (Test&& right) {
        cout << "operator=(Test&&) called. this->buf @ "
            << hex << buf << endl;
        if (this != &right){
            if(buf) delete buf;
            this->buf = right.buf;
            right.buf = nullptr;
        }
        return *this;
    }
    void print(const char *name) const {
        cout << name << ".buf @ " << hex << buf << endl;
    }
};

Test func(const Test& t) {
    Test b = std::move(t); // HERE
    return b;
}

int main() {
    Test a = std::move(func(3));
    return 0;
}

运行一下就可以知道,HERE处实际上调用的是拷贝构造函数。为什么不调用移动构造函数?

其实上一届的同学也讨论过这个问题,采用了实验的方法,参见关于std::move()到底干了什么? · Issue #32 · thu-coai/THUOOP · GitHub。这次我们做些理论层面的工作,来看一看std::move()究竟是怎么实现的。

模板的特化

如果已经学习了这部分知识或不感兴趣可以跳过这一节。

首先简要介绍一下模板的特化(specialization)。顾名思义,特化就是模板针对某些特殊情形的特别处理,也就是对某些特别的类型参数的处理。为什么要特化?因为对于特定的情况,如果你能给出更合适的实现,那么当然就该用你提供的。

模板分为类模板和函数模板,而特化分为全特化和偏特化。全特化就是完全限定模板要用的参数,偏特化就是部分限定参数。例如:

template<typename T1, typename T2>
class Test
{
public:
	Test(T1 i,T2 j):a(i),b(j){ cout<<"模板类"<<endl; }
private:
	T1 a;
	T2 b;
};
 
template<>
class Test<int, char>
{
public:
	Test(int i, char j):a(i),b(j){ cout<<"全特化"<<endl; }
private:
	int a;
	char b;
};
 
template <typename T2>
class Test<int, T2>
{
public:
	Test(int i, T2 j):a(i),b(j){ cout<<"偏特化"<<endl; }
private:
	int a;
	T2 b;
};

第一个是基本的函数模板,有两个类型参数T1T2。第二个和第三个都是其特化。其中第二个是全特化,类型名Test后面的<int, char>限定T1intT2char,所以template后面的<>里是空的(没有variable了)。第三个是偏特化,类型名Test后只把T1限定为int,而T2仍保留,所以template后面的<>里还有T2

特化如何工作呢?看如下例子:

Test<double, double> t1(0.1, 0.2); // 使用基本模板,无特化
Test<int, char> t2(1, 'A'); // 使用全特化版本
Test<int, bool> t3(2, true); // 使用偏特化版本

这是Test类的实例化(instantiation,不要与特化混淆)。其实就是个匹配的过程。编译器先看能不能用特化的版本,如果不能的话就还是用基础的版本。理应如此。

此外,类模板可以全特化、偏特化,而函数模板只能全特化(其他的可以通过重载实现)。这已经不是本文需要的内容了,我们只需要对特化的工作方式有个感性认识就可以继续了。

std::move()的实现

如果使用vscode,按住ctrl单击std::move()即可跳转至其实现。上代码:

// move.h
/**
  *  @brief  Convert a value to an rvalue.
  *  @param  __t  A thing of arbitrary type.
  *  @return The parameter cast to an rvalue-reference to allow moving it.
  */ 
template<typename _Tp>
  constexpr typename std::remove_reference<_Tp>::type&&
  move(_Tp&& __t) noexcept
  { return static_cast<typename std::remove_reference<_Tp>::type&&>(__t); }

……有点复杂?其实没有看上去那么复杂。可以拆出以下几个部分:

  • template<typename _Tp>:这是模板函数的标志。_Tp是类型参数。
  • constexpr typename std::remove_reference<_Tp>::type&&:这是返回值类型。constexpr表示编译期常量,可以暂时忽略。typename指示编译器,接下来的东西是个类型名。真正的返回值类型其实就是std::remove_reference<_Tp>::type,也就是std::remove_reference<_Tp>这个类里面定义的type
  • move(_Tp&& __t):这是函数名及其参数。noexcept表示不会抛出异常。
  • 函数体:只有一行,直接返回。可以看出,是要对参数__t进行一个static_cast,目标类型是刚才提到过的std::remove_reference<_Tp>::type&&

由此可见,std::move()实现的关键在于std::remove_reference<_Tp>

*若不感兴趣可以跳过此部分。*它的源码可以是这样的:

// type_traits
// remove_reference
template<typename _Tp>
  struct remove_reference
  { typedef _Tp   type; };

template<typename _Tp>
  struct remove_reference<_Tp&>
  { typedef _Tp   type; };

template<typename _Tp>
  struct remove_reference<_Tp&&>
  { typedef _Tp   type; };

第一个是基本模板,而第二个和第三个则是它的特化。特化在这里便发挥了像“选择”一样的作用,决定了std::remove_reference的具体实现。如果是普通的_Tp,那么就用第一个;如果是_Tp&,即_Tp的左值引用,就用第二个;同理,_Tp&&就用第三个。然而,无论哪个版本,都用typedef_Tp定义为type。因此,如果传进普通的_Tptype毫无疑问是_Tp;如果传进来的是带引用(&)的_Tp,无论是左值引用还是右值引用,模板推导规则会把引用从_Tp上“剥离”,从而type还是_Tp原本的样子。由此便达到了所谓remove_reference的效果。因为这都是基于模板推导实现的,所以以上均在编译期即可完成。

*跳到这里 ~* 回过头看move便一目了然。std::remove_reference正如其名,只是去掉了引用标签,那么所谓的std::remove_reference<_Tp>::type&&,其实就是两步:先把_Tp上的引用标签都去掉,取出这个“纯粹”的类型,再强行打上一个右值引用的标签,就得到了目标类型。然后由static_cast转换,一步到位。

于是我们也就更加清楚地了解了std::move()的功能:std::move()只是修改成右值引用。具体地说,T&改成T&&T&&还是T&&const T&改成const T&&const T&&还是const T&&。其实源码上面的注释也说了:The parameter cast to an rvalue-reference to allow moving it. 应当注意,std::move()和"move"(包括移动构造、移动赋值)没有直接的关系。如果类并没有相应的移动函数,std::move()也做不了什么。毕竟它只是"to allow moving it"。

至于本文开头的问题,也就很好解释了。std::move()接受一个const T&,出来的就是const T&&。如果构造函数的参数只接受const T&(拷贝)和T&&(移动)型,那么const T&&就只能按万能的const T&来调用拷贝构造,毕竟不能随随便便把const的东西当成非const处理。

杂谈

一些可以进一步研究的东西:

  • 左值/右值:lvalue?rvalue?prvalue?xvalue?尝试区分。
  • 模板元编程(Template Metaprogramming, TMP):如果你对C++模板的这些奇形怪状的花样感兴趣,可以尝试。(一言不合就千百行报错,你值得拥有

参考文献

makefile分步编译执行makedebug的问题

在分步编译的makefile写法中,-DDEBUG应该加在编译生成.o文件的指令中,还是加在由.o文件链接生成可执行文件的指令中,抑或二者都需要加?

第三次作业第三题problem 28的delete问题

我的程序中对rp cp sp 都进行了delete
可是却只能成功释放rp
却不能成功释放sp和cp
将把size强制转化为void*类型后就可以成功释放了…
请问这是为什么啊???

数据库大作业相关问题

  1. 请问一条sql语句中是否可能有关键词大小写混用的情况?变量名是否区分大小写?
  2. 请问where clause语句中有含括号的复合表达式吗?and和or的优先级又是怎样的?
  3. 输入语法错误或不能执行的错误语句是否需要报错?测例中是否有这类语句?

[HW2]T2题意是什么

我有点不太理解这道题怎么样算有错误或风险。把程序跑一遍输出都很正常,如果理解成是这些程序片段有没有问题的话,这些都没有问题。
是不是题目的意思是不考虑Test.h里为了避免错误而加入的优化,只看主程序,并把Test类看做某个一般的类,并调用一般的构造析构函数,然后分析程序是不是会错?
这样的话是不是把程序跑一遍跑出来的结果就不能相信了?

数据库大作业问题

1、请问“SHOW DATABASES 列列出现有的数据库以及其包含的所有表名。”这个操作能不能给个包含数据表的数据库的样例啊[Facepalm]样例都是没有数据表的数据库?
2、请问default和Extra是不是第一阶段不做过多考虑?

数据库大作业 RE

大作业第五、第六个测试点 runtime error ,但复制数据到本地(windows)评测 没有错误,且答案正确

函数内、块内、全局变量创建和析构顺序

函数内、块内、全局变量创建和析构顺序

这次作业的第二题中涉及到了一些对象创建和销毁的顺序、时间相关的问题这里做一个简单的整理。

1、全局变量最先创建最后销毁

2、在同一个块内,先创建的后销毁

3、局部变量(非static变量,非new产生的对象),在一个块内的变量,在块结束后将会被销毁

例如,在如下块内,有a1,a2两个对象,则a1会先创建,而a2后创建,块结束后,a2先销毁,a1后销毁。

{
	Test a1;
	Test a2;
}

4、在带参数的函数中,参数会在进入前创建(先于函数体内对象),会在结束后销毁(晚于函数体内对象)

5、由于函数返回值是对象而在return时也会产生一次对象构造(通常会被编译器优化,但是可以通过开启编译指令-fno-elide-constructors让编译器不优化),这个临时对象会在return语句执行时创建,然后return语句结束后,函数体内的对象被析构,然后这个临时变量被析构。最后才到作为参数的变量被析构。

如下面例子:

Test f(Test a){
    cout << "------ entering f() ------" << endl;
    Test b;
    Test c;
    cout << "------ exiting f() ------" << endl;
    return b;
}

对象的创建和析构顺序如下

a -> b -> c -> t(临时变量) -> ~c -> ~b -> ~t -> ~a

6、对于被当作参数的临时变量来说,会在函数调用结束后被析构

下列示例:

Test f(Test a){
    cout << "------ entering f() ------" << endl;
    Test b;
    Test c;
    cout << "------ exiting f() ------" << endl;
    return b;
}

int main(){
	Test k1;
    Test k2 = f(Test(k1));
    return 0;
}

f(Test(k1));此处会先调用拷贝构造函数创建一个临时变量t1,而函数形参为a,此处t1会先于a构造,晚于a析构

可以看一个较为完整的例子:

注:编译开启-fno-elide-constructors选项

#include <iostream>

using namespace std;

class Test{
private:
    int id;
    int copy;
public:
    static int count;
    Test(){
        this->copy = 0;
        count++;
        this->id = count;
        cout << id <<" is created" << endl;
    }
    Test(const Test & a){
        this->id = a.id;
        this->copy = a.copy + 1;
        cout << id << " copyed version " << copy << " is created" << endl;
    }
    ~Test(){
        cout << id;
        if(this->copy){
            cout << " copyed version " << copy;
        }
        cout <<" is destoryed" << endl;
    }
};

int Test::count = 0;

Test g1;
Test g2;

Test f(Test a){
    cout << "------ entering f() ------" << endl;
    Test b;
    Test c;
    cout << "------ exiting f() ------" << endl;
    return b;
}

int main(){
    cout << "------ entering main() ------" << endl;
    Test k1;
    Test k2 = f(Test(k1));
    cout << "------ exiting main() ------" << endl;
    return 0;
}

执行结果如下:

1 is created
2 is created
------ entering main() ------
3 is created
3 copyed version 1 is created
3 copyed version 2 is created
------ entering f() ------
4 is created
5 is created
------ exiting f() ------
4 copyed version 1 is created
5 is destoryed
4 is destoryed
4 copyed version 2 is created
4 copyed version 1 is destoryed
3 copyed version 2 is destoryed
3 copyed version 1 is destoryed
------ exiting main() ------
4 copyed version 2 is destoryed
3 is destoryed
2 is destoryed
1 is destoryed

可以较为完整的判断上述每个对象被创建和析构的时机

数据库大作业问题

所给样例中有
Database
information_schema
Oop
edc
ijn
mysql
oOp
performance_schema
qaz
那么information_schema和performance_schema有什么区别,是performance_schema为当前使用的数据库而information_schema不是吗

VSCode 配置相关

Code Runner 插件配置修改

Code Runner 插件默认情况下的编译选项没有设置 C++ 标准,需要自己添加。

各系统的插件目录为:

  1. Windows%USERPROFILE%\.vscode\extensions
  2. Linux, macOS~/.vscode/extensions

其中 %USERPROFILE% 是 Windows 系统对应用户的文件夹,一般为 C:\Users\用户名

在插件目录下 formulahendry.code-runner-0.9.17 的文件夹中(后面版本号可能有所不同)打开 package.json 文件,在第 $125$ 行(不同版本可能有所区别)中有如下语句:

"cpp": "cd $dir && g++ $fileName -o $fileNameWithoutExt && $dir$fileNameWithoutExt",

将其修改为:

"cpp": "cd $dir && g++ $fileName -o $fileNameWithoutExt -std=c++14 && $dir$fileNameWithoutExt",

其中 -std=c++14 可以根据自己需求改为其他 C++ 标准。

然后重启 VSCode 或者根据扩展程序已被修改的提示点击重新加载窗口,即可完成和应用配置修改。

Windows 系统下调试配置

launch.json 配置文件中需要添加调试前的 Task

"preLaunchTask": "build"

后面的 build 对应为 tasks.json 中的编译任务的 label 属性。

tasks.json 中如需更改调试时编译选项,比如 C++ 标准或者栈空间等等,可以修改 args 属性:

"args": [
    "-g",
    "${file}",
    "-o",
    "${fileDirname}\\${fileBasenameNoExtension}.exe",
    "-std=c++14",
    "-Wl,--stack=1024000000"
]

更多的选项,只需要在对应位置增加一行插入即可。

对于无法打开编译错误或警告中的文件时,如果发现其目录中有一段出现了两遍,可以选择修改 tasks.json 文件中 problemMatcher 属性:

"problemMatcher": {
    "owner": "cpp",
    "fileLocation": [
        "relative",
        "\\"
    ],
    "pattern": {
        "regexp": "^(.*):(\\d+):(\\d+):\\s+(warning|error):\\s+(.*)$",
        "file": 1,
        "line": 2,
        "column": 3,
        "severity": 4,
        "message": 5
    }
}

或将下面序号全部加 $1$,上面配置按原有配置。

如果调试过程中出现了无法输入的现象,可以修改 launch.json 文件中 externalConsole 的属性:

"externalConsole": true

如果调试过程中输入前总是需要先选择继续,可以修改 launch.json 文件中 stopAtEntry 的属性:

"stopAtEntry": false

注意上述修改后,各属性之间需要以 , 分割,也就是说,除非是最后一项属性,末尾都需要注意添加 ,

L12笔记

L12笔记

ppt中讲的比较清楚或者比较容易的内容就不再作为重点了,主要挑一些我觉得比较难理解的来记。

string类

  • string s2(s0, 8, 3); //截取,index从8开始,长度为3。这实际上跟substr实现了一样的功能,是可以记住的两种截取方式。用法上,除了参数列表多了个s0, 跟substr也没有什么区别。

  • string s4(10, 'x'); //复制字符:xxxxxxxxxx

  • str.c_str() //注意返回值为常量字符指针(const char*),不能修改。

  • 清空:clear()。

  • 查询长度:size()或者length()

  • 尾部添加有两种:push_back()和append() ,当然也可以直接+=或者+。

  • 比较:字典序。C++的字典序:比较第一个不一样的字符。跟长度不一定有关系。比如aaaa<av。

  • 一个试水案例:

    #include <iostream>
    using namespace std;
    
    int main(){
        string str; 
        cout<<str.empty()<<endl;//1
        cin>>str;
        cout<<str.empty()<<endl;//0
        for(char c: str){//跟vector类似的用法
            cout<<c;
        }
        cout<<endl;
        str.clear();
        cout<<str.empty()<<endl;//1
    }
  • p10的解释:

    int b = stoi("50 cats", &sz)

    自动找到string中可以转成数字的那些东西

    (其实是非数字的字符前面的那些数字字符)

    开头是数字字符时才能直接用这个

    S表示string;

    i,d,f表示目标的类型

  • 输入方式

    直接cin:读取可见字符 到空格。

  • Getline(cin, str); :读取一行,以换行符判断。

  • Getline(cin, str, '#'); :读到指定分隔符为止,这个分隔符可以任选。

  • ***注意,字符串里面可以有换行符(作为元素)

  • 补充一些输入相关:

  • #include <iostream>
    #include <string>
    #include <cctype>
    #include <fstream>
    using namespace std;
    int main(){
        // string str;
        // str=cin.get();
        // cout<<str<<endl;//1,只能拿到一位。
        char a[15];
        cin.get(a, 10);//2 拿到10位
    }
  • IOstream

  • Istream, ostream都是类,cin, cout是类的对象。

  • 头文件中只有cin和cout对象

  • ostream:重载了针对基础类型(内嵌类型)的输出流运算符(<<), 接受不同类型的数据,再调用系统函数进行输出

  • 每次输出运算都要返回对象的引用

  • 格式化输出:include 。

  • cout << fixed << 2018.0 << " " << 0.0001 << endl;
    				//浮点数 -> 2018.000000 0.000100 6位
    cout << scientific << 2018.0 << " " << 0.0001 << endl;
    				//科学计数法 -> 2.018000e+03 1.000000e-04
    cout << defaultfloat;  //还原默认输出格式
    cout << setprecision(2) << 3.1415926 << endl;
    				//输出精度设置为2 -> 3.1
    cout << oct << 12 << " " << hex << 12 << endl; 
    				//八进制输出 -> 14  十六进制输出 -> c
    cout << dec;	//还原十进制
    cout << setw(3) << setfill('*') << 5 << endl;
    				//设置对齐长度为3,对齐长度所需填充的字符为* -> **5
    
  • 上面是课件内容。fixed、scientific、defaultfloat、oct、hex、dec是流操作算子。补充一点较常用的steprecision:

  •     -- precision是成员函数,其调用方式为:cout.precition(5);
        -- setprecision是流操作算子,其调用方式为:cout << setprecision(5);   // 可以连续输出 即是连续起作用,设置一个之后,后面其他浮点数都按照这个格式输出
        它们的功能相同。
        指定输出浮点数的有效位数(非定点方式输出时)
        指定输出浮点数的小数点后的有效位数(定点方式输出时)
        定点方式:小数点必须出现在个位数后面
        非定点方式:小数点不在个位数后面 例如科学计数法
    
  • 自定义流操纵算子,如:

  •     eg:
            ostream& tab(ostream& output)
            {
                return output << '\t';
            }
            // 函数可以交给cout用于输出,称作流操纵算子
            // 为什么可以把函数名字写在cout输入语句?
            // 因为iostream里对 << 进行了重载(成员函数)
            // ostream& operator<< (ostream& (*p)(ostream&));   // 函数指针
            // 该函数内部会调用p所指向的函数,且*this作为参数  hex, dec, oct都是函数
            cout << "aa" << tab << "bb" << endl;
  • endl:是一个函数,等同于输出'\n',再清空缓冲区 os.flush()。

  • 重载流运算符总是返回引用,禁止复制,只能移动。全局只有cout一个对象(减少复制开销,符合oop**)。

    文件流

    ifstreamistream的子类,功能是从文件中读入数据。(不能写出)

    打开文件的三种方式:

    ifstream ifs("input.txt");
    
    ifstream ifs("binary.bin", ifstream::binary);//以二进制形式打开文件
    
    ifstream ifs;
    ifs.open("file");//do something
    ifs.close();
    

    p26读入示例的解释:

    • ws:用于在输入时跳过开头的空白符 。

    • EOF:EOF是end of file的缩写,表示”文字流”(stream)的结尾。这里的”文字流”,可以是文件(file),也可以是标准输入(stdin)。
      EOF不是特殊字符,而是一个定义在头文件stdio.h的常量,一般等于-1。#define EOF (-1)
      除了表示文件结尾,EOF还可以表示标准输入的结尾。但是,标准输入与文件不一样,无法事先知道输入的长度,必须手动输入一个字符,表示到达EOF
      (转自https://blog.csdn.net/sinat_36053757/article/details/66546610)

字符串输入输出流

stringstream是iostream子类,后者又是i和ostream的子类。所以stringstream继承了双方的接口。

  • 它在对象内部维护了一个buffer,使用流输出函数可以将数据写入buffer,使用流输入函数可以从buffer中读出数据。

  • 程序内部的字符串操作。

  • int main() {
    stringstream ss;
    ss << "10";
    ss << "0 200";
    
    int a, b;
    ss >> a >> b;		//a=100 b=200
    return 0;
    }
    //这里主要是体现了buffer在结合上<<, >>之后的用途
    //先把“10”和“0 200”用<<放进来,这样buffer里面是这样:100 200
    //然后再用>>把buffer里面的内容放进去,由于指定了读法:两个int,所以就和先后输入100和200一样了。
    //可以连接字符串,可以将字符串转换为其他类型的数据
    
  • p33解释:从tail开始读。head** 是等待读入的最后位置**(还没有读过的在开头;已经读过的在尾巴)head到tail表示当前等待读入的缓冲区,所以head在后面。

  • 函数模板:convert<目标类型>( ) 可以代替std::to_string 和atoi, stoi.

  • //ppt p35
    template<class outtype, class intype>
    outtype convert(intype val)
    {
    	static stringstream ss;
    						//使用静态变量避免重复初始化
    	ss.str(""); 	//清空缓冲区
    	ss.clear(); 	//清空状态位(不是清空内容)
        //https://blog.csdn.net/clearriver/article/details/4366872
    	ss << val;	//把intype类的输入量放进缓冲区
    	outtype res;	//先定义一个接收器
    	ss >> res;	//把缓冲区里的东西放进接收器。这个过程跟outtype的具体类型有关。
    	return res;
    }

字符串处理和正则表达式

  • 正则表达式的定义:由字母和符号组成的特殊文本,搜索文本时定义的一种规则

  • image-20220522124721269

  • 中括号:内部的东西是允许出现的

    大括号:长度范围要求

  • 三种模式:匹配,搜索,替换。

  • 指定某个字符,可以在字符串里找到所有的该字符。例如the,使用the进行搜索,可以找到句中所有的"the"。

  • 用法和格式:

    • [a-z] 匹配所有单个小写字母。

    • **[0-9]**匹配所有单个数字。

    • **[a-z] [0-9]**匹配所有字母数字的组合。

    • **[ab] c **匹配ac或者bc。例如:•[Tt]he: The car parked in the garage.

      • 推广来说,前面特殊(如[], . ,等等有特殊意义的表示)+后面一般(就是单纯的几个字符,没有任何形式的括号等)这个模式经常会用。例如[Tt]ree, .ar ......
      • 当然也可以是前面一般后面特殊。ge\ .
    • **\d **等于[0-9].

    • \w 等于**[a-zA-Z0-9_]**,大小写字母,数字,下滑线的组合。

    • . 匹配换行外的全部字符。

    • \ . 匹配句号。

    • [ ^ a-z] 匹配所有非小写字母的单个字符.

    • \D 等价[ ^ 0-9 ], 匹配所有单个非数字.

    • \s 匹配所有空白字符,如\t,\n

    • \S 匹配所有非空白字符

    • \W 匹配非字母、数字、下划线。

    • ^后面紧跟着的是开头,^\t只能匹配到以制表符开头的内容。

    • ? 出现0次或1次

    • +至少连续出现1次及以上

    • *至少连续出现0次及以上

    • 重复模式:

      • **x{n,m}**代表前面内容出现次数重复n~m次。m若为空则表示没有上限。按照个人尝试的结果,n不可以为空。

      • +前一个字符至少连续出现1次及以上。比如a\w+: The car parked in the garage

      • 扩展:[a-z] {5, 12}长度为5~12的英文字母组合; .{5}所有长度为5的。

  • 怎么用?先要引入.

  • 创建正则表达式:regex re("^[1-9][0-9]{10}$")

    这就是 第一位非0 其他是0-9

    创造方法:括号里面一个字符串。字符串是目标表达式

  • 如果需要创建正则表达式"\d+",应该写成regex re("\ \d+")。因为\是转义字符。

  • 使用原生字符串可以保留字面值,取消转义: R"(str)".

    • 括号里面是字符串,外面是个引号。
    • "\d+" = R"(\d+)" = \d+
    • 可以换行。
    • R这个符号不光可以用在regex中。实际上它本身是对string做操作的。仅用在string上当然也可以。
  • 匹配:•regex_match(s, re):询问字符串s是否能完全匹配正则表达式re。注意此处s自然是string类,而re是regex类的。

  • //自行试水
    #include <iostream>
    #include <string>
    #include <regex>
    using namespace std;
    
    int main() {
    string s("student");
    regex e("stu.*");
    //.匹配换行符以外任意符号
    //*任意长度
    if(regex_match(s,e))
    	cout << "matched" << endl;
    //another regex
    string str;
    cin>>str;
    regex new_e(R"(^[a-z0-9_]{3,15}$)");
    if(regex_match(str,new_e))
    	cout << "matched" << endl;
    }
    //注意到单纯以匹配为目的时,smatch sm这句话不是必要的。
  • 捕获和分组:有时我们需要知道正则式的某一部分匹配了字符串的哪一部分。

    • 使用()进行标识,每个标识的内容被称作分组。

    • regex_match(s, result, re):询问字符串s是否能完全匹配正则表达式re,并将捕获结果储存到result中。re是regex类的对象,result是smatch类型的对象。

    • 如果需要括号,又不想捕获该分组,可以使用(?:pattern)

      ​ 用(?:sub)(.*)匹配subject:0号为subject,1号为ject

      ​ 这样sub就不捕获了

    • 分组按顺序编号:

      •0号永远是匹配的字符串本身

      •(a)(pple): 0号为apple,1号为a,2号为pple

      •用(sub)(.*)匹配subject:0号为subject,1号为sub,2号为ject

      就是说,括号里面的匹配都是1+号匹配。

    • //ppt示例
      int main () {
      string s("version10");
      regex e(R"(version(\d+))"); smatch sm;
      if(regex_match(s,sm,e)) {
      	cout << sm.size() << " matches\n";
      	cout << "the matches were:" << endl;
      for (unsigned i=0; i<sm.size(); ++i) {
      	cout << sm[i] << endl;
      }
      }
      return 0;
      }
      //结果:2 matches
      //the matches were:
      //version10
      //10
      
      //一些解释
      //R"(version(\d+))"==" version(\d+)"其实只有一个括号,那个R自带的括号可以认为是假括号。
      //这里有两个匹配,0号匹配匹配整个字符串,匹配结果是version10;1号匹配是那个内部括号,匹配结果是10
      //regex_match(s,sm,e)
      //将匹配结果都放到了sm里面
      
  • 搜索

    • regex_search(s, result, re):搜索字符串s中能够匹配正则表达式re的第一个子串,并将结果存储在result中。result照例还是smatch对象。

    • 分组同样会被捕获。

    • //ppt示例,带有自己的批注
      #include <iostream>
      #include <string>
      #include <regex>
      using namespace std;
      int main() {
      string s("this subject has a submarine");
      regex e(R"((sub)([\S]*))");
      //正则表达式是:(sub)([\S]*)
      smatch sm;
      //每次搜索时当仅保存第一个匹配到的子串
      while(regex_search(s,sm,e)){
      	for (unsigned i=0; i<sm.size(); ++i)
      		cout << "[" << sm[i] << "] ";
      	cout << endl;
      	s = sm.suffix().str();
          //Suffix是把sm后面的子串传到s里面(也就是,不再有前面已搜索过的子串。这样可以不重复 
      }
      return 0;
      }
      
  • 替换

    • regex_replace(s, re, s1):替换字符串s中所有匹配正则表达式re的子串,并替换成s1(字符串类型)。

    • regex_replace返回值即为替换后的字符串。不过要注意一开始的那个字符串s并没有变

    • #include <iostream>
      #include <string>
      #include <regex>
      using namespace std;
      //ppt样例
      int main() {
      string s("this subject has a submarine");
      regex e(R"(sub[\S]*)");
      //表达式为sub[\S]*
      //regex_replace返回值即为替换后的字符串 
      cout << regex_replace(s,e,"SUB") << "\n";
      cout<<s<<endl;
      //this SUB has a SUB
      //this subject has a submarine
      //s没有变。
      return 0;
      }
    • #include <iostream>
      #include <string>
      #include <regex>
      using namespace std;
      //ppt样例
      int main() {
      string s("this subject has a submarine");
      regex e(R"((sub)([\S]*))");
      //表达式:(sub)([\S]*)
      //regex_replace返回值即为替换后的字符串 
      cout << regex_replace(s,e,"SUBJECT") << endl;
      //$&表示所有匹配成功的部分,[$&]表示将其用[]括起来
      cout << regex_replace(s,e,"[$&]") << endl;
      //$i输出e中第i个括号匹配到的值
      cout << regex_replace(s,e,"$1") << endl;
      cout << regex_replace(s,e,"$2") << endl;
      cout << regex_replace(s,e,"$1 and [$2]") << endl;
      return 0;
      }
      //this SUBJECT has a SUBJECT
      //this [subject] has a [submarine]
      //this sub has a sub
      //this ject has a marine
      //this sub and [ject] has a sub and [marine]
    • 可以看出,捕获的分组还是存贮了的。

      • $& 代表re匹配的所有子串

        $1, $2 代表re匹配的第1/2个分组

  • 学生信息例题的一些解释

    • 注意在正则表达式中使用|符合来表达“或者”

      第一个表达式:(My name is |I am )(\w+).

      0——整个;1——My name is |I am ;2——\w+

      第二个表达式:(\d{4}) [.-] (\d{1,2}) [.-] (\d{1,2})

      0——整个;1——\d{4};2——\d{1,2};3——同2

      第三个表达式:[1-9]\d{10}

      0——整个

      第四个表达式也没有括号,和3一样。

计算图大作业相关问题

在编写placeholder等类的时候,题目是不是要求其中存储数据的成员必须是float而不是double?

函数调用运算符重载与仿函数

运算符重载拓展:operator()与仿函数

我们已经在课上学习了C++运算符重载。然而乍看上去,有一些重载未免有些奇怪:我们到底为什么需要重载它们呢?重载new delete,或者[]()又有什么用呢?这里就operator()所拓展出的功能——仿函数(functor)做一点简单的介绍。

从重载operator()到仿函数

首先复习一下operator()的重载。一个常见的operator()重载可能长成这样:

class Add {
	int operator()(int a, int b)
    {
        return a + b;
    }
};

于是一个Add类的对象可以这样使用:

#include <iostream>
using namespace std;
int main()
{
    Add t;
    cout << t(2,3) << endl; // 输出2+3的运算结果
}

经过重载,Test类的对象表现得就像个函数,可以像函数一样被调用,也做着函数该做的事。可以看出,operator()为我们提供了一种方法,使我们可以利用对象模拟函数的行为。自然,这样的对象就被称为仿函数(functor)。

为什么要用仿函数?

当我们有了仿函数的概念,也就自然产生了这样的问题。毕竟,有好好的函数不用,何必要大费周章去写一个类和重载呢?不妨设想这样一个例子。假如有一个int数组,我们想要统计其中能被5整除的数据的个数。我们可以写一个函数来完成:

int count_if_mod_5(int* arr, int size)
{
    int count = 0;
    for(int i = 0; i < size; ++i)
    {
        if(arr[i] % 5 == 0)
            count++;
    }
    return count;
}

写成函数的好处是把功能模块化,方便复用。比如可以在另外一个相对更主要的函数里调用这个count_if_mod_5函数:

void do_something(int* arr, int size)
{
    // do something
    int cnt = count_if_mod_5(arr, size);
    // do something
}

更一般地,为了更方便地调用不同的模块,我们可以用函数指针来调用其他函数:

void do_something(int* arr, int size, int (*fp)(int*, int))
{
    // do something
    int whatever = fp(arr, size);
    // do something
}

函数指针给予了我们更大的灵活性,使得这个函数不局限于对整除5的数据计数——只要满足函数指针相应的接口就行。但是这足够了吗?回到count_if_mod_5。假如我们的需求发生了改变,现在改成要统计整除10的数据个数了。怎么办?最直接的办法当然是再写一个count_if_mod_10。但是追求lazy简洁和优雅的程序员怎么能满足于此呢?显然k应该被处理为变量。使用全局变量?虽然可以实现功能,但是可能产生的各种bug使之并不是最佳选择。k应该作为函数的一个参数会更好。于是可能想这样写:

int count_if_mod_k(int* arr, int size, int k)
{
    int count = 0;
    for(int i = 0; i < size; ++i)
    {
        if(arr[i] % k == 0)
            count++;
    }
    return count;
}

但是这样会有一个问题:原来采用指针调用函数的do_something不再和这个count_if_mod_k适配了,因为count_if_mod_k有三个参数,而do_something的函数指针只能有两个参数(数组及其大小)。难道要为此去修改do_something吗?首先,这一部分代码你未必能修改(比如在一个工程里,那不是你负责的部分,你只是负责搬一搬像count_if_mod_k这样的砖,摸鱼划水);其次,即便能修改,do_something并不只调用count_if_mod_k这一个函数,如果修改了do_something里函数指针的相应接口,可能导致采取原接口的函数也不能使用了,正是难以两全的局面。在这时,仿函数便可以来救场了。我们使用仿函数改写do_somethingcount_if_mod_k

class CountIfModK {
private:
    int mod;
public:
    CoundIfModK(int k) // 用k初始化除数
  		: mod(k) {}
    int operator()(int* arr, int size) // 完成相同的功能
    {
        for(int i = 0; i < size; ++i)
        {
            if(arr[i] % k == 0)
                count++;
        }
        return count;
    }
}

void do_something(int* arr, int size, CountIfModK mod_functor)
{
    // do something
    int cnt = mod_functor(arr, size);
    // do something
}

do_something看来,无论mod_functor具体的除数是什么,都具有相同的类型,也就可以以完全相同的方式调用。就mod_functor的角度来看,除数被妥善地保存、封装、隐藏起来,使得相同的签名(即CountIfModK)也可以产生“不同”的函数。这也正是仿函数的特点:可以在同一签名下拥有不同的实际功能,并且可以拥有自己的状态(甚至可以记录状态)。

你或许会问:这个do_something似乎并不能像函数指针一样调用不同的处理函数?事实上,采用我们在后续课程将会学习的泛型编程的方法,就可以做到了:

template <typename T> // template,即模板,定义一个“模板函数”
void do_something(int* arr, int size, T general_functor) // 需要一个类型为T的仿函数
{
    // do something
    int whatever = general_functor(arr, size); // 要保证重载了相应接口的operator()
    // do something
}

这样,不论是对数组的内容进行修改、判断、统计,都可以用仿函数来实现。它们提供了一致的接口,而它们各自所需要的特别的参数则作为成员数据封装起来,do_something就越来越flexible了。

关于仿函数的杂谈

  • 仿函数还可以这样调用:

    int cnt = CountIfModK(5)(arr, size);

    结合仿函数对象的构造方法即可理解。看上去仿佛又多了一个参数。

  • 仿函数实际上是一个类及其实例化而得到的对象,因此除了拥有数据以外,甚至还可以拥有组合、继承等关系。可见相较于模板函数(即上文中的template),仿函数有其自身的独特之处。

  • (将要学习的)C++的STL中大量使用了仿函数,并且也内置了很多的仿函数,定义在<functional>头文件中。<functional>中还有std::function,将C++中的几种具有函数形式及功能的“可调用对象”抽象成同一种事物,可谓九九归一。

  • 逐渐深入到本人也一知半解的东西了,放弃治疗

参考资料

  1. C++ 仿函数_恋喵大鲤鱼的博客-CSDN博客_c++仿函数
  2. 仿函数(functors)_JUAN425的博客-CSDN博客_仿函数
  3. Standard library header - cppreference.com (codingdict.com)

重写覆盖中返回值相关

函数重写覆盖的返回值相关

重写覆盖的定义

课上,老师给出了派生类函数重写覆盖基类函数的定义:

  • 派生类重新定义基类中的虚函数,函数名必须相同,函数参数必须相同,返回值一般情况应相同。
  • 派生类的虚函数表中原基类的虚函数指针会被派生类中重新定义的虚函数指针覆盖掉。

重写覆盖的一般情况

下面给出重写覆盖的一般实现:

#include <iostream>

class Base
{
public:
    virtual int f()
    {
        return 0;
    }
};

class Derive: public Base
{
public:
    int f()
    {
        return 1;
    }
};

int main()
{
    Derive d;
    std::cout << ((Base&)d).f();//1
    return 0;
}

而在一般情况下,返回值不同时,编译失败:

class Base
{
public:
    virtual int f()
    {
        return 0;
    }
};

class Derive: public Base
{
public:
    void f()//编译失败
    {
        return;
    }
};

重写覆盖中返回值不同的情况

在作业的选择题中,有如下问题,部分同学有所疑问:

#include <iostream>
using namespace std;

class Base {
    int* x;
public:
    Base(){x = new int[10];}
    Base(const Base& other){fn();} // (1)
    virtual void fn(){}
    virtual void g1(){}
    virtual Base& g2(){}
    ~Base(){delete [] x;}
};

class Derive: public Base {
    int* y;
public:
    Derive(){y = new int[10];}
    Derive(const Derive& other):Base(other){fn();}
    void fn(){}
    void g1() const {}
    Derive& g2(){}
    ~Derive(){delete [] y;}
};

void fn(){
    Base* b = new Derive();
    delete b; // (*)
}

int main(){
    fn();
    return 0;
}

C. Derive::g2()的返回值为Derive&,Base::g2()的返回值为Base&。Derive::g2()仍能重写覆盖Base::g2(),不会出现编译错误。

许多同学不太明白这种重写覆盖的实现方式,以及为何返回值不同。下面做简要说明。

在题目中的这种特殊情况下,函数返回值可以不同,具体而言,
可以是基类函数返回基类引用,派生类函数返回派生类引用:

class Base
{
public:
    virtual Base& f() {return *this;}
};

class Derive: public Base
{
public:
    Derive& f() {return *this;}
};

也可以是基类函数返回基类指针,派生类函数返回派生类指针:

class Base
{
public:
    virtual Base* f() {return this;}
};

class Derive: public Base
{
public:
    Derive* f() {return this;}
};

当然,返回的并不一定是当前类,例如:

class Baser {} b;

class Deriver: public Baser {} d;

class Base
{
public:
    virtual Baser& f() {return b;}
};

class Derive: public Base
{
public:
    Deriver& f() {return d;}
};

也是可以的。
那么派生类成员函数返回的类型会被向上转换吗?答案是肯定的。这里给出例子:

#include <iostream>

class Base
{
public:
    virtual Base& f() {return *this;}
    virtual void test2() {return;}
};

class Derive: public Base
{
public:
    void test() {std::cout << "is Derive&\n";}
    Derive& f() {return *this;}
    void test2()
    {
        f().test();
    }
};

int main()
{
    Derive d;
    d.test();
    d.f().test();
    d.test2();
    
    Base& b = d;
    b.test();//tes错误,t()不是Base的成员,无法调用
    b.f().test();//错误,b.f()的类型是Base&,同样无法调用test()
    b.test2();//调用成功,输出"is Derive&\n",说明Derive::f()的返回值确实是Derive&,但在外部调用时被转换为Base&
    return 0;
}

gdb相关

info break相关

关于info b中的信息:

disp一列中keepdisdel, 对应断点生效后是 保留 还是 disable 还是 delete

enb一列中yn,对应该断点现在disable还是enable

例如: disable 1 会让断点失效(disp=dis, enb=n),之后enable once 1 会让(disp=dis, enb=y), 然后该断点生效一次之后(disp=dis, enb=n)

又例如:tbreak main后(disp=del, enb=y)

break if

1	int x;
2	for (int i = 1; i <= 10; ++i)
3	for (int j = 1; j <= 10; ++j) {
4		x = i * j;
5	}

可用b 4 if i == 1 && j == 2 , b 4 if (i==1||j==2)等等
if 后面要加空格

watch的使用方法

这好像不同平台使用不太一样[捂脸], 仅供参考

watch是一个运行过程中使用的命令

使用方式为 break -> run -> watch -> continue

1、对于局部变量,只有程序运行到该作用域后才能watch它,例如:

void f() {
	int x;
	x = 1;
}
int main(){
	while(1){
		f();
	}
	return 0;
}

想要watch函数f中的x,就需要先执行程序跑到f里,否则会被告知no simbol "x" in current context
这段代码里,f执行了很多次,而每当函数f执行结束, 你关于x的那个watch point就会被 自动delete掉

2、而对于一个全局变量,gdb是允许你在未运行的时候将其加入watch的,且程序结束并不会将其自动delete掉

但是加入watch后,直接run的话,它并不会如你期望的成为一个断点,使用时还是要 b r c

经测试对于new出来的变量,如果在未运行时加入watch,则会导致第一次看到old value为unreadable,而如果先b r,再watch,则old value显示正常。 但是 , 当你用r重新运行程序的时候,watch第一次看到的old value又会变为unreadable

对于非new出来的变量貌似不会出现unreadable的问题

upd: 见下方评论
评论中,后面类对象的那一句是指,比如你的A类的对象在初始化时把某成员设成了2,在watch时到变化时oldvalue仍会是0,即一个与实际成员值不一致的一个情况

3、watch检测到后,会在那一行的下一行停下来, 就好像你用disp一直看着某个变量,然后按了一下n,发现那个变量变了,你才停下了。 这里下一行指的是代码执行时的下一行而不是代码中的下一行。因为比如在循环中,最后一语句的下一行可能在它上面

lldb 与 gdb 命令对照表

link

继承关系中的类型转换相关

继承关系中的类型转换相关

派生类的向上类型转换

在课上,老师给出的课堂习题中有一个选项:

通过对象的向上类型转换,我们可以访问到因为private继承而访问不到的基类成员变量。

这一说法是错误的,一种易于理解的说法是:private继承不希望用户通过派生类对象以任何方式访问到基类的成员。
那么我们会提出疑问,各种继承方式下的向上类型转换的机制是怎样的,可以参考以下例程:

class A {};
class B: A {}; // 不阻止任何B类的用户向A进行类型转换
class C: protected A {}; // 阻止C类的对象用户向A进行类型转换
class D: private A {}; // 阻止D类的一切用户向A进行类型转换
class E: B { void test { static_cast<A *>(this); } }; // B类的继承类用户可以向A进行类型转换
class F: C { void test { static_cast<A *>(this); } }; // C类的继承类用户可以向A进行类型转换
class E: D { void test { static_cast<A *>(this); } }; // Error!D类的继承类用户不可以向A进行类型转换
int main
{
    static_cast<A *>(new B); // B类的实例用户可以向A进行类型转换
    static_cast<A *>(new C); // Error!C类的对象用户不可以向A进行类型转换
    static_cast<A *>(new D); // Error!D类的对象用户不可以向A进行类型转换
}

因此,我们可以得出结论:

  • public继承:不阻止类的任何用户进行向上类型转换
  • protected继承:阻止类的对象用户进行向上类型转换
  • private继承:阻止类的一切用户进行向上类型转换

通过类的不同用户访问权限的角度,我们可以理解不同继承方式下,派生类的向上类型转换不同的原因。

基类的向下类型转换

在课上我们学习到,基类对象不能直接进行向下类型转换。而指针和引用是可以进行向下类型转换的。下面介绍一种方法——dynamic_cast
具体语法:

dynamic_cast< type* >(e)

type必须是一个类类型且e必须是一个有效的指针,或

dynamic_cast< type& >(e)

type必须是一个类类型且e必须是一个左值,或

dynamic_cast< type&& >(e)

type必须是一个类类型且e必须是一个右值。

e的类型必须符合以下三个条件中的任何一个:
1、e的类型是目标类型type的公有派生类
2、e的类型是目标type的基类
3、e的类型就是目标type的类型

否则,dynamic_cast<type*>(e)为0,而dynamic_cast<type&>(e)dynamic_cast<type&&>(e)会抛出std::bad_cast异常。
这种方式比起强制类型转换的优点是会检查转换类型是否合法,并有良定义的处理,但会耗费大量时间。

Linux系统

作业里的makefile要求Linux下编译通过,未来各种作业是不是最好在linux环境下去完成,而不是windows?现在装双系统还来得及吗QAQ

数据库大作业问题

SHOW columns from tablenName 此条目中显示columns是按照什么顺序?
在create语句中的添加顺序?还是表头名字的字典顺序?

计算图大作业关于内存泄漏的问题

请问如果在智能指针make_shared之后在Node节点中出现了vector<Node*>,这个指针组会指向其他的make出来的Node节点,那么在最后函数周期结束以后会发生内存泄漏吗orz

一个正则表达式匹配问题

正则表达式可以做不定个数的匹配捕获吗?例如,对于字符串:
func(x1,123,456);
若现在括号内参数个数不确定,但是我希望能够捕获若干个(,.)串,也就是实现类似于(,.)的效果。
不过直接写(,.
)*发现并不能实现这一目标……
代码:

string s("func(x1,23,456);");
regex e(R"((\w+)\((.*)(,.*)*\);)");
smatch sm;
regex_match(s,sm,e);
for(unsigned i=0;i<sm.size();i++) cout << i << ':' << sm[i] << '\n';

实际运行结果:

0:func(x1,23,456);
1:func
2:x1,23,456
3:

(另外为什么捕获了一个空串能否解释一下呢)
恳请解答,谢谢!

计算图大作业相关问题

想请问一下助教:

  1. 定义新节点时, 等号右边是否可能有包含多步计算的表达式?
    (例如:a = b + c * d)

  2. 一个节点创建后, 有可能创建同名节点吗?
    (比如以下状况)
    a = b + c
    d = a + b
    a = b + b
    如果有的话, 这样d依赖的a, 会进而改变为之后创建的节点吗?

3.节点的名称会包含哪些字符呢?
会包含特殊符号 例如 空格 + - * / 等嘛?

  1. 赋值表达式的两边一定会由恰好一个空格隔开吗?
    表达式运算过程中 操作数会出现字面值常量吗? (如 b = a + 1)

5.给浮点数时候会出现科学计数法的表达吗?

运算符重载问题

运算符重载问题

重载类的运算符的时候的方法有两种,可以在类内重载,也可以在类外重载。(部分例外)

类内重载(成员函数)

#include <iostream>
using namespace std;

class A {
public:
    A(int t = 0) {
        num = t;
    }
    A operator-(A &t2) {
        this->num = this->num - t2.num;
        return *this;
    }
    int getNum() {
        return this->num;
    }
private:
    int num;
};

int main(){
    A a1(5);
    A a2(2);
    A a3 = a1 - a2;
    cout << a3.getNum() << endl;
    return 0;
}

类外重载(非成员函数)

#include <iostream>
using namespace std;

class A {
public:
    A(int t = 0) {
        num = t;
    }

    int getNum() {
        return this->num;
    }
    friend A operator-(A& t1, A &t2);
private:
    int num;
};

A operator-(A& t1, A &t2) {
    A ret;
    ret.num = t1.num - t2.num;
    return ret;
}

int main(){
    A a1(5);
    A a2(2);
    A a3 = a1 - a2;
    cout << a3.getNum() << endl;
    return 0;
}

通常类外重载需要声明为该类的友元(运算操作通常会访问到类的私有成员)

c++中有的运算符只能通过成员函数的方式重载,例如:operator=

这是由于编译器会在类内默认合成一个operator=函数,而如果我们在类外再重载一个,那么会造成编译器编译时不知道选择调用哪一个。

但是重载流运算符operator<<, operator>>时,我们通常将其重载为非成员函数。

我们通常是希望把类作为某种变量输入或者输出,并且,以cout为例

<< 操作符是一个二元操作符,它只接受两个参数

如果我们在类内重载该操作符,由于类内成员函数重载operator<<时自动将*this对象作为第一个参数,我们给的流对象作为第二个参数,则调用时会出现a << cout;这样与预期不符的调用效果。

例如:

#include <iostream>
using namespace std;

class A
{
public:
    A(int t = 0) {
        num = t;
    }

    int getNum() {
        return this->num;
    }

    ostream & operator<<(ostream & a){
        a << this->num;
        return a;
    }
private:
    int num;
};

int main(){
    A a1(5);
    a1 << cout;
    return 0;
}

而如果我们在类外重载。

#include <iostream>
using namespace std;

class A
{
public:
    A(int t = 0) {
        num = t;
    }

    int getNum() {
        return this->num;
    }

   friend ostream & operator<<(ostream & a, const A & t);
private:
    int num;
};

ostream & operator<<(ostream & a, const A &t){
    a << t.num;
    return a;
}

int main(){
    A a1(5);
    cout << a1;
    return 0;
}

使用效果就符合我们预期了。

关于第二阶段开发

第二阶段选择自己组的代码进行开发则会乘上衰减因子是出于什么考量呢?
按我目前的理解,假如助教赞同我选择自己组代码时给出的解释,这个衰减因子或许显得有些没有道理。

编译与 Makefile 相关

编译器

Q: 我用 Mac。我可不可以用 clang 而不用 gcc
A: clang 的命令行接口的一个设计目标就是能和 gcc 无缝切换,所以完全没有问题。

Q: 为什么会显示 collect2: error: ld returned 1 exit status
A: ld 是链接器,这说明出现了链接错误。可以排查如下几种可能:

  • 你在编译命令中使用了 gcc 而不是 g++g++ 会自动链接 C++ 库的二进制文件,而 gcc 不会。
  • 你未把自己代码生成的所有 .o 文件链接在一起。

Makefile

Q: 有没有简单的 make 教程?
A: 这篇文章 至少可以教会你基本的编译,而且其各种版本广为流传。当然,不排除作业中有它没有涉及到的知识点。

Q: make 报错 Missing separator. 是什么情况?
A: 一种常见可能性是命令之前使用了空格而不是Tab,请把空格改成 Tab。如果你使用 VSCode,注意如果 editor.insertSpaces 选项打开,按 Tab 键会输入空格,数量取决于缩进设置。如果仅仅是要将单个文件由用空格缩进改为用 Tab 缩进,可以点击右下角形如 空格:4 的位置打开菜单。

Q: 为什么 make 没有找到 Makefile
A: 注意你的 Makefile 只能叫 Makefile makefile 等少量名称,默认情况下不能叫 Makefile.txt Makefile.mk 等。有的编辑器会自动加入后缀名。

Q: 为什么即使我 make clean 了,再次 make 的时候也没有重新编译?
A: 如果你用 Windows 并且试图将 clean 命令和 OJ 上的 Linux 保持一致,在 make clean 的时候可能只尝试删除了 main,但没有尝试删除 main.exe

计算图大作业问题 关于assert

  1. 请问题目要求中写的“能够在变量小于等于0时这个条件时报错。这项功能使得
    你能够用Assert(x)来保证x>=0功能”是否有矛盾?
  2. 请问当同一个节点(value < 0) 被第二次assert计算时,是否需要再次输出ERROR: Assertion failed?
    感谢!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.