文章目录
- 关于
- 课程介绍
- 1.Welcome!
- 2.Types and Structs
- 3. Initialization and References
- 4. Streams
- 5. Containers
- 6. Iterators and Pointers
- 7. Classes
- 8. Template Classes and Const Correctness
- 9. Template Functions
- 10. Functions and Lambdas
- 11. Operator Overloading
- 12. Special Member Functions
- 13. Move Semantics
- 14. std::optional and Type Safety
- 15. RAII, Smart Pointers, and Building C++ Projects
关于
个人博客,里面偶尔更新,最近比较忙。发一些总结的帖子和思考。
江湖有缘相见🤝。如果读者想和我交个朋友可以加我好友(见主页or个人博客),共同学习。笔者是学生,课业还是比较繁重的,可能回复不及时。笔者也正在四处寻找一些可以兼职锻炼知识并且补贴一些生活的工作,如果读者需要一些详细的辅导,或者帮助完成一些简易的lab也可以找我,笔者还是学生,自以为才学有限,也没有高价的理由📖。
课程介绍
CS106L 是斯坦福大学的一门 C++ 进阶课程,专注于 现代 C++ 语法、标准库(STL)和工程实践。它是 CS106B(数据结构与算法)的补充课程,适合已经掌握基础编程技能的学生,希望进一步深入了解 C++ 语言及其最佳实践。
课程主页
1.Welcome!
课程介绍,略。
2.Types and Structs
阅读材料
讲解了C++是静态类型的,介绍struct,include,using,auto这些。PPT做得很简洁很好看。
Assignment 0: Setup!
CS106L似乎没有像CS61A那样,把作业也放到时间表中,我自行穿插。
第一个作业是安装软件啥的,我的环境早就能运行了,没有按着他说的过程安装。按部就班应该不会出错。
界面好看,(●’◡’●)。
3. Initialization and References
阅读材料
PPT讲解了,结构化绑定,左值右值,引用和统一初始化。比较基础,感觉重点是左值和右值,这个在编程的时候比较容易搞错。
4. Streams
阅读材料
讲解了iostream相关知识。
Assignment 1: SimpleEnroll
用markdown浏览器插件打开可能好看一些。
Part 0: Read the code and fill in the Course struct
给Coures类添加定义。根据上下文推断都是string类型。
/**
* Represents a course a student can take in ExploreCourses.
* You must fill in the types of the fields in this struct.
* Hint: Remember what types C++ streams work with?!
*/
struct Course {
/* STUDENT TODO */ std::string title;
/* STUDENT TODO */ std::string number_of_units;
/* STUDENT TODO */ std::string quarter;
};
Part 1: parse_csv
分析csv文件,csv是逗号连接的文本文件。使用ifstream。注意了哦。
Do you need to change anything in the function definition? Spoiler, you do.
需要改变一些函数定义,比如,parse_csv的vector需要改成引用类型。
/**
* This function should populate the `courses` vector with structs of type
* `Course`. We want to create these structs with the records in the courses.csv
* file, where each line is a record!
*
* Hints:
* 1) Take a look at the split function we provide in utils.cpp
* 2) Each LINE is a record! *this is important, so we're saying it again :>)*
* 3) The first line in the CSV defines the column names, so you can ignore it!
*
* @param filename The name of the file to parse.
* @param courses A vector of courses to populate.
*/
void parse_csv(std::string filename, std::vector<Course>& courses) {
/* (STUDENT TODO) Your code goes here... */
// 创建文件输入流
std::ifstream ifs(filename);
// 使用getline进行读取
if (ifs.is_open()) {
std::string line;
std::getline(ifs, line);
// 舍弃第一行,循环读取
while (std::getline(ifs, line)) {
auto vec = split(line, ',');
courses.push_back({vec[0], vec[1], vec[2]});
}
}
ifs.close();
}
Part 2: write_courses_offered
往文件中写入,不是null的课程。
/**
* This function has TWO requirements.
*
* 1) Write the courses that are offered to the file
* "student_output/courses_offered.csv"
*
* 2) Delete the courses that are offered from the `all_courses` vector.
* IMPORTANT: do this after you write out to the file!
*
* HINTS:
* 1) Keep track of the classes that you need to delete!
* 2) Use the delete_elem_from_vector function we give you!
* 3) Remember to write the CSV column headers at the beginning of the output!
* See courses.csv for reference.
*
* @param all_courses A vector of all courses gotten by calling `parse_csv`.
* This vector will be modified by removing all offered courses.
*/
void write_courses_offered(std::vector<Course>& all_courses) {
/* (STUDENT TODO) Your code goes here... */
std::ofstream ofs(COURSES_OFFERED_PATH);
if (ofs.is_open()) {
ofs << "Title,Number of Units,Quarter" << std::endl;
std::vector<Course> new_all_courese;
for (auto [title, units, quarter] : all_courses) {
if (quarter != "null") {
ofs << title << "," << units << "," << quarter << std::endl;
} else {
new_all_courese.push_back({title, units, quarter});
}
}
all_courses = new_all_courese;
}
ofs.close();
}
Part 3: write_courses_not_offered
再次迭代输出未输出的。类似的
/**
* This function writes the courses NOT offered to the file
* "student_output/courses_not_offered.csv".
*
* This function is ALWAYS called after the `write_courses_offered` function.
* `unlisted_courses` will trivially contain classes that are not offered
* since you delete offered classes from `all_courses` in the
* `write_courses_offered` function.
*
* HINT: This should be VERY similar to `write_courses_offered`
*
* @param unlisted_courses A vector of courses that are not offered.
*/
void write_courses_not_offered(std::vector<Course> unlisted_courses) {
/* (STUDENT TODO) Your code goes here... */
std::ofstream ofs(COURSES_NOT_OFFERED_PATH);
if (ofs.is_open()) {
ofs << "Title,Number of Units,Quarter" << std::endl;
std::vector<Course> new_all_courese;
for (auto [title, units, quarter] : unlisted_courses) {
ofs << title << "," << units << "," << quarter << std::endl;
}
}
ofs.close();
}
5. Containers
阅读材料
讲解了各种STL容器。
Assignment 2: Marriage Pact
Marriage Pact貌似是斯坦福大学的一个问卷调查项目的名字。
Part 0: Setup
写上名字。
std::string kYourName = "hard stone"; // Don't forget to change this!
Part 1: Get all applicants
获取文件中所有的名字,放入集合容器当中。并且回到,short_answer文件里面的问题。
先回答问题1。
A1. 有序集合:
优点:元素始终按照某种顺序(通常是升序)排列。这使得有序集合在执行某些操作时非常高效,例如查找最小值或最大值、按顺序遍历所有元素等。
缺点:为了保持元素的有序性,插入和删除操作通常需要更多的时间复杂度(通常是O(log n))。这意味着在频繁插入或删除的场景下,性能可能不如无序集合。
无序集合:
优点:无序集合通过哈希函数存储和检索元素,通常具有更快的平均时间复杂度(O(1))来完成插入、删除和查找操作。这种高效性使得无序集合在处理大量数据时表现出色。
缺点:不保证元素的顺序,因此无法支持依赖于顺序的操作。此外,如果哈希函数设计不当或数据分布不均匀,可能会出现哈希冲突,从而导致性能下降。
size_t hash_student_name(const std::string& name) {
size_t hash = 0;
for (char c : name) {
hash = hash * 31 + c; // 使用质数(31)作为乘数,减少哈希冲突
}
return hash;
}
然后是代码,和前面的类似。
/**
* Takes in a file name and returns a set containing all of the applicant names as a set.
*
* @param filename The name of the file to read.
* Each line of the file will be a single applicant's name.
* @returns A set of all applicant names read from the file.
*
* @remark Feel free to change the return type of this function (and the function
* below it) to use a `std::unordered_set` instead. If you do so, make sure
* to also change the corresponding functions in `utils.h`.
*/
std::set<std::string> get_applicants(std::string filename) {
// STUDENT TODO: Implement this function.
std::ifstream ifs(filename);
std::set<std::string> st;
if (ifs.is_open()) {
std::string line;
while (std::getline(ifs, line)) {
st.insert(line);
}
}
return st;
}
Part 2: Find matches
先写一个获取名字的initial的函数,注意initial是名字的每个首字符。
std::istringstream
构造一个字符串的流的时候会拷贝一份字符串到自己的缓冲区,互不影响。
引用其实是变量的别名,取地址是相同的。
要把函数都写完才能测试。main函数在程序尾部的头文件中。
/**
* Takes in a set of student names by reference and returns a queue of names
* that match the given student name.
*
* @param name The returned queue of names should have the same initials as this name.
* @param students The set of student names.
* @return A queue containing pointers to each matching name.
*/
// 获取姓名的首字母
#include <sstream>
std::string get_initials(const std::string& name) {
std::istringstream iss(name);
std::string first, last;
iss >> first >> last;
std::string s;
s += std::toupper(first[0]);
s += std::toupper(last[0]);
return s;
}
std::queue<const std::string*> find_matches(std::string name, std::set<std::string>& students) {
// STUDENT TODO: Implement this function.
std::string initials_name = get_initials(name);
std::queue<const std::string*> q;
for (auto &student : students) {
if (get_initials(student) == initials_name) {
q.push(&student);
}
}
return q;
}
/**
* Takes in a queue of pointers to possible matches and determines the one true match!
*
* You can implement this function however you'd like, but try to do something a bit
* more complicated than a simple `pop()`.
*
* @param matches The queue of possible matches.
* @return Your magical one true love.
* Will return "NO MATCHES FOUND." if `matches` is empty.
*/
std::string get_match(std::queue<const std::string*>& matches) {
// STUDENT TODO: Implement this function.
if (matches.empty()) {
return "NO MATCHES FOUND.";
}
return *matches.front();
}
Q2. Note that we are saving pointers to names in the queue, not names themselves. Why might this be desired in this problem? What happens if the original set where the names are stored goes out of scope and the pointers are referenced?
A2. 存储指针的优点:节省内存、提高效率、避免重复数据。
潜在问题:如果原始数据超出作用域,队列中的指针会变成悬挂指针,导致未定义行为。
解决方案:确保数据生命周期足够长,或使用智能指针,或直接存储副本。
6. Iterators and Pointers
阅读材料
前缀自增和后缀自增
// 前缀递增运算符重载
Counter& operator++() {
++value; // 增加value的值
return *this; // 返回递增后的对象引用
}
// 后缀递增运算符重载
Counter operator++(int) { // 注意:后缀递增需要一个int参数(通常不使用)
Counter temp = *this; // 保存当前对象的副本
++(*this); // 调用前缀递增
return temp; // 返回递增前的对象副本
}
重载函数有参数的是后缀自增,后缀自增传递的是原来旧的值的拷贝,前缀自增返回的引用。一般来说,前缀自增的效率更高一些。
所有迭代器都提供以下四种操作:
auto it = c.begin();
获取容器c的开始迭代器。begin()函数返回一个迭代器,指向容器中的第一个元素。
++it;
前缀递增操作,将迭代器it向前移动到下一个元素。
*it;
通过迭代器访问当前指向的元素。解引用操作符*用于获取迭代器指向的元素的值。
it == c.end()
检查迭代器是否已经到达容器的末尾。end()函数返回一个迭代器,指向容器中最后一个元素之后的位置。如果it等于c.end(),表示it已经遍历完容器中的所有元素。
但大多数迭代器还提供更多操作:
--it;
前缀递减操作,将迭代器it向后移动到前一个元素。这种操作通常只在双向迭代器(bidirectional iterators)上可用。
*it = elem;
通过迭代器修改当前指向的元素。解引用操作符*用于设置迭代器指向的元素的值。
it += n;
随机访问迭代器(random access iterators)允许你将迭代器向前或向后移动n个位置。这种操作通常只在随机访问迭代器上可用,例如std::vector和std::array的迭代器。
it1 < it2
比较两个迭代器。如果it1在it2之前,即it1指向的元素在容器中排在it2指向的元素之前,那么这个表达式返回true。这种操作通常只在支持比较的迭代器上可用。
vector的迭代器的底层类型就是指针。
7. Classes
阅读材料
讲解的C++的类的继承。对我来说相对难的是虚函数和虚基类。C++的类是支持多重继承的,Java和Python都是单继承的,所以会出现菱形继承的问题,而继承虚基类则只会保留一个副本,避免产生冲突。
Assignment 3: Make a Class
Part 1: Making your class
?创建任意的类。
遇到了gccxml命令错误,感觉是环境哪里的问题。
头文件。
#include <string>
class Car {
private:
std::string make; // 汽车品牌
int year; // 汽车年份
public:
// 默认构造函数
Car();
// 带参数的构造函数
Car(std::string make, int year);
// 获取品牌
std::string getMake() const;
// 获取年份
int getYear() const;
// 设置品牌
void setMake(std::string make);
// 设置年份
void setYear(int year);
};
源文件。
#include "class.h"
// 默认构造函数
Car::Car() : make("Unknown"), year(2000) {}
// 带参数的构造函数
Car::Car(std::string make, int year) {
this->make = make;
this->year = year;
}
// 设置品牌
void Car::setMake(std::string make) {
this->make = make;
}
// 设置年份
void Car::setYear(int year) {
this->year = year;
}
std::string Car::getMake() const {
return this->make;
}
int Car::getYear() const {
return this->year;
}
sandbox
/*
* CS106L Assignment 3: Make a Class
* Created by Fabio Ibanez with modifications by Jacob Roberts-Baca.
*/
#include "class.h"
void sandbox() {
// STUDENT TODO: Construct an instance of your class!
// 使用带参数的构造函数
Car car2("Toyota", 2020);
// 修改属性
car2.setMake("Honda");
car2.setYear(2023);
}
Part 2: Short answer questions
略。
8. Template Classes and Const Correctness
阅读材料
讲解了模板类和Const 相关的知识。比较琐碎,建议细看。
9. Template Functions
阅读材料
更加复杂的模板元编程。
10. Functions and Lambdas
阅读材料
更加高级的语法,介绍了C++的新引入的range。
11. Operator Overloading
阅读材料
运算符重载相关内容,值得一读。
12. Special Member Functions
阅读材料
关于特殊的成员函数。
13. Move Semantics
阅读材料
移动语义。这个东西学过C++的人都喜欢拿来考别人,为了避免被C++的语言律师为难,还是要深入了解一下。Rule of Zero, Three, Five要记住。
Assignment 4: Ispell
实现一个比较古老的Unix上的拼写检查软件Ispell。
这个作业要求不能使用任何for或者while循环,全都使用STL实现。
tokenize
这个函数主要的功能是将传入的字符串转化为token集合。
Step One: Identify all iterators to space characters
这个assignment相对麻烦一些,用的都是新东西。
观察提供的find_all
。
template <typename Iterator, typename UnaryPred>
std::vector<Iterator> find_all(Iterator begin, Iterator end, UnaryPred pred) {
std::vector<Iterator> its{begin};
for (auto it = begin; it != end; ++it) {
if (pred(*it))
its.push_back(it);
}
its.push_back(end);
return its;
}
这里帮你循环了,并且会把容器头尾的迭代器存起来。所以我们只需要传入一个单个参数的判断函数pred。
Step Two: Generate tokens between consecutive space characters
使用transform和inserter搭配。
Step Three: Get rid of empty tokens
清空一些空串token。
注意vscode插件的设置,否则可能无法显示C++20的语法。
完整代码
Corpus tokenize(const std::string& source) {
/* TODO: Implement this method */
// 获取空格所在位置的迭代器,以及首尾两个迭代器
auto vec = find_all(source.begin(), source.end(), isspace);
// 把构造出token放入到集合当中
// 创建一个空的集合
Corpus tokens;
// 构造token插入集合
std::transform(vec.begin(), vec.end() - 1, vec.begin() + 1,
std::inserter(tokens, tokens.begin()),
[&source](auto it1, auto it2) {
return Token{source, it1, it2};
}
);
std::erase_if(tokens, [](Token token) {
return token.content.empty();
});
return tokens;
}
spellcheck
完成单词检查的函数。
Step One: Skip words that are already correctly spelled.
使用lambda函数过滤掉在字典中的单词。
Step Two: Find one-edit-away words in the dictionary using Damerau-Levenshtein
真的很贴心已经把计算编辑距离的函数给了。
完整代码
中文注释会影响对源代码的分析,要注意不能使用中文写注释。
template <typename Iterator, typename UnaryPred>
std::vector<Iterator> find_all(Iterator begin, Iterator end, UnaryPred pred);
Corpus tokenize(const std::string& source) {
/* TODO: Implement this method */
auto vec = find_all(source.begin(), source.end(), isspace);
Corpus tokens;
std::transform(vec.begin(), vec.end() - 1, vec.begin() + 1,
std::inserter(tokens, tokens.begin()),
[&source](auto it1, auto it2) {
return Token{source, it1, it2};
}
);
std::erase_if(tokens, [](Token token) {
return token.content.empty();
});
return tokens;
}
namespace rv = std::ranges::views;
std::set<Mispelling> spellcheck(const Corpus& source, const Dictionary& dictionary) {
/* TODO: Implement this method */
auto view = source | rv::filter(
[&dictionary](Token token) {
return !dictionary.contains(token.content);
}
)
| rv::transform(
[&dictionary](Token token) -> Mispelling {
auto oneDistance = dictionary | rv::filter([&token](std::string str){
return levenshtein(str, token.content) == 1;
});
std::set<std::string> suggestions(oneDistance.begin(), oneDistance.end());
return Mispelling{token, suggestions};
}
)
| rv::filter(
[](Mispelling miss) {
return !miss.suggestions.empty();
}
)
;
return std::set<Mispelling>(view.begin(), view.end());
};
Assignment 5: Treebook
Part 1: Viewing Profiles
重载一下打印的运算符<<,这里要使用左侧的其他类的对象,因此要定义在类的外面。由于这里的string数组是私有的,需要定义为友元函数。
首先在User类中添加。
class User
{
public:
User(const std::string& name);
void add_friend(const std::string& name);
std::string get_name() const;
size_t size() const;
void set_friend(size_t index, const std::string& name);
/**
* STUDENT TODO:
* Your custom operators and special member functions will go here!
*/
friend std::ostream& operator<< (std::ostream& os, const User& user);
private:
std::string _name;
std::string* _friends;
size_t _size;
size_t _capacity;
};
然后再实现重载。
/**
* STUDENT TODO:
* The definitions for your custom operators and special member functions will go here!
*/
std::ostream& operator<< (std::ostream& os, const User& user) {
os << "User(name=" << user.get_name() << ", friends=[";
std::string* friends = user._friends;
size_t size = user.size();
for (size_t i = 0; i < size; ++i) {
if (i + 1 < size) {
os << friends[i] << ", ";
} else {
os << friends[i];
}
}
os << "])";
return os;
}
Part 2: Unfriendly Behaviour
再次温习一下。总共是6个特殊的成员函数。
默认空参构造函数,拷贝构造函数,拷贝赋值函数,析构函数,移动构造函数,移动赋值函数。
记住零原则,三原则,五原则。
编译器的行为是各种抑制,但是根据三原则,五原则写就不会错。
头文件。
~User();
User(const User& user);
User& operator=(const User& user);
User(User&& user) = delete;
User& operator=(User&& user) = delete;
实现。注意自赋值要特判。
User::~User() {
delete[] _friends;
}
User::User(const User& user) : _name(user._name),
_size(user._size),
_capacity(user._capacity),
_friends(new std::string[user._capacity])
{
for (size_t i = 0; i < _size; ++i) {
_friends[i] = user._friends[i];
}
}
User& User::operator=(const User& user) {
if (this == &user) {
return *this;
}
delete[] _friends;
_name = user._name;
_size = user._size;
_capacity = user._capacity;
_friends = new std::string[user._capacity];
for (size_t i = 0; i < _size; ++i) {
_friends[i] = user._friends[i];
}
return *this;
}
Part 3: Always Be Friending
重载+=和<
User& operator+=(User& other);
bool operator<(const User& other) const;
注意const一致性,否则编译器不允许编译。
实现。
User& User::operator+=(User& other) {
this->add_friend(other.get_name());
other.add_friend(this->get_name());
return *this;
}
bool User::operator<(const User& other) const {
return this->get_name() < other.get_name();
}
通过!
14. std::optional and Type Safety
阅读材料
讲解了std::optional和类型安全。但是C++的STL是不会实现这些的,性能第一。其他让程序员来解决,防止出现UB。
Assignment 6: Explore Courses
使用std::optional来改进程序。
Part 0: Include optional
引入optional头文件
Part 1: Write the find_course function
修改find_course我一个就是用optional修改。注意FillMeIn是string*类型。
std::optional<FillMeIn> find_course(std::string course_title)
{
/* STUDENT_TODO: Implement this method! You will need to change the return
* type. */
for (auto& course : courses) {
if (course.title == course_title) {
return &course;
}
}
return {};
}
Part 2: Modifying the main function
有有点诡异的说。要求到C++23的语法。修改main函数用所谓的monadic(一元的)。
/********************************************************
STUDENT_TODO: Populate the output string with the right information to print
Please pay special attention to the README here
********************************************************/
std::string output = *course.and_then(
[](FillMeIn f) {
std::optional<std::string> ans = "Found course: " + f->title + "," + f->number_of_units + "," + f->quarter + "\n";
return ans;
}
).or_else(
[]() {
std::optional<std::string> ans = "Course not found.\n";
return ans;
}
)
;
/********************************************************
DO NOT MODIFY ANYTHING BELOW THIS LINE PLEASE
********************************************************/
15. RAII, Smart Pointers, and Building C++ Projects
阅读材料
RAII(Resource Acquisition Is Initialization,资源获取即初始化),是C++中的一个很重要的概念,PPT介绍一个例子,就是说当你new了一个指针对象之后,在释放前,抛出错误了,就没有释放资源,导致内存泄漏了,通过智能指针类能解决这个问题。后面介绍了一下编译过程,makefile和cmake。
Assignment 7: Unique Pointer
终于来到最后一集了。这个作业的内容是实现自己的unique_ptr类。
Part 1: Implementing unique_ptr
Implementing unique_ptr functionality
完成基础功能。
Implementing RAII
完成SMF的定义。
Part 2: Using unique_ptr
实现一个构造链表的函数。
完整代码
头文件。要把那些throw都去掉,运行时间可能比较长,需要几分钟。
#pragma once
#include <cstddef>
#include <utility>
namespace cs106l {
/**
* @brief A smart pointer that owns an object and deletes it when it goes out of scope.
* @tparam T The type of the object to manage.
* @note This class is a simpler version of `std::unique_ptr`.
*/
template <typename T> class unique_ptr {
private:
/* STUDENT TODO: What data must a unique_ptr keep track of? */
T* ptr;
public:
/**
* @brief Constructs a new `unique_ptr` from the given pointer.
* @param ptr The pointer to manage.
* @note You should avoid using this constructor directly and instead use `make_unique()`.
*/
unique_ptr(T* ptr) : ptr(ptr) {
/* STUDENT TODO: Implement the constructor */
// throw std::runtime_error("Not implemented: unique_ptr(T* ptr)");
}
/**
* @brief Constructs a new `unique_ptr` from `nullptr`.
*/
unique_ptr(std::nullptr_t) : ptr(nullptr) {
/* STUDENT TODO: Implement the nullptr constructor */
// ptr = nullptr;
// throw std::runtime_error("Not implemented: unique_ptr(std::nullptr_t)");
}
/**
* @brief Constructs an empty `unique_ptr`.
* @note By default, a `unique_ptr` points to `nullptr`.
*/
unique_ptr() : unique_ptr(nullptr) {}
/**
* @brief Dereferences a `unique_ptr` and returns a reference to the object.
* @return A reference to the object.
*/
T& operator*() {
/* STUDENT TODO: Implement the dereference operator */
return *ptr;
// throw std::runtime_error("Not implemented: operator*()");
}
/**
* @brief Dereferences a `unique_ptr` and returns a const reference to the object.
* @return A const reference to the object.
*/
const T& operator*() const {
/* STUDENT TODO: Implement the dereference operator (const) */
return *ptr;
// throw std::runtime_error("Not implemented: operator*() const");
}
/**
* @brief Returns a pointer to the object managed by the `unique_ptr`.
* @note This allows for accessing the members of the managed object through the `->` operator.
* @return A pointer to the object.
*/
T* operator->() {
/* STUDENT TODO: Implement the arrow operator */
return ptr;
// throw std::runtime_error("Not implemented: operator->()");
}
/**
* @brief Returns a const pointer to the object managed by the `unique_ptr`.
* @note This allows for accessing the members of the managed object through the `->` operator.
* @return A const pointer to the object.
*/
const T* operator->() const {
/* STUDENT TODO: Implement the arrow operator */
return ptr;
// throw std::runtime_error("Not implemented: operator->() const");
}
/**
* @brief Returns whether or not the `unique_ptr` is non-null.
* @note This allows us to use a `unique_ptr` inside an if-statement.
* @return `true` if the `unique_ptr` is non-null, `false` otherwise.
*/
operator bool() const {
/* STUDENT TODO: Implement the boolean conversion operator */
return ptr != nullptr;
// throw std::runtime_error("Not implemented: operator bool() const");
}
/** STUDENT TODO: In the space below, do the following:
* - Implement a destructor
* - Delete the copy constructor
* - Delete the copy assignment operator
* - Implement the move constructor
* - Implement the move assignment operator
*/
~unique_ptr() {
delete ptr;
}
unique_ptr(const unique_ptr& other) = delete;
unique_ptr& operator=(const unique_ptr& other) = delete;
unique_ptr(unique_ptr&& other) noexcept {
ptr = other.ptr;
other.ptr = nullptr;
}
unique_ptr& operator=(unique_ptr&& other) noexcept {
if (this != &other) {
delete ptr;
ptr = other.ptr;
other.ptr = nullptr;
}
return *this;
}
};
/**
* @brief Creates a new unique_ptr for a type with the given arguments.
* @example auto ptr = make_unique<int>(5);
* @tparam T The type to create a unique_ptr for.
* @tparam Args The types of the arguments to pass to the constructor of T.
* @param args The arguments to pass to the constructor of T.
*/
template <typename T, typename... Args>
unique_ptr<T> make_unique(Args&&... args) {
return unique_ptr<T>(new T(std::forward<Args>(args)...));
}
}
构造链表的函数。
template <typename T> cs106l::unique_ptr<ListNode<T>> create_list(const std::vector<T>& values) {
/* STUDENT TODO: Implement this method */
cs106l::unique_ptr<ListNode<T>> head = nullptr;
int n = values.size();
for (int i = n - 1; i >= 0; --i) {
cs106l::unique_ptr<ListNode<T>> node = cs106l::make_unique<ListNode<T>> (values[i]);
node->next = std::move(head);
head = std::move(node);
}
return head;
throw std::runtime_error("Not implemented: createList");
}