cpp learning note

COMP6771 lecture note. COMP6771学习笔记

cpp

1
2
3
4
5
6
7
8
#include <iostream>

using namespace std;

int main() {
cout << "Hello world" << endl;
return 0;
}
1
2
3
4
5
6
7
8
#include <iostream>

using namespace std;

auto main() -> int {
cout << "Hello world" << endl;
return 0;
}

cpp 数据类型

学过c语言应该对数据类型都有所了解,cpp的数据类型和c语言差不多,但是比c语言要多几种。

1
bool, int, float, double, char, void(无类型), wchar_t(宽字符型)

wchar_t 是这样定义的,所以wchar_tshort int占用的空间一样:

1
typedef short int wchar_t;

typedef的作用就是给数据类型换一个名字而已,语法:typedef type name,比如:

1
typedef int bit;

编译器会理解成bitint是一样的数据类型。

枚举类型:

enum是枚举类型的关键词,枚举类型的定义如下:

1
enum enum-name { list of names} var-list;

enum-name 是枚举类型名。list of name 是由逗号分隔开的。

例如,我们可以使用枚举类型来定义一个color:

1
2
3
4
5
enum color {
red,
green,
blue
} c;

在color里面red默认是0,green默认是1,blue默认是2。也可以手动去更改相对应的数字,比如:

1
2
3
4
5
enum color {
red = 1,
green,
blue
} c;

那么green就是2,blue就是3。也可以去定义c = red || c = blue等等,这样c代表的就是不同的数字。一般在去设定人物名称或者别的不同类型的时候会用到枚举来替代数字,这样更容易分辨。比如说:

1
2
3
4
5
6
// assume:
// 0 is red,
// 1 is green
// 这样的话很难记住,如果使用枚举就不需要考虑这些,直接对数据进行判断就行
if (color == red) /*better than*/ if (color == 1)
// it's hard to known what is 1
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <iostream>
#include <limits>

using namespace std;
enum color {
red,
green,
blue
} c;

int main() {
c = blue;
cout << c << endl;
cout << color::red << endl;
return 0;
}

提示:

  1. 枚举变量可以直接输出,但不能直接输入
  2. 不能直接将常量赋给枚举变量
  3. 不同类型的枚举变量之间不能相互赋值
  4. 枚举变量的输入输出一般都采用switch语句将其转换为字符或字符串;枚举类型数据的其他处理也往往应用switch语句,以保证程序的合法性和可读性。

常见的类型修饰符:

1
long, short, signed, unsigned

long int 与 int 都是 4 个字节

可以使用<limits> 来查看相对应的数据类型的限制(在c语言里面声明头文件需要*.h,在cpp里面不需要带.h)

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include <limits>

using namespace std;

int main() {
cout << "Type size" << endl;
cout << "Int: " << sizeof(int) << endl;
cout << "Max int: " << numeric_limits<int>::max() << endl;
cout << "Min int: " << numeric_limits<int>::min() << endl;

return 0;
}

如果使用using namespace xxx;,在调用function的时候不需要xxx:cout,可以直接使用function。endl = \n,就是换行符的意思。<<可以理解为合并字符串,有点类似于shell里面的<不一样,只是类似numeric_limits这个函数里面使用到了泛型,但是忘得差不多了o(╥﹏╥)o。。。慢慢补吧,如果学过Java那么应该对这部分比较熟悉。

变量的声明:

1
2
3
4
type variable_name = value;
int a = 1;
int b,c;
extern int b;

未初始化的变量都被默认为null。

声明extern关键字的全局变量和函数可以使得它们能够跨文件被访问。

cpp 变量作用域

局部变量:

在函数或代码块内部声明的变量称为局部变量。他们在函数体内声明后仅能被其声明的所在函数体内部的后续语句操作。局部变量不能被函数外部访问到。也就是说只作用于这一个函数内:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

#include <iostream>

using namespace std;
int cal(int, int);

int main() {
int a = 10;
cout << a << endl;

cout << cal(1, 2) << endl;

return 0;
}


