《C++游戏服务器开发入门到掌握》深入学习C++
2018-12-05 17:48
281 查看
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/fenglingfeixian/article/details/82629771
准备
- 三大编译器: vs、gcc(gcc.gnu.org)、clang(www.llvm.org)
- 安装 gcc:
sudo apt-get install g++
然后按两下talbe
看看有哪些版本,选择最新的安装。 - 增加 update 更新库:
sudo add-apt-repository ppa:ubuntu-toolchain-r/test
。g++ 安装失败有可能需要这样添加。 - 查看 gcc 版本:
gcc --version
。注意:如果自己选择安装了与默认不同的版本,则需要输入gcc-8 --version
查看。 - 安装 make:
sudo apt-get install make
。相比于windows环境下 VS 的工程创建不一样,linux 下必须要自己 make。规则本身简单,但是要写得好的话比较繁锁。(.h 文件修改要保持 make 更新的话,就需要在 makefile 文件中添加大量的 .h) - 编写 makefile:
hello:hello.o
g++ -o hello hello.o
hello.o:main.cpp
g++ -c -o hello.o main.cpp
四行拼起来。 - **安装 cmake:**手写 makefile 太复杂,用一些更高级的包装软件。
关键字
- 代替键盘上的字符:
and
、and_ep
、bitand
、bitor
、compl
、not
、not_eq
、or
、or_eq
、xor
、xor_eq
。 - constexpr: 编译期间能确定函数的返回值,直接用该值优化。
- const_cast:
const int j = 3; int *p = const_cast<int*>(&j); *p = 4;
有坑不要往里跳,代码中出现这种情况,说明设计有一定的问题。 - decltype: (declared type)
decltype(a->x) y; // 类型和a->x一样。
、decltype((a->x)) z = y; // y的引用。
表面看起来还没有auto好用。实际上是和auto一起用于模板编程中template<typename T, typename U> auto add(T a, U b) -> decltype(a+b) { return a+ b; }
后置返回类型。 - dynamic_cast: 去除动态转换。指针转换失败可以用是否为空来判断,但引用转换失败则会抛出异常。在C类cast、static_cast、const_cast中效率最低,但正确性最高(在运行期会做检查)。
- enum: 不受命名空间限制,容易冲突。所以以前的代码习惯性会在变量名中加自己的前缀。而且它的大小是根据实现决定的。
- enum class: 增加了命名空间限制,使用起来就像类一样。还可以自定义取值范围。
enum class NewColor : char { Red, Green, Blue };
之前说的接口参数不建议用bool类型,此时就可以用enum class替代。 - explicit: 增强明确性。个人见解,不要怕麻烦,能用则用。
- export: 比较尴尬。只有一款不流行的编译器实现了这个功能。Java编译器开发者,两三个人就能完成。两个顶尖计算机科学家花了两年也没有实现。C++11明确指出export基本不用了。
- friend: 很少用到。
- goto: 功能过于强大,基本不用。
- namespace: 未命名的namespace只能在本文件中使用,类似于static全局变量。
- noexcept(C++11起): 承诺函数不抛出异常,便于编译器优化。后面还可带括号
void f(int) noexcept(false) {}
。和以前的void f(int) throw() {}
一样。不太建议使用,需要像标准库一样严谨。 - nullptr(C++11起): 是一种类型,用于模板编程中固定为指针类型,如果是传入0或者NULL,模板是识别不出来指针类型的,nullptr就省略了(int*)0强转这一步。
- operator: 便于库的编写者用处比较大一些,对于普通C++程序员最好不用,带来的坏处比带来的好处还要多一些。
- register: 定义的变量放到寄存器里(建议编译器),提高速度。现在基本不用。
- reinterpret_cast: 几种cast总结:相对于C风格cast意图和分工更明确。除dynamic_cast以外,其他的都可以用C风格代替。建议使用C++版本的。
- requires(概念TS): C++17可能会用到。
- **static_assert:**编译时期确定。assert 是在运行时期确定的。
类
三个基本法则
- 资源管理: Java不需要手动释放只是说内存,除此之外还有文件句柄,网络连接。
- 三法则: 如果需要自己管理资源。析构函数、复制构造函数、赋值运算符。如果确实不需要复制对象,(C98中将复制构造函数、赋值运算符声明为private,但不实现它,链接时检查)(C++11中可以在private声明中 = delete,编译时检查)(继承boost::noncopyable可以省去写代码)
- C++诟病: 太多的复制。
vector<int> v = makeVector();
创建了临时变量,有没有办法将它利用起来。(C++采取了右值引用) - 左值和右值: 名字、类型、值。能取地址的通常是左值。右值无法取地址。
- 右值引用: 以前引用必须是可取地址的,
int& la = 1;
是错误的,但引入右值引用后int&& ra = 1;
可以。 - 新法则: 利用右值引用可以重载复制构造函数和赋值运算符。
- std::move(b): 将括号中的元素变换为右值。目的是把部分资源从右边直接移动到左边来(C++11之前没有区别)。因为对于右值来说,通常是临时变量,移交所有控制权。移交后的对象是完整的,但资源被抽干了,可以使用,但是不建议。
- 右值转换: 右值可以转换为const引用。
print(const int& a); print(1);
- delete规则: delete空指针没有问题,但是delete两次非空指针是会出问题的。
40、虚函数遇到构造析构就退化了
- 面向对象: 1、数据的封装;2、类的继承;3、函数的多态。
41、重新审视 auto
- 类型推导: 自动推导 = 号右边的数据类型,包括值和指针。
- range for: 自动遍历。优点是书写简单,运行效率高。缺点是必须全部遍历,没办法选取一半。
42、左值引用和右值引用(不考虑模板)
- 花括号构造: 更方便。但不允许有编译警告的值转换。比如 double 转 int 。
- 类成员初始化: 可以直接在定义的地方赋值。书写简单,不容易漏掉。如果不需要特殊的初始化,也要习惯地用默认值初始化,现在就可以用
int m_value {};
即可。 - 构造转发: 没有用到,所以没讲。
- 右值引用: 值可以改变。
int&& a = 10; a = 20;
。 - 右值引用: 不能直接等于左值,需要借助
std::move()
。
45、用 weak_ptr 打破循环引用
- 初始化: 用智能指针对象初始化。
- lock():
auto p = weakObj.lock();
如果有其它引用技术,则增加一份引用技术,返回智能指针对象,否则返回空。 - 重置: 当引用的对象调用 reset() 之后,就过期了。
- expired(): 是否过期(不会生成引用技术)。
48、使用智能指针需要注意的几个“坑”
ObjectPtr obj3(new Object(2)); // 会调用两次new() ObjectPtr obj4 = obj3; // 拷贝操作非原子操作,比较复杂,尤其是在多线程编程中 ObjectPtr obj5 = std::make_shared<Object>(3); // 保证只会调用一次new()
- 优先考虑: 如果有可能,优先使用类的实例,其次使用
std::unique_ptr
,万不得已使用std::shared_ptr
。
49、lambda 函数
// 作为本地变量 auto local = [](int a, int b) { std::cout << "a " << a << ", b" << b << std::endl; } local(1, 2); // 作为参数 template<typename Func> void printUseFunc(Func func, int a, int b) { func(a, b); } printUseFunc([](int a, int b) { std::cout << "a " << a << ", b" << b << std::endl; }, 1, 2); // 也可将外部参数传递进去 int a = 1; int b = 2; // [&a, &b]引用 or [=]全部采用 or [&]全部引用 auto local = [a, b]() { std::cout << "a " << a << ", b" << b << std::endl; } local(); // 这一切与Lua中的function功能相似
- 优点: 本质是inline函数,效率高于普通函数调用。算法中作为算子;网络编程中使用。
STL之容器
50、概述
- 内容: 算法、容器、迭代器。
- 序列式容器:
array/vector/deque/list/forward_list
,实现方式数组(遍历最快)或者指针。 - 关联式容器:
set/map/multiset/multimap
,实现方式二叉树(平衡二叉树(查找最差lgn)、红黑树)。 - 无顺序容器:
unordered_map/unordered_set/unordered_multimap/unordered_multiset
,hash table(哈希表(查找遍历比map/set
都快))。 - 总结: 清楚实现方式可以在使用时更好的评估代价。
- 比较重要的三种数据结构:
stack
栈,queue
队列,priority_queue
优先级队列。对以上的容器做封装、简化。 - 还有一些容器:
string
字符串其实是对字符封装的容器。bitset
对于表示对错效率更高。 - 新鲜血液:
regex
正则表达式,匹配文本。rand/thread/async/future/time
,写服务器要利用多核变得高效,就要经常用到这些东西。
51、容器保存的是什么
- 容器内元素的条件: 1、必须可以复制(copy)或者搬移(move);2、元素必须可以被赋值操作开复制或者搬移。
=
赋值操作符;3、元素可以被销毁。 - 不同的容器特殊的要求: 1、序列容器要求元素必须有默认构造函数;2、对于某些操作,元素需要定义
==
,比如std::find
;3、关联式容器需要定义<
;4、无顺序容器要提供一个hash
函数。 - stl 容器里面存的是元素的值而不是引用: 对于我们自己定义的类,里面到底存的什么东西呢?
- stl 设计原则: 效率优先、安全为次,并且基本没有处理异常(只有两个函数)。(错误检查是需要消耗性能的。)
52、容器的通用接口
- 通过对接口进行编程: 1、类的派生和继承;2、通过模板(STL主要就是采取这种方式)。
- size():
forward_list
不支持。 - clear():
std::array
不支持。 - <:
unordered
系列不支持。 - max_size(): 很少用到。
- e.swap(g)/swap(e,g): 交换容器内容。
std::array
比较特殊,耗时是线性增加的,其他的都很快O1。 - cbegin(): 返回
const &
迭代器。(begin()返回二者皆可能) - 总结:
forward_list
很少用到,std::array
几乎可以用vector
代替。
53、std::array
- 特点: 可以和C的接口做对接。
- 获取元素:
arr[i];
越界直接崩溃;arr.at(i);
越界抛出异常;std::get<i>(arr);
编译前检查是否越界(tuple的用法)。 - 其他函数:
arr.front();
、arr.back();
- tuple: C++11一个非常强大的数据结构,比以前常用的如pair,vector等都要强大很多。
54、std::verctor
- 特点: 可以和C的接口做对接。
- front()、back(): 调用前需要检查
empty()
,因为为空时是未定义的。array不会有这样的问题。 - pop_back(): maybe_wrong,要判断是否为空。并且多线程下要注意。
- clear(): 清理所有元素,但不会降低内存。
- shink_to_fit(): C++11才有的。建议capacity()根据size()降低使用内存。
- emplace(): 和copy/move操作相关。
- 特殊: 绝对不要存bool值
std::vector<bool>
,感兴趣可以自己查文档。
55、std::deque
- 特点: 随机访问元素,末端和头部添加删除元素效率高,中间低。元素的访问和迭代比vector要慢(内存是分开一块一块的),迭代器不是普通的指针(可理解为智能指针)。不能和C的接口做对接。
- 不同点: 没有
capacity()
、reserve(100)
,但是有shrink_to_fit()
,其他都与vector相同。
// 存储世界聊天内容 using Buffer = std::vector<char>; using Group = std::deque<Buffer>;
56、std::list
- 特点: 不支持随机访问,访问头部尾部元素速度快。对于异常支持好。
- 不同点: 和vector相比,没有
capacity()
、reserve(100)
、shrink_to_fit()
。 - 不能随机访问: 不支持
list[0]
和list.at(0)
。deque的存储方式是一块一块的,list的存储方式是一个一个的。
// 找到中间元素 auto iterBegin = a.begin(); // 初学者方法 for (int i = 0; i < 4; ++i) ++iterBegin; // 高级一点的方法 std::advance(iterBegin, 4); auto iter5 = std::next(iterBegin, 4); // 但最好不要出现这样的情况,如果频繁访问中间元素,而不是插入删除,可能用别的container会好一些。
// 算法 b.remove(1.0f); // 值等于1.0f的全部删除 b.remove_if([](auto v) { return v > 100.0f; }); // 传入比较条件,满足的全部删除。 b.reverse(); // 反转列表 b.sort(); // 默认以"<"排序。// stl的std::sort(a.begin(), a.end());对于list编译出错,所以list自带sort()方法。 g.sort(); b.merge(g); // 合并两个排好序的列表。 c.unique(); // 去重排好序的列表。没排好序的也能去重,只不过结果不对。1 1 2 2 1 1 3 4 -> 1 2 1 3 4 c.splice(c.begin(), b); // 在c的某个位置插入整个b。
总结: 在使用list和算法的时候,优先考虑list自身的算法,更高效。
57、std::forward_list
- 特点: C++11引入。不支持随机访问,访问头部元素速度快。对于异常支持好。有些接口看起来和list一样,但却有着细微的差别。
- 没有size():
forward_list
和自己手写的c-style singly linked list
相比无异,没有任何时间和空间上的额外开销,任何性质如果和这个目标抵触,我们放弃该特征。 - before_begin(): 返回头指针的前一位置。只能用于自身的一些算法,不能用于std通用算法。
- erase_after(): 删除传入迭代器之后的元素,返回void(list返回下一指向的迭代器)。
- insert_after(): 在传入的迭代器之后插入元素,返回指向插入元素的迭代器。
- splice_after():
c.splice_after(c.before_begin(), b);
58、智能指针的一个陷阱
// 单次消耗时间对比(纳秒) int 1.00 int* 1.23 SharedPtr 1.49 weakPtr 14.64
第三方程序: celero::Run(argc, argv);
总结: 频繁使用、循环遍历(玩家或怪物列表),不建议使用weak_ptr。
59、std::set
- 特点: 耗时O(logn),适合频繁插入删除查找元素,并且按照顺序排列的场景。和list一样,有自己的保存方式,需要消耗额外内存。(父节点+左节点+右节点+前置节点+后置节点)五个指针 * 8 = 40Byte。
- pair: 其实就是个含有first、second的数据结构。
- 成员算法:
a.count(1.0f);
、a.find(1.0f);
、a.lower_bound(3); // 返回第一个大于等于该值的迭代器位置
、a.upper_bound(3); // 返回第一个大于该值的迭代器位置
、a.equal_range(3); // 返回lower和upper的pair
。 - insert(): 对于set返回pair<Iterator, bool>,multiset肯定是成功的。
60、std::set(第二部分)
- 用起来不方便: 1、迭代器获取到的值是const,不能改变值(set根据值排序,想想如果值改变了,顺序是不是就乱了);2、查找也是通过值来查找,值满足条件即可,不能保证除此以外的符合条件(Person类有age和name,创建时要传入第二个参数决定排序的依据,所以查找时也是依据这个)。
- 坑: 即使重载了==运算符,也只有std::find(线性耗时)能找到,set本身的find(logn耗时)还是通过构造传入的比较类(第二个参数)进行查找。
- 应用场景: 一般用来存指针或者智能指针,或者基本类型的值。用的非常少,等待网络信息的socket(指针)存入set,主逻辑用的非常少。
61、std::map
- 特点: 使用频率仅次于vector,有的场景甚至排第一。内部是pair这点和set一样,只不过封装的接口不一样。
- 注意: find()返回的iterator实际为
std::pair<const int, std::string>&
,如果将此赋值给std::pair<int, std::string>
的对象或者引用对象又或者const引用对象,都会额外生成一个临时变量,很废。 - insert(): 对于map返回pair<Iterator, bool>,multimap肯定是成功的。
- 插入方式变化: 以前的insert()方式有限,推荐用C++11新特性emplace(),方法更多效率更高,键入字母也少。(讲解类型推导有点深奥)。
- 第三种插入方式[]: 因为没有的会创建,所以要求value的类型有默认构造函数。表达式等同于返回value的引用(没有就新建)。
- 第四种插入方式at(): C++11引入的,有则返回引用,没有就抛出异常。
- 总结: 实际生产中建议使用find()。虽然有点繁琐,但可以包装一下。
auto findIter = b.at(10); if (findIter != std::end(b) /* b.end() */) { auto& v = (*findIter).second; } else { }
// 包装 template <class Map> typename Map::mapped_type get_default( const Map &map, const typename Map::key_type &key, const typename Map::mapped_type &dflt = typename Map::mapped_type() ) { auto pos = map.find(key); return (pos != map.end() ? pos->second : dflt); } auto info = get_default(b, 10); if (info.empty()) { } else { }
62、std::unordered set/map
- 优点: C++11引入的查找速度比set/map的O(logn)还要快。
- 缺点: 1、没有排序(对于明确要求排序的场景不适用);2、平摊下来的速度是常量的,但也有极端情况(未讲);3、美国测试:一千万以下unordered优于set,一千万以上就反过来了(数量多hash算法会重复,然后会重新排列结构)(不过对于我们日常开发,不会到达千万级)。
- 模板参数: 可传5个参数,好在后面3个有默认值。在某本书上说过,你写的模板类每多一个模板参数,就会让潜在的用户减少一半。第4个参数主要用于处理不同key但hash值一样的情况。
- 模板类特化: 对于已经存在的模板类,指定其中一个模板参数的实现方式,成为模板类特化,实际用的很少。这里举例实现hash用到。
- hash评测: 一个好的hash算法,就是冲突尽可能地少,例子中只是简单的实现,比较挫。第二个例子会好很多,但是你无法证明冲突是最少的。第三个例子是经过数学证明的一种非常好的方法(来自boost库,具体原理讲师也不知道)。
参考自《C++游戏服务器开发入门到掌握》教学视频。
如有侵权,请联系本人删除。
相关文章推荐
- 《C++游戏服务器开发入门到掌握》Windows下原生API,用IOCP编写一个简易的服务器模型
- 用C++ 进行 ArcGis 开发的入门学习过程
- Android开发人员必须掌握的10 个开发工具+应该深入学习的10个开源应用项目
- [C++]DirectX 12 3D游戏开发实战—第14章 学习笔记01
- 在游戏开发中学习C++
- C++ 11学习和掌握 ——《深入理解C++ 11:C++11新特性解析和应用》读书笔记(一)
- [C++]DirectX 12 3D游戏开发实战—第13章 学习笔记01 2019.5.10
- 自学C++游戏程序开发学习顺序(转)
- 游戏服务器开发需要学习的技术
- Unity结合C++开发服务器实现多人游戏(三)
- 如何学习C++之资料看哪些,Visual C++入门及深入编程
- [C++]DirectX 12 3D游戏开发实战—第13章 学习笔记06 2019.5.16
- [C++]DirectX 12 3D游戏开发实战—第8章 学习笔记01 2019.4.30
- 游戏开发:OpenGL入门学习
- C++程序员学习android开发快速入门指引
- (pomelo系列入门教程)深入浅出Node.js游戏服务器开发--分布式聊天服务器搭建
- C++零基础教程,游戏开发入门
- Unity结合C++开发服务器实现多人游戏(演示)
- Unity结合C++开发服务器实现多人游戏(七)
- Linux服务器开发学习之C++