COMP6771 lecture note. COMP6771学习笔记
cpp
1 |
|
1 |
|
cpp 数据类型
学过c语言应该对数据类型都有所了解,cpp的数据类型和c语言差不多,但是比c语言要多几种。
1 | bool, int, float, double, char, void(无类型), wchar_t(宽字符型) |
wchar_t
是这样定义的,所以wchar_t
和short int
占用的空间一样:
1 | typedef short int wchar_t; |
typedef
的作用就是给数据类型换一个名字而已,语法:typedef type name
,比如:
1 | typedef int bit; |
编译器会理解成bit
和int
是一样的数据类型。
枚举类型:
enum
是枚举类型的关键词,枚举类型的定义如下:
1 | enum enum-name { list of names} var-list; |
enum-name 是枚举类型名。list of name 是由逗号分隔开的。
例如,我们可以使用枚举类型来定义一个color:
1 | enum color { |
在color里面red默认是0,green默认是1,blue默认是2。也可以手动去更改相对应的数字,比如:
1 | enum color { |
那么green就是2,blue就是3。也可以去定义c = red || c = blue
等等,这样c代表的就是不同的数字。一般在去设定人物名称或者别的不同类型的时候会用到枚举来替代数字,这样更容易分辨。比如说:
1 | // assume: |
1 |
|
提示:
- 枚举变量可以直接输出,但不能直接输入
- 不能直接将常量赋给枚举变量
- 不同类型的枚举变量之间不能相互赋值
- 枚举变量的输入输出一般都采用switch语句将其转换为字符或字符串;枚举类型数据的其他处理也往往应用switch语句,以保证程序的合法性和可读性。
常见的类型修饰符:
1 | long, short, signed, unsigned |
long int 与 int 都是 4 个字节
可以使用<limits>
来查看相对应的数据类型的限制(在c语言里面声明头文件需要*.h
,在cpp里面不需要带.h)
1 |
|
如果使用using namespace xxx;
,在调用function的时候不需要xxx:cout
,可以直接使用function。endl = \n
,就是换行符的意思。<<
可以理解为合并字符串,有点类似于shell里面的<
(不一样,只是类似) numeric_limits
这个函数里面使用到了泛型,但是忘得差不多了o(╥﹏╥)o。。。慢慢补吧,如果学过Java那么应该对这部分比较熟悉。
变量的声明:
1 | type variable_name = value; |
未初始化的变量都被默认为null。
声明extern关键字的全局变量和函数可以使得它们能够跨文件被访问。
cpp 变量作用域
局部变量:
在函数或代码块内部声明的变量称为局部变量。他们在函数体内声明后仅能被其声明的所在函数体内部的后续语句操作。局部变量不能被函数外部访问到。也就是说只作用于这一个函数内:
1 |
|
比如在int cal(int,int)
里面定义了一个c的变量,那么这个c只能作用于cal
这个function内。
全局变量:
在整个cpp文件内都可以用。在整个生命周期内都可以使用。
1 |
|
这样就可以在任意函数调用sum
这个变量。
cpp 常量
二进制,八进制,十进制,十六进制。二进制是以0bxx
开头,数字范围是0和1。八进制是0开头的,数字范围是0-7。十进制就是我们常用的数字。十六进制是ox
开头,取值范围是0-9, A-F
。
1 | 212 // 合法 |
其中u
代表的是unsigned
,也可以是l
等:
1 | 85 // 十进制 |
cpp储存类型
储存类型如下:
- auto
- register
- static
- extern
- mutable
auto储存类型:
auto 关键字用于两种情况:声明变量时根据初始化表达式自动推断该变量的类型、声明函数时函数返回值的占位符。
1 | auto a = 3.14; |
auto 仅能运用于函数内的局部变量。
register 存储类型:
register 存储类用于定义存储在寄存器中而不是 RAM 中的局部变量。这意味着变量的最大尺寸等于寄存器的大小(通常是一个词),且不能对它应用一元的 ‘&’ 运算符(因为它没有内存位置)。
1 | register int miles; |
register类型应该仅应用于需要快速访问的变量,比如计数器。需要注意的是,定义 register 类型的变量并不意味着该变量一定就存储在寄存器中,这仅仅意味着需要按照硬件以及具体实现的限制来判定到底是不是存储在寄存器中。
static储存类型:
static 存储类型的变量意味着该变量将会从始至终地存活在程序的整个生命周期内,而不会随着每次访问到它所在的代码块时就建立该变量,离开代码块时就销毁该变量
1 |
|
extern 存储类型:
extern 存储类型用于使全局变量的引用对所有程序文件可见。如果前面已经定义了一个变量名,那么就不能再使用 extern 来声明同一变量名的变量了。有点类似于Python的import
。
在main.app
中:
1 |
|
在write.app
中:
1 |
|
编译: g++ main.cpp write.app -o main
运行:./main
结果:
1 | count is : 1 |
写着写着发现和C语言如此相似(基础部分)。(^▽^)
cpp Const 类型
Const 被当做一个很基础但是很有用的关键字。
先说下为什么用const:
- Clear code (know a function won’t try and modify something just by reading the signature)
- Immutable objects are easier to reason about.
- The complier may be able to mkae certain optimisations
- Immutable objects are much easier to use in multithreading situations.
eg:
1 | auto const x = 10; |
const修饰普通类型的变量:
1 | int const a = 10; |
a可以被定义为一个常量,也可以把a的内容赋值给b。但是不能对b再次赋值。
1 |
|
我们可以通过得到a的地址,然后对地址直接重新赋值的方法来改变a的值。(指针就是一个变量的地址,&取地址,*取内容)。
const修饰指针变量:
- const修饰指针指向的内容,则内容为不变量
- const修饰指针,则指针为不变量
- const修饰指针和指针指向的内容,则指针和指针指向的内容都是不可变
For 1:
内容不可以变,但是可以通过地址重新改变数据。
1 | const a = 10; |
当int b = a
的时候b只是得到了a的内容,所以可以修改。
For 2:
1 | int a = 8; |
在const左边,代表地址不可变,所以在第一次把a的地址赋值给p的时候没什么问题,但是当把c的地址赋值给p是就不行,*因为地址不可以变**。
For 3:
1 | int a = 8; |
啥都变不了。
ps: 左定值,右定向,const修饰不变量
References and const
在这里reference可以表示为引用的意思。在reference里面&
不带比较地址。一个reference天生就是const。也就是说,一旦将一个reference绑定到一个对象,就无法再将它重新绑定到另一个不同的对象。在声 明一个reference之后没有写法可以将它重新绑定到另外一个对象
- A reference to const means you can’t modify the object using the reference
- The object is still able to be modified, just not through this reference
1 | auto i = 1; |
cpp functions
c++function有两种放回形式
1 | auto square(int const x) -> int { |
Overload:
指函数名相同,但是它的参数表列个数或顺序,类型不同。但是不能靠返回类型来判断。
(1)相同的范围(在同一个作用域中) ;
(2)函数名字相同;
(3)参数不同;
(4)virtual 关键字可有可无。
(5)返回值可以不同;
1 | auto square(int const x) -> int { |
突然发现了一个很有意思的东西:const int * = int const *
Overload Resolution
这个过程简称方程匹配(function matching)
- Find function name
- Select viable ones: Same number arguments + each argument convertible
- Best match:Type much better in at least one argument
1 | auto g() -> void; |
尽量少的overload,或者不用,因为不容易分辨。
Functions: pass by reference
- The formal parameter merely acts as an alias for the actual parameter
- Anytime the method/function uses the formal parameter (for reading or writing), it is actually using the actual parameter
- Pass by reference is useful when:
- The argument has no copy operation
- The argument is large
1 |
|
reference可以让function的执行速度变快。
structures
和c语言的structure差不多的。
1 |
|
Declarations vs Definitions
- A declaration makes known the type and the name of a variable
- A definition is a declaration, but also does extra things
- A variable definition allocates storage for, and constructs a variable
- A class definition allows you to create variables of the class’ type
- You can call functions with only a declaration, but must provide a definition later
- Everything must have precisely one definition
1 | void declared_fn(int arg); |
In c++, the global will not 0 without initialization,in function it will be null without initialization both are defination
Program error
- Compile-time
- Link-time
- Run-time
- Logic
这些错误循序渐进。
1 | auto main() -> int { |
因为没有返回任何数据,在编译的过程中就可以发现的错误。
1 |
|
找到function相对应的文件和声明。(can not find library)
1 | // attempting to open a file... |
运行时候出错,不能找到相对应的资源。
1 | auto const empty = std::string(""); |
逻辑上的错误,属于操作失误,比如说out index range,这些都是操作的失误。
Test Case
在cpp test case里面,CHECK和REQUIRED的区别:REQUIRE会导致test立马fail,但是CHECK不会,只会导致一个fail,test case还能继续。
Week 2 lecture notes
Why do we want to use libraries:
- well documented
- well tested
- well reviewed
- many feedback
What we use:
- c++ stl
- abseil
- catch2 (testing)
- {fmt} string format
- gsl-lite
- range-v3 (iterators)
Algorithms and Conatiners and Ranges combined to Iterators.
std::vecotr
is always the defalut container
Sequence containers:
1 | std::vecotr |
Unordered associate container:
1 | // a collection of unique keys |
String in cpp
1 |
|
可以在一个区域内使用using namespaces
1 |
|
formatting string
1 |
|
vector in cpp
1 | auto vec = std::vector<int>{1,2,3}; |
初始化数组
1 | // 初始化数组长度为5,且每一个元素为0 |
stack
可以直接根据reference来写。
1 |
|
queue
可以直接根据reference来写。
1 |
|
记得插图
some algorithm in iterators
有毒,直接卡死了。。。但是别的因该是可以的,在使用ranges::swap()
的时候速度很快。
但是感觉和std::swap
没什么区别。。。稍微快了那么一点。。。
1 |
|
Lambda expressions
1 |
|
Surprise binary search
1 |
|
Week 2 part 2
the difference between ranges::distance
and vector::size
下面三种方法都是为了使用auto,来遍历数组
1 | // E.g. 1 |
类型转换
1 | auto const doubles = std::vector<double>{0.0,1.0}; |
genreate a sequence of integers on deman
生成一个序列
1 |
|
使用piping
1 |
|
Filters(keep if)
1 |
|
transform
通过一种形式来转换吧,可以理解为替换
1 | auto res = hand | views::transform([](){}); |
split
1 | views::split(' ') | views::transform([](){}) | ranges::to<std::string>; |
join
1 | auto const words = std::vector<std::string>{"hello", "word"}; |
concatenating ranges (结合ranges)
1 | auto const str1 = "hello "; |
use only the first n elements
1 | auto const vec = std::vector<int>{1,2,3}; |
week3
Explicit type conversions
- A constructor for a class has only 1 parameter, the compiler will create an
implicit type conversion from the parameter to the class - This may be the behaviour you want(but usually not)
- You have to opt-out of this implicit type conversion with the explicit keyword
1 | // 当加了explicit关键字以后就不能使用 auto a3 = 20这种写法了 |
通常情况下是不建议使用的,因为为了保持代码整洁explicit
通常不会添加到construcotr里面的。
但是貌似在vlab里面还是要加,保持代码稳定
explicit是为了避免隐式调用。隐式调用通常发生在只有一个参数的时候。。
比如在不使用explicit的时候这样的写法是可以通过编译的:
1 | class Point{ |
虽然displayPoint
要求的是一个Point类型,但是我们传过去一个2也能成功编译的。就是因为这隐式调用. 另外说一句, 在对象刚刚定义时, 即使你使用的是赋值操作符=
, 也是会调用构造函数, 而不是重载的operator=
运算符.
但是当我们声明了explicit
的时候就不可以隐式调用了:
1 | // |
这个时候main函数里面这些都会报错的。
Const objects
Member functions are by default only be possible on non-const objects
- You can declare a const member function which is valid on const objects
- A const member function may only modify mutable members
- A mutable member should mean that the state of the member can change without the state of the object changing
- Good uses of mutable members are rare
- Mutable is not something you should set lightly
- One example where it might be useful is a cache
1 | class person { |
this function
- A member function has an extra implicit parameter, named this
- This is a pointer to the object on behalf of which the function is called
- A member function does not explicitly define it, but may explicitly use it
- The compiler treats an unqualified reference to a class member as being made through the this pointer.
- For the next few slides, we’ll be taking a look at the BookSale example in the course repo
在使用class的时候也可以只用this
的方法
还有就是在定义的时候可以考虑使用这个, 因为如果变量不使用下划线age_
,这样不容易分辨出到底使用的是什么变量可能是gobal或者别的
所以一般会采用this。
其实就是识情况而定
1 | class age{ |
static members
- Static functions and members belong to the class (i.e. every object), as opposed to a particular object.
- These are essentially globals defined inside the scope of the class
- Use static members when something is associated with a class, but not a particular instance
- Static data has global lifetime (program start to program end)
static
可以被全局调用,并且从开始到结束都能存活。static
的function可以被直接调用,可以不通过object来调用,object没办法调用static function。
1 | class user { |
static
也可以用正变量里面,但是必须在public里面,不然没办法调用。
但是在使用变量的时候必须声明是const,不然会报错,但是可以在前面加inline。
1 | class user { |
OOP
当使用default
的时候表示没有参数,只是为了让编译器安静,没什么用。。。
1 | class intvec{ |
week3 part2
nodiscard
指的是当返回值被抛弃时,编译器给出警告
*friend 友元,别人是你的朋友,他可以访问我的东西。(但不是我可以访问他的东西: *
类的友元函数是定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员。尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数。
友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元。
1 | class INTEGER |
}
特点:
友元关系不能被继承;
友元关系是单向的,不具有交换性。若类 B 是类 A 的友元,类 A 不一定是类 B 的友元,要看在类中是否有相应的声明;
友元关系不具有传递性。若类 B 是类 A 的友元,类 C 是 B 的友元,类 C 不一定是类 A 的友元,同样要看类中是否有相应的申明;
友元函数并不是类的成员函数,因此在类外定义的时候不能加上 class::function name;
1 | // |
一般把friend的function放在class的定义里面
1 | class point { |
operator overloadding
就是对一些操作字符的重构。。
1 |
|