int cal(int a, int b) {
int c = a + b;
return c;
}

比如在int cal(int,int)里面定义了一个c的变量,那么这个c只能作用于cal这个function内。

全局变量:

在整个cpp文件内都可以用。在整个生命周期内都可以使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <iostream>

using namespace std;
void cal(int, int);

int sum = 0;

int main() {

cal(1, 2);
cout << sum << endl;

return 0;
}

void cal(int a, int b) {
sum = a + b;
}

这样就可以在任意函数调用sum这个变量。

cpp 常量

二进制,八进制,十进制,十六进制。二进制是以0bxx开头,数字范围是0和1。八进制是0开头的,数字范围是0-7。十进制就是我们常用的数字。十六进制是ox开头,取值范围是0-9, A-F

1
2
3
4
5
212 // 合法   
215u// 合法
0xFeeL // 合法
078 // 不合法: 8不是合法的八进制
032UU // 不合法: U后缀不能重复使用

其中u代表的是unsigned,也可以是l 等:

1
2
3
4
5
6
7
85 // 十进制
0213 // 八进制
0x4b // 十六进制
30 // 整型
30u// 无符号整型
30l// 长整型
30ul // 无符号整型

cpp储存类型

储存类型如下:

  1. auto
  2. register
  3. static
  4. extern
  5. mutable

auto储存类型:

auto 关键字用于两种情况:声明变量时根据初始化表达式自动推断该变量的类型、声明函数时函数返回值的占位符。

1
2
auto a = 3.14;
auto int month;

auto 仅能运用于函数内的局部变量。

register 存储类型:

register 存储类用于定义存储在寄存器中而不是 RAM 中的局部变量。这意味着变量的最大尺寸等于寄存器的大小(通常是一个词),且不能对它应用一元的 ‘&’ 运算符(因为它没有内存位置)。

1
register int  miles;

register类型应该仅应用于需要快速访问的变量,比如计数器。需要注意的是,定义 register 类型的变量并不意味着该变量一定就存储在寄存器中,这仅仅意味着需要按照硬件以及具体实现的限制来判定到底是不是存储在寄存器中。

static储存类型:

static 存储类型的变量意味着该变量将会从始至终地存活在程序的整个生命周期内,而不会随着每次访问到它所在的代码块时就建立该变量,离开代码块时就销毁该变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <iostream>

void call_num ();
using namespace std;


int main() {
call_num();
call_num();
call_num();

return 0;
}

void call_num () {
static int a = 5;
cout << a-- << endl;
}

/**
输出结果:
5
4
3
*/

extern 存储类型:

extern 存储类型用于使全局变量的引用对所有程序文件可见。如果前面已经定义了一个变量名,那么就不能再使用 extern 来声明同一变量名的变量了。有点类似于Python的import

main.app中:

1
2
3
4
5
6
7
8
9
#include <iostream>
int num;
extern void write_line();

int main() {
num = 1;
write_line();
return 0;
}

write.app中:

1
2
3
4
5
6
7
8
9
10
#include <iostream>

using namespace std;

extern int num;
//void write_line();

void write_line(void) {
cout << "count is : " << num << endl;
}

编译: g++ main.cpp write.app -o main

运行:./main

结果:

1
count is : 1

写着写着发现和C语言如此相似(基础部分)。(^▽^)

cpp Const 类型

Const 被当做一个很基础但是很有用的关键字。

先说下为什么用const:

  1. Clear code (know a function won’t try and modify something just by reading the signature)
  2. Immutable objects are easier to reason about.
  3. The complier may be able to mkae certain optimisations
  4. Immutable objects are much easier to use in multithreading situations.

eg:

1
2
auto const x = 10;
auto const y = 173

const修饰普通类型的变量:

1
2
3
4
int const a = 10;
int b = a;

a = 8; // error

a可以被定义为一个常量,也可以把a的内容赋值给b。但是不能对b再次赋值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>

using namespace std;

auto main() -> int {
const int a = 7;
int *p = (int*)&a;
*p = 1;

cout << *p << endl;
cout << "A: " << *p << endl;

return 0;
}

/**
Output:
1
A: 1
*/

我们可以通过得到a的地址,然后对地址直接重新赋值的方法来改变a的值。(指针就是一个变量的地址,&取地址,*取内容)

const修饰指针变量:

  1. const修饰指针指向的内容,则内容为不变量
  2. const修饰指针,则指针为不变量
  3. const修饰指针和指针指向的内容,则指针和指针指向的内容都是不可变

For 1:

内容不可以变,但是可以通过地址重新改变数据。

1
2
3
const a = 10;

int b = a

int b = a的时候b只是得到了a的内容,所以可以修改。

For 2:

1
2
3
4
5
6
7
int a = 8;
int * const p = &a;
*p = 9;

// error code
int c = 10;
p = &c;

在const左边,代表地址不可变,所以在第一次把a的地址赋值给p的时候没什么问题,但是当把c的地址赋值给p是就不行,*因为地址不可以变**。

For 3:

1
2
int a = 8;
const int * const p = &a;

啥都变不了。

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
2
3
4
5
6
7
8
9
10
auto i = 1;
auto const& ref = i;
std::cout << ref << '\n';
i++; // This is fine
std::cout << ref << '\n';
ref++; // This is not

auto const j = 1;
auto const& jref = j; // this is allowed
auto& ref = j; // not allowed

cpp functions

c++function有两种放回形式

1
2
3
4
5
6
7
auto square(int const x) -> int {
return x * x;
}

int square(int const x) {
return x * x;
}

Overload:

指函数名相同,但是它的参数表列个数或顺序,类型不同但是不能靠返回类型来判断。
(1)相同的范围(在同一个作用域中) ;
(2)函数名字相同;
(3)参数不同;
(4)virtual 关键字可有可无。
(5)返回值可以不同;

1
2
3
4
5
6
7
8
auto square(int const x) -> int {
return x * x;
}


auto square(double const x) -> double {
return x * x;
}

突然发现了一个很有意思的东西:const int * = int const *

Overload Resolution

这个过程简称方程匹配(function matching)

  1. Find function name
  2. Select viable ones: Same number arguments + each argument convertible
  3. Best match:Type much better in at least one argument
1
2
3
4
5
auto g() -> void;
auto f(int) -> void;
auto f(int, int) -> void;
auto f(double, double = 3.14) -> void;
f(5.6); // calls f(double, double)

尽量少的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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <iostream>

auto swap(int& x, int& y) -> void {
auto const tmp = x;
x = y;
y = tmp;
}

auto main() -> int {
auto i = 1;
auto j = 2;
std::cout << i << ' ' << j << '\n'; // 1 2
swap(i, j);
std::cout << i << ' ' << j << '\n'; // 2 1
}

reference可以让function的执行速度变快。

structures

和c语言的structure差不多的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
#include <vector>

using namespace std;

struct scientist {
string family_name;
string given_name;
vector<string> files;
};

auto main() -> int {
scientist s = scientist {
.family_name = "Li",
.given_name = "Shunyang",
.files = {
"c++",
"python"
}
};

cout << s.family_name << " " << s.given_name << s.files[0] << endl;
return 0;
}

Declarations vs Definitions

  1. A declaration makes known the type and the name of a variable
  2. A definition is a declaration, but also does extra things
    1. A variable definition allocates storage for, and constructs a variable
    2. A class definition allows you to create variables of the class’ type
    3. You can call functions with only a declaration, but must provide a definition later
  3. Everything must have precisely one definition
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void declared_fn(int arg);
class declared_type;

// This class is defined, but not all the methods are.
class defined_type {
int declared_member_fn(double);
int defined_member_fn(int arg) { return arg; }
};

// These are all defined.
int defined_fn() { return 1; }

int i;
int const j = 1;
auto vd = std::vector<double>{};

In c++, the global will not 0 without initialization,in function it will be null without initialization both are defination

Program error

  1. Compile-time
  2. Link-time
  3. Run-time
  4. Logic

这些错误循序渐进。

1
2
3
auto main() -> int {
a = 5; // Compile-time error: type not specified
}

因为没有返回任何数据,在编译的过程中就可以发现的错误。

1
2
3
4
5
6
7
#include "catch2/catch.hpp"

auto is_cs6771() -> bool;

TEST_CASE("This is all the code")
CHECK(is_cs6771()); // Link-time error: is_cs6771 not defined.
}

找到function相对应的文件和声明。(can not find library)

1
2
3
4
// attempting to open a file...
if (auto file = std::ifstream("hello.txt"); not file) {
throw std::runtime_error("Error: file not found.\n");
}

运行时候出错,不能找到相对应的资源。

1
2
auto const empty = std::string("");
CHECK(empty[0] == 'C'); // Logic error: bad character access

逻辑上的错误,属于操作失误,比如说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
2
3
4
5
std::vecotr
std::array
std::deque
std::forward_list
std::list

Unordered associate container:

1
2
3
4
5
6
7
// a collection of unique keys
std::unordered_map
absl::flat_hash_map

// a unique key with a values
std::unordered_set
absl::flat_hash_set

String in cpp

1
2
#include <string>
auto const gretting = std::string("hello world");

可以在一个区域内使用using namespaces

1
2
3
4
5
6
7
#include <string>

using namespace std::string_literals;
auto const str = "hello world"s;

// 拼接字符串
auto const str = absl::StrCat("hello", "word");

formatting string

1
2
3
4
5
6
7
8
#include <fmt/format.h>

// 有点类似python的写法,参考python的写法
auto const str = fmt::format("Hello: {}", "world");

// name parameters, 这个参数可以多次使用
auto const str = fmt::format("{key}: {value}, {value}", fmt::arg("key", 1),
fmt::arg("value", 12));

vector in cpp

1
2
3
4
5
6
7
8
9
10
11
12
auto vec = std::vector<int>{1,2,3};
// 删除末尾的元素
vec.pop_back();
REQUIRE(ranges::distance(vec) == 2);

// erase 的作用也是删除元素,不过可以删除任意位置的元素
// index 从0开始的
std::erase(vec, 1);


// 清空list
vec.clear();)

初始化数组

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 初始化数组长度为5,且每一个元素为0
auto vec = std::vector<int>(5);
REQUIRE(ranges::distance(vec) == 5);

CHECK(vec[0] == 0);
CHECK(vec[1] == 0);
CHECK(vec[2] == 0);
...

// 也可以指定初始化内容
auto const str = std::string("hello world");
auto all = std::vector<std::string>(2, str);

CHECK(all[0] == str);
CHECK(all[1] == str);

stack

可以直接根据reference来写。

1
2
3
4
5
#inlcude <stack>

// stack 是先进后出的数据结构
auto stack = std::stack<card>();
REQUIRE(stack.empty());

queue

可以直接根据reference来写。

1
2
3
4
5
6
#inlcude <queue>

// 先进先出
auto queue = std::queue<std::string>{};
queue.push("x");
queue.pop();

记得插图

some algorithm in iterators

有毒,直接卡死了。。。但是别的因该是可以的,在使用ranges::swap()的时候速度很快。
但是感觉和std::swap没什么区别。。。稍微快了那么一点。。。

1
2
3
4
5
6
7
8
9
10
11
12
13
14

#inlcude <range/v3/algorithm.hpp>
// count numbers

auto num = ranges::count(vector, "x");
auto node = ranges::find(vector, "x");
// node != vector.end() 才算是找到了,和普通的find效果是一样的

// ranges::adjacent_find 的做用就是找到两个相邻的一样的数据,并且直接返回该数据
// a, b, c, a ranges::adjacent_find(xxx, a)会返回最后一个a
auto node = ranges::adjacent_find(vector, "x");

// ranges::next(xx), 下一个, iterator的形式
auto node = ranges::next(vector);

Lambda expressions

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <range/v3/algorithm.hpp>

auto vec = std::vector<std::string>{'a', 'b', 'c', 'a'};
// 使用lambda表达式来计算a的数量
auto const numbers = ranges::count_if(vec, [](auto const& x) {
return x == 'a';
});

// 也可以指定返回格式,一般都是bool
auto const numbers = ranges::count_if(vec, [](auto const& x)->bool {
return x == 'a';
});

CHECK(numbers == 2);

Surprise binary search

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <range/v3/algorithm.hpp>
auto nums = std::vector<std::string>{'1','3','2', '1', '1'};
// 排序,这两种方法都可以排序,但是ranges快一点。。
ranges::sort(nums);
std::sort(nums);

#include "range/v3/algorithm/is_sorted.hpp"
// 检查是否排序
ranges::is_sorted(nums);


// 获取某一个区间, 这个区间内都是这个element
auto [first, last] = ranges::equal_range(nums, '1');
// first = 3, last = 4

// 使用[temp] 需要声明temp
auto temp = "1";
ranges::all_of(first, last, [temp](auto const& x) {return x == temp});

Week 2 part 2

the difference between ranges::distance and vector::size
下面三种方法都是为了使用auto,来遍历数组

1
2
3
4
5
6
7
8
9
10
11
12
13
// E.g. 1
auto v = std::vector<int>(other.size());

// E.g. 2 (yuck, but best option till you get more experience)
for (auto i = 0; i < ranges::distance(v); ++i) {
using size_type = std::vector<int>::size_type; // C++ typedef
v[gsl_lite::narrow_cast<size_type>(i)];
}

// E.g. 3 i should not leave the scope of the loop
for (auto i = std::vector<int>::size_type{0}; i < v.size(); ++i) {
v[i];
}

类型转换

1
2
3
4
5
6
auto const doubles = std::vector<double>{0.0,1.0};
auto const ints = std::vector<int>{doubles.begin(), doubles.end()};

// 所以这时候ints就是
// 2 个0
CHECK(ints = std::vector(2, 0);

genreate a sequence of integers on deman
生成一个序列

1
2
3
4
5
6
7
#inlcude <range/v3/numberic.hpp>
// 初始化大小为10的vector
auto vec = std::vector<int>(10);

// iota的作用就是生成一个从0到vec长度的数字存到vec里面
// 0..ranges::distance(vec)
ranges::iota(vec, 0);

使用piping

1
2
3
4
5
6
7
8
9
10
11
#include <range/v3/range.hpp>
#include <range/v3/view.hpp>

// 老师ppt上写错了
// 生成的结果好像是array,但是没办法检查type,我也不知道了
// 生成结果是iota_view 不是array
// 输出结果 [0,1,2,3,4,5,6,7,8,9]
auto numbers = ranges::views::iota(0, 10);

// 通过使用pipling的方法转换
auto vec = numbers | ranges::to<std::vector>;

Filters(keep if)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <range/v3/range.hpp>
#include <range/v3/view.hpp>

namespace view = ranges::views;
auto is_blue = [] (card const c) {return c.lolor == color::blue};

auto all_blue = hand | views::filter(is_blue);

// 都可以check是不是有这个元素
ranges::find();
// 也可以使用lambda表达式
ranges::any_of();

// 删除某个元素,都可以使用lambda的表达式
auto no_blue = hand | views::remove_if(is_blue);

// 在使用find的时候差别
std::find(vec.begin(), vec.end(), x);
ranges::find(vec, x);


// reversing, 反转链表
auto res = hand | views::reverse;

// 还有一个find_if, 但是返回的是指针, 需要*得到内容
auto res = ranges::find_if (hand, is_blue);

// 另一种用法
ranges::to<std::vector>(views::reverse(hand));

transform
通过一种形式来转换吧,可以理解为替换

1
auto res = hand | views::transform([](){});

split

1
views::split(' ') | views::transform([](){}) | ranges::to<std::string>;

join

1
2
3
auto const words = std::vector<std::string>{"hello", "word"};

auto const res = words | views::join(' ') | ranges::to<std::string>;

concatenating ranges (结合ranges)

1
2
3
4
5
6
auto const str1 = "hello ";
auto const str2 = "word";
auto const str3 = std::vector<string>{" ni", " hao"};

// 合并字符串
auto const str = views::concat(str1, str2, str3 | views::join(' ')) | ranges:to<std::string>;

use only the first n elements

1
2
3
4
5
6
7
auto const vec = std::vector<int>{1,2,3};
// 取前两个
auto const n = vec | views::take(2) | ranges::to<std::vector>;

// 取后几个

auto const n = vec | views::take_last(2) | ranges::to<vector>;

week3

Explicit type conversions

  1. A constructor for a class has only 1 parameter, the compiler will create an
    implicit type conversion from the parameter to the class
  2. 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 当加了explicit关键字以后就不能使用 auto a3 = 20这种写法了
// 因为explicit是为了避免这种隐式调用
class age{
private:
int age_;

public:
age(int age)
: age_{age} {}
};

auto main() -> int {
age a3 = 20;
auto a2 = age{20};
return 0;
}

通常情况下是不建议使用的,因为为了保持代码整洁explicit通常不会添加到construcotr里面的。
但是貌似在vlab里面还是要加,保持代码稳定

explicit是为了避免隐式调用。隐式调用通常发生在只有一个参数的时候。。

比如在不使用explicit的时候这样的写法是可以通过编译的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Point{
public:
Point(int x = 1, int y = 2) : x_{x}, y_{y} {};
auto get_x() const -> int {return x_;};
auto get_y() const -> int {return y_;};
private:
int x_;
int y_;
};


void displayPoint(const Point& p) {
std::cout << "(" << p.get_x() << ","
<< p.get_y() << ")" << std::endl;
}

auto main() -> int {
Point p = 2;
displayPoint(2);
return 0;
}

// (2,2)

虽然displayPoint要求的是一个Point类型,但是我们传过去一个2也能成功编译的。就是因为这隐式调用. 另外说一句, 在对象刚刚定义时, 即使你使用的是赋值操作符=, 也是会调用构造函数, 而不是重载的operator=运算符.

但是当我们声明了explicit的时候就不可以隐式调用了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
//
// Created by lsy on 2020/6/25.
//

#include <iostream>

class Point{
public:
explicit Point(int x = 1, int y = 2) : x_{x}, y_{y} {};
auto get_x() const -> int {return x_;};
auto get_y() const -> int {return y_;};
private:
int x_;
int y_;
};


void displayPoint(const Point& p) {
std::cout << "(" << p.get_x() << ","
<< p.get_y() << ")" << std::endl;
}

// error
auto main() -> int {
Point p = 2;
displayPoint(2);
return 0;
}

这个时候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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class person {
private:
mutable std::string name_;

public:
explicit person(std::string const& name)
: name_{name} {}

auto set_name(std::string const& name) const -> void {
name_ = name;
}

auto get_name() const -> std::string const& {
return name_;
}
};

auto main() -> int {

auto const p1 = person{"charles"};
p1.set_name("charles li");
std::cout << p1.get_name() << std::endl;


// const object
/**
* 一个const的object只能调用const的function
* 需要先定义function是const的才呢给你调用
* 一般只能调用没什么作用的function,不能进行赋值
* 可以随意定义function是const但是不做任何事情
* const object 不能修改内部变量的值
*
* 在 const object里面可以使用mutable来修改相对行的变量, 但是调用的function也必须是const的
*/

return 0;
}

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class age{
private:
int age_;

public:
age(int age)
: age_{age} {}

auto get_age() -> int {
return this->age_;
}

auto set_age(const int age) -> void {
this->age_ = 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class user {
private:
std::string name_;
public:
user(std::string const& name)
: name_{name} {}

static auto valid_name(std::string const& name) -> bool {
return name.length() < 0;
}
};


auto main() -> int {
auto name = std::string{"Charles li"};
std::cout << user::valid_name(name) << std::endl;
return 0;
};

static 也可以用正变量里面,但是必须在public里面,不然没办法调用。
但是在使用变量的时候必须声明是const,不然会报错,但是可以在前面加inline。

1
2
3
4
5
6
7
8
9
10
class user {
public:
static int const MAX=123;
};

// inline表示只能在一个文件里面,只定义一次。。
class user {
public:
inline static int MAX=123;
};

OOP

当使用default的时候表示没有参数,只是为了让编译器安静,没什么用。。。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class intvec{
public:
intvec() = default;
explicit intvec(std::vector<int>::size_type length)
: vec_(length, 0) {}

private:
std::vector<int> vec_;
};
auto main() -> int {

auto a = intvec{};
auto b = intvec{2};
};

week3 part2

nodiscard指的是当返回值被抛弃时,编译器给出警告
*friend 友元,别人是你的朋友,他可以访问我的东西。(但不是我可以访问他的东西: *

  • 类的友元函数是定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员。尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数。

  • 友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class INTEGER
{
friend void Print(const INTEGER& obj);//声明友元函数
};

// 请注意:print() 不是任何类的成员函数,函数名前不加类声明符
void Print(const INTEGER& obj)
{
//函数体
}
void main()
{
INTEGER obj;
Print(obj);//直接调用
}

}
特点:
友元关系不能被继承;
友元关系是单向的,不具有交换性。若类 B 是类 A 的友元,类 A 不一定是类 B 的友元,要看在类中是否有相应的声明;
友元关系不具有传递性。若类 B 是类 A 的友元,类 C 是 B 的友元,类 C 不一定是类 A 的友元,同样要看类中是否有相应的申明;
友元函数并不是类的成员函数,因此在类外定义的时候不能加上 class::function name;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
//
// Created by lsy on 2020/6/21.
//

#include <iostream>

class point {
private:
int x_;
int y_;
public:
point(int const& x, int const& y) : x_{x}, y_{y} {}

[[nodiscard]] auto x() const -> int {
return this->x_;
}

[[nodiscard]] auto y() const -> int {
return this->y_;
}

// 对operator进行重定义,但是只作用在这个class里面
friend point operator+(point const& p1, point const& p2);
friend std::ostream& operator<<(std::ostream& os, point const& p);

};

// friend function可以访问内部class变量,但是class不可以访问friend(关系不够好啊,哈哈哈)
// 而且friend可以访问class的private的变量
// 和普通的定义方法不一样,因为不是类的函数,所以不需要申明class::function name
point operator+(point const& p1, point const& p2) {
return point{p1.x() + p2.x(), p1.y() + p2.y()};
}
point operator+(point const& p1, point const& p2) {
return point{p1.x_ + p2.x_, p1.y_ + p2.y_};
}

// 相当与overloadding一个function
std::ostream& operator<<(std::ostream& os, point const& p) {
os << "(" << p.x() << ", " << p.y() << ")\n";
return os;
}

auto main() -> int {
auto p1 = point{1, 2};
auto p2 = point(3, 4);

std::cout << p1 + p2;
}

一般把friend的function放在class的定义里面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class point {
private:
int x_;
int y_;
public:
point(int const& x, int const& y) : x_{x}, y_{y} {}

[[nodiscard]] auto x() const -> int {
return this->x_;
}

[[nodiscard]] auto y() const -> int {
return this->y_;
}

// 对operator进行重定义,但是只作用在这个class里面
friend point operator+(point const& p1, point const& p2){
return point{p1.x() + p2.x(), p1.y() + p2.y()};
}

friend std::ostream& operator<<(std::ostream& os, point const& p) {
os << "(" << p.x() << ", " << p.y() << ")\n";
return os;
}

};

operator overloadding

就是对一些操作字符的重构。。

1
2

int operator+(int const& a, int const& b) {return a + b;}
----- End Thanks for reading-----