外贸网站建设哪家公司好青岛市住房城乡建设局网站
外贸网站建设哪家公司好,青岛市住房城乡建设局网站,天津市建设工程交易信息网,python 直播网站开发本文旨在以最小的篇幅#xff0c;最少的信息#xff0c;介绍最高频使用的内容#xff0c;从而掌握C编程开发的能力。
这种能力#xff0c;只是语法层面#xff0c;不涉及具体的函数库#xff0c;基础库等内容。
能力准备#xff1a;需要C语言基础。基本的if else, whi…本文旨在以最小的篇幅最少的信息介绍最高频使用的内容从而掌握C编程开发的能力。
这种能力只是语法层面不涉及具体的函数库基础库等内容。
能力准备需要C语言基础。基本的if else, while基础数据类型等等不在本文涉及的范围之内。
环境准备
感谢微软的努力让我们在 Windows环境下可以毫无障碍的进行Linux开发。
推荐使用Windows wsl2 的环境开发。
打开Microsoft Store搜索ubuntu安装最新版本即可。
安装如有问题请自行百度
使用 vscode wsl插件的形式编辑、编译代码。ctrl 可以在命令行和文件编辑之间切换非常的方便。
在ubuntu下安装cmake, gcc, g。安装方法自行百度。
代码的组织结构
build : 编译目录
docs : 文档目录负责存放该代码相关的信息
libs : 该项目依赖的外部的库及头文件
libs/include 依赖库的头文件
libs/[编译器名称] 平台相关的库文件。比如cc放x86-64位的库arm-linux-gnu-gcc放该编译器编译出的相关的库文件
source 项目源码目录
test 测试目录内含测试代码
本文中所有涉及到的示例代码可在此下载
链接https://pan.baidu.com/s/1f73k5uxYTRgtMEORbvgqvA?pwdtnje
提取码tnje
编译方法:
cd build
cmake ..
make
该代码展示了一个功能库的目录结构编译方法。
如果想要做成可执行程序参考test目录中的内容即可。
类的基本规则
通常定义一个类我们会分为源文件.cpp和头文件.h分开来用。
如下为头文件。其中的注释请仔细阅读。 // rtspc.h
/*** author * brief rtsp客户端* version 0.1* date 2023-11-30* * copyright Copyright (c) 2023* */
// 使用pragma once 让头文件只引用一次。与下面的 _RTSPC_H_ 作用一致
#pragma once// 头文件避免重复引用。与#pragma once 二选一
#ifndef _RTSPC_H_
#define _RTSPC_H_#include string
#include functional// 注意原则上禁止在头文件中使用using namespace xxx。避免命名空间失效
// 实际上不管源文件和头文件都不建议using namespace的方式。而是直接写全。
// using namespace std;// 这是命名空间。可以有效隔离类函数变量的重名问题。在定义库时都建议添加使用。
namespace rtsp
{class Rtspc
{
// 公共方法类外部可访问
public:// 回调函数新写法。对应lambda表达式使用using OnData std::functionvoid(const char *data, int len);Rtspc(bool btcp, OnData onData);// 注意如果该类会被继承则务必将它写成虚函数。否则影响析构virtual ~Rtspc();// 建议安装doxygen插件在函数上方输入 /// 或者 /** 自动生成注释模板。/// brief 公共方法大写开头。私有方法小写开头代码规范自行约定。/// param url /// param bTCP /// return int Run(const std::string url, bool bTCP);/// brief 停止/// return void Stop(){_running false;}// 保护方法。类内及类的子类可访问。
protected:// 对于不改变类的内容的方法后面加const// 对于不希望被改变的返回的引用前面加constconst std::string getValue() const {return _url;}// 私有方法。通常它和私有成员的private分开写更清晰一些。
private:void workthread();// 私有成员。成员变量通常为私有成员
private:std::string _url; // 成员变量以 _ 开头以便代码中与局部变量参数做区分。bool _btcp;bool _running; OnData _onData;
};#endif //_RTSPC_H_} // namespace rtsp源文件长这样。
// rtspc.cpp
#include iostream
#include chrono
#include thread
#include rtspc.hnamespace rtsp
{// 构造函数
Rtspc::Rtspc(bool btcp, OnData onData)
// 这下面是类成员初始化的写法。据说比写在大括号里效率要高
:_btcp(btcp)
,_running(true)
,_onData(onData)
{}Rtspc::~Rtspc()
{
}int Rtspc::Run(const std::string url, bool bTCP)
{std::cout Running url std::endl;const std::string data haha, i am data;while (_running){std::this_thread::sleep_for(std::chrono::milliseconds(400));_onData(data.c_str(), data.size());}return 0;
}} // namespace rtsp
继承与虚函数
干货都在代码中
//rtp-pack.h 这是父类
#pragma once#include memory
#include functionalnamespace rtsp
{class RtpPack
{
public:// 回调打包好的数据using OnRtpData std::functionvoid(const std::string rtp);using Ptr std::shared_ptrRtpPack;RtpPack(OnRtpData onRtp):_onRtp(onRtp){}// 注意如果该类会被继承则务必将它写成虚函数。否则影响析构virtual ~RtpPack(){}virtual int Pack(const uint8_t *data, int len) 0;/// brief 创建打包器/// param encode 编码方式/// return static Ptr CreatePacker(const char *encode, OnRtpData onData);
protected:OnRtpData _onRtp;
};} // namespace rtsp// rtp-pack-h264.h 这是子类
#pragma once#include rtp/rtp-pack.hnamespace rtsp
{
class RtpPackH264 : public RtpPack
{
private:/* data */
public:RtpPackH264(RtpPack::OnRtpData onRtp):RtpPack(onRtp){}virtual ~RtpPackH264(){}virtual int Pack(const uint8_t *data, int len) override{std::string rtp;rtp.append(begin flag);rtp.append((const char *) data, len);_onRtp(rtp);return 0;}// 非虚函数int Demo(){return 0;}
};} // namespace rtsp如代码所示RtpPackH264为子类它继承于RtpPack
虚函数
其中Pack 在RtpPack中被定义为纯虚函数。这意味着你无法将RtpPack实例化。
也就是说RtpPack pack; 是非法的。
只有在子类中实现了 Pack方法就像RtpPackH264 一样它才能够被实例化。
std::function 与lambda表达式
它是C11开始支持的好东西它有两个作用
1替代回调函数
2替代回调函数的 param回传参数。
最重要的是第二点结合lambda函数使用让我们的代码看起来是如此的与众不同。
请参看如下示例。
testrtp.cpp
#include rtp/rtp-pack-h264.h
#include sys/types.h /* See NOTES */
#include sys/socket.h
int main()
{int sock socket(AF_INET, SOCK_STREAM, 0);connect(sock, xxx);// std::function 的定义与 lambda的应用rtsp::RtpPackH264 rtp([sock](const std::string rtp){send(sock, rtp.c_str(), rtp.size());});while (1){rtp.Pack(123456, 6);}return 0;
}可以看到RtpPackH264 rtp 在实例化的时候传的参数是一个奇怪的东西[sock](const std::string rtp){xxx
这个奇怪的东西叫作lambda表达式。也叫匿名函数。
[]内部就相当于我们注册回调函数时注册进去的param它通过回调函数再传回给我们。
而这里则不需要这么麻烦你可以在[ ] 中加入任意多的变量然后就如代码中的sock一样在lambda体中使用。
需要注意的是[sock]这是值传递的写法。它会记录sock的值。还可以这样写[sock]引用传递。此时需要注意它相当于记录了sock的指针。
这里还有另一种写法可以将lambda表达式写成一个变量 auto onRtp [sock](const std::string rtp){send(sock, rtp.c_str(), rtp.size());};rtsp::RtpPackH264 rtp(onRtp);
小提示
本节中的代码没有源文件。类的定义与实现可以都写在头文件中只不过这要看实际情况而写。
它的缺点是编译、链接较慢封装性差。
但有些时候比如模板必须写在头文件中。
类的本质是什么
C中的类命名空间虚实函数本质上都可以用C来表达。或者换个说法C编译器最终会把它变成C语言那样的东西。
就拿RtpPackH264来讲它在编译器处理后变成了如下的东西。至于C的各种特性都是语法糖。
rtp-pack-h264.c#include stdlib.h
#include stdint.htypedef void (*rtpCallback_t)(void *userparam, const uint8_t *data, int len);struct rtpH264_class{// 类成员变量rtpCallback_t _onRtp;void *_userparam;// 虚函数表struct rtpH264VirtualFunctionTable{int (*Pack)(struct rtpH264_class *thiz, const uint8_t *data, int len);}functionTable;
};// 虚函数的实现对应rtsp::RtpPackH264::Pack
// 注意这奇怪的名字param之后列出了参数类型。这就是为什么C允许重名但参数不同的函数。
int rtsp_RtpPackH264_Pack_param_u8_i32(struct rtpH264_class *thiz, const uint8_t *data, int len)
{return 0;
}// 构造函数生成对象时自动调用。无论是new还是局部变量
struct rtpH264_class *rtsp_RtpPackH264_RtpPackH264_param_rtpCallback_t_void(rtpCallback_t onRtp, void *userparam){// 分配内存struct rtpH264_class *rtp malloc(sizeof(struct rtpH264_class));// 构造虚函数表rtp-functionTable.Pack rtsp_RtpPackH264_Pack_param_u8_i32;rtp-_onRtp onRtp;rtp-_userparam userparam;return rtp;
}// 析构函数。在对象生命周期结束时自动调用
void rtsp_RtpPackH264_del_RtpPackH264(struct rtpH264_class *rtp)
{free(rtp);
}// 非虚函数的实现
int rtsp_RtpPackH264_Demo(struct rtpH264_class *thiz)
{return 0;
}以上代码中可以看到类的函数的名字实际上是由“命名空间类名方法名参数类型”以一定规则形成的。
而构造函数实际上是编译器在生成对象是帮我们调用的。
虚函数表是在构造函数中指向了各个实际的函数。不准确但按此理解无不利影响
敲黑板
所以非虚函数是在编译时就确定了调用关系的。比如调用RtpPackH264::Demo是在编译时就确定了要调这个函数。
虚函数是在执行时查表确定虚函数表中指向的是哪个函数从而完成调用。
请仔细研读对照上述c实现的类代码与类本身的关系。
请思考如下代码最终输出的是什么
class A{
public:void running(){printf(A running\n);}virtual void VirtualFunc(){printf(A virtual func\n);}
};
class B: public A{
public:void running(){printf(B running\n);}virtual void VirtualFunc(){printf(B virtual func\n);}
};void main()
{B b;A *a b;b.running();a-running();b.VirtualFunc();a-VirtualFunc();
}搜索一下隐藏和覆盖看看网上五花八门的解释对照我们把C的类改成C的写法你能明白隐藏和覆盖是咋回事了吗
还有lambdastd::function。
我们将lambda以基础的C类的方式来实现它是这样的
rtp-pack-lambda.hpp (由 rtp-pack 的无lambda写法
/*** author LiuFengxiang (20451250qq.com)* brief 以C的类模拟 lambda,捕获等行为* version 0.1* date 2023-12-01* * copyright Copyright (c) 2023* */
#pragma once#include stringnamespace lambdaTest
{// 代替 std::functionvoid(const std::string rtp)
// std::functionxxx 实际上是模板实例化成了一个类这个类会记录函数指针和lambda捕获的变量
class RtpPackFunc
{
public:typedef void (*OnRtpData)(RtpPackFunc *thiz, const std::string rtp);RtpPackFunc(OnRtpData data):_onRtp(data){}virtual ~RtpPackFunc(){}void Call(const std::string rtp){_onRtp(this, rtp);}
public:OnRtpData _onRtp;
};class RtpPack
{
public:RtpPack(RtpPackFunc *callback):_callback(callback){}~RtpPack(){}void Pack(){_callback-Call(haha);}
private:RtpPackFunc *_callback;
};} // namespace lambdaTest它的测试代码testrtp-lambda.cpp 由testrtp.cpp转化而来
/*** author LiuFengxiang (20451250qq.com)* brief 对应testrtp.cpp我们不使用lambda而是改用基础的类来实现* version 0.1* date 2023-12-01* * copyright Copyright (c) 2023* */#include sys/types.h /* See NOTES */
#include sys/socket.h
#include unistd.h#include rtp/rtp-pack-lambda.hppusing namespace lambdaTest;// 实际的实现并不相同但是这样写起来优雅一点儿也并不妨碍理解。
class MyRtpPackFunc :public RtpPackFunc
{
public:MyRtpPackFunc(RtpPackFunc::OnRtpData data, int sock):RtpPackFunc(data),_sock(sock){}virtual ~MyRtpPackFunc(){}
public:int _sock;
};// 编译器将lambda表达式生成了回调函数
static void MyOnRtpData(RtpPackFunc *func, const std::string rtp)
{printf(%s running, data: %s\n, __func__, rtp.c_str());MyRtpPackFunc *mine dynamic_castMyRtpPackFunc * (func);if (mine ! nullptr){// 不能真发没准备好呢if (0)send(mine-_sock, rtp.c_str(), rtp.size(), 0);}
}int main()
{int sock socket(AF_INET, SOCK_DGRAM, 0);// connect(sock, xxx);/*在使用lambda时编译器干了很多事情1将匿名函数以自有的规则命名objdump可以看一下巨长这里是 MyOnRtpData2将 std::function模板实例化相当于 MyRtpPackFunc3将实例化的类生成对象也就是这里的 func 并传入初始化的两个参数 MyOnRtpData, sock*/ MyRtpPackFunc *func new MyRtpPackFunc(MyOnRtpData, sock);// RtpPack记录的实际上就是 std::function 的对象: funcRtpPack rtp(func);while (1){rtp.Pack();usleep(1*1000*1000);}}为了简化写法我们帮编译器翻译的并不精确但这并不妨碍理解。
你就记住lambda表达式就是编译器帮你起名的匿名函数。而std::function 则是编译器帮你生成的类。
所谓捕获同样没什么神奇之处值捕获在类中直接记录了该变量的值引用捕获则是在类中记录了该类的指针。
思考值捕获和引用捕获的变量它们的生命周期是怎样的 本节是想告诉你C的很多规则并不是人为制订出来的而是语言本身的实现上必须这么做。它的因果关系是因为这门语言是这样设计的所以产生了这样的规则。
本节只是个引子借此提示。
多思考
多思考
多思考
多思考背后的机理那才能举一反三抓住本质。
记住这句话C的所有规则都是因为设计时只能这样做。
最常用容器
std::string 其实也是容器。但是我们把它当成一个普通类用就好了
vector 是数组容器。用来管理数量不定的同类型的内容
map 相当于一个映射表key,value的形式。通过key可以快速的查找到对应的值。
如下代码展示了一些常用的函数更详细内容查文档DevDocs
/*** author LiuFengxiang (20451250qq.com)* brief 介绍常用容器的用法* version 0.1* date 2023-12-01* * copyright Copyright (c) 2023* */#include vector
#include string
#include map
#include iostream#include memory // 智能指针相关class Node
{
public:// 智能指针的技巧。简化写法// 使用的时候 Node::Ptr p 相当于 std::shared_ptrNode pusing Ptr std::shared_ptrNode;Node(int val, const std::string str):_val(val),_str(str){}virtual ~Node(){}const std::string Str()const {return _str;}int Val()const {return _val;}void SetVal(int val){_val val;}private:int _val;std::string _str;
};static void vectorDemo()
{// 使用智能指针代替Node * 或者 直接Node。// 如果用指针在释放时必须逐个 delete。一但遗漏就会有内存泄漏// 如果直接用Node则每次加入时都会产生拷贝动作。std::vectorNode::Ptr vec;for (int i 0; i 10; i){auto node std::make_sharedNode(i, haha);vec.push_back(node);}// 遍历并按条件删除for (auto itvec.begin();it! vec.end();){if ((*it)-Val() 5){// 删除成员时不能直接itit vec.erase(it);}else{it;}}// 另一种遍历方式for (auto it : vec){printf(node val: %d, str: %s\n, it-Val(), it-Str().c_str());it-SetVal(it-Val()1);}// 注意如果是 std::vectorNode * vec, vec.clear() 执行时并不能自动对每个node做delete动作。// 所以此时你需要先逐个 delete 再行 clearvec.clear();
}// map 主要是为了快速通过key 来找到对应的内容。key可以是基础类型string
// 如果要把自定义的类作为key则需要自定义比较函数
void mapDemo()
{std::mapint, Node::Ptr _map;for (int i 0; i 10; i){auto node std::make_sharedNode(i, haha);_map.emplace(i, node);}// 查找int key 5;auto it _map.find(key);if (it ! _map.end()){std::cout we Found it, key: it-first , str: it-second-Str() std::endl;}else{std::cout We Failed found with key: key std::endl;}// 遍历并按条件删除for (auto it_map.begin();it! _map.end();){if (it-first 5){// 删除成员时不能直接itit _map.erase(it);}else{it;}}// 另一种遍历方式for (auto it : _map){std::cout node key: it.first , val: it.second-Str() std::endl;it.second-SetVal(it.second-Val()1);}
}int main()
{vectorDemo();mapDemo();return 0;
}智能指针的使用
智能指针是现代C编程非常重要的一个特性。
实际上有了智能指针之后我们不应该再使用裸指针了。
下面罗列几个主要的使用场景
1配合容器使用
比如有一个类Car它有很多成员。
如果定义std::vectorCar _carvec它的问题是
Car需要可拷贝有可能需要实现拷贝构造函数
Car是拷贝了多份的是独立的。它们之间互相完全无关。
而如果使用指针 std::vectorCar * _pcarvec。
那你需要注意的是插入前要new Car 擦除前要先 delete 成员。
最容易忘的是_pcarvec.clear(). 这个方法执行前你需要先遍历逐个delete car
此时更方便的用法是std::vectorstd::shared_ptrCar _shCarVec;
2回调函数中使用weak_ptr
weak_ptr的概念可以先百度一下。
回调函数有个比较大的问题是当回调上来之后数据的消费者可能已经被销毁了。这时我们的指针是否还生效如何判断
如下代码中rtspc的回调数据上来之后窗口是否还存在
这里我们通过保存它的weak_ptr句柄使用时通过lock的形式来处理。
只要lock成功了weak_ptr将会升级成为强引用说明对象还在我们就可以正常输入数据。 //rtspclient.cpp
#include memory
#include map
#include vector
#include mutex
#include thread
#include rtspc/rtspc.h// 解码窗口
class MyWindow
{
public:using Ptr std::shared_ptrMyWindow;MyWindow(int winid):_winid(winid){}~MyWindow(){printf(window %d destroyed\n, _winid);}int InputMediaData(const char *data, int len){// printf(input data in window: %d\n, _winid);return 0;}
private:int _winid;
};class WindowMgr
{
private:WindowMgr(/* args */){}~WindowMgr(){}
public:// 单例static WindowMgr Instance(){static WindowMgr _inst;return _inst;}void SetWindowCnt(int cnt){std::lock_guardstd::mutex guard(_mutex);// 注意哦这里窗口重建了if ((size_t)cnt ! _windows.size()){_windows.clear();// 延时扩大rtspc上回调时窗口销毁的概率std::this_thread::sleep_for(std::chrono::milliseconds(1000));for (int i 0; i cnt; i){_windows.push_back(std::make_sharedMyWindow(i));}}}MyWindow::Ptr GetWindow(int winid){std::lock_guardstd::mutex guard(_mutex);if ((size_t)winid _windows.size()){return nullptr;}return _windows[winid];}private:std::vectorstd::shared_ptrMyWindow _windows;std::mutex _mutex;
};static void PlayInWindow(int winid, const char *url){std::weak_ptrMyWindow weak WindowMgr::Instance().GetWindow(winid);rtsp::Rtspc rtspc(true, [rtspc, weak](const char *data, int len){std::shared_ptrMyWindow strongPtr weak.lock();if (strongPtr nullptr){printf(window destroyed, exit rtspc\n);rtspc.Stop();}else{strongPtr-InputMediaData(data, len);}});rtspc.Run(url, true);
}static void SetWindowCnt(int winCnt)
{printf(Now win cnt: %d\n, winCnt);WindowMgr::Instance().SetWindowCnt(winCnt);for (int i 0; i winCnt; i){std::thread([i](){char url[256];snprintf(url, sizeof(url), rtsp://192.168.1.2:554/live/chn%d, i);PlayInWindow(i, url);}).detach();}
}int main(int argc, const char *argv[])
{int count 4;while (true){SetWindowCnt(count);getchar();count;}return 0;
}可能还有同学有疑问如果strongPtr拿到之后在InputMediaData执行之前发生了窗口切换怎么办呢
这完全无须担心由于我们已经持有了window的强引用此时它并不会被销毁。只有等我们InputMediaData执行完之后rtspc的回调函数执行完strongPtr的生命周期完结此时智能指针的计数清零MyWindow才会得到释放。
3类成员指针
类成员指针它的重建需要先delete老的。析构时也需要析构。
而使用了智能指针这些工作都不需要做了
如下代码中智能指针_packer构造函数中的创建ChangePacker函数中把它重新赋值都不需要考虑销毁。因为智能指针会自动析构老的内容。
同时~Rtsps()析构函数执行时也不需要手工析构_packer。
/*** author LiuFengxiang (20451250qq.com)* brief rtsp 服务端* version 0.1* date 2023-12-9* * copyright Copyright (c) 2023* */
#pragma once
#include string
#include rtp/rtp-pack.hnamespace rtsp
{class Rtsps
{
public:Rtsps(/* args */){_packer RtpPack::CreatePacker(H264, [this](const std::string rtp){onRtpData(rtp);});}// 注意如果该类会被继承则务必将它写成虚函数。否则影响析构virtual ~Rtsps(){}/// brief 更换打包器/// param encode 打包器名称void ChangePacker(const char *encode){_packer RtpPack::CreatePacker(encode, [this](const std::string rtp){onRtpData(rtp);});}int Run(){return 0;}
private:void onRtpData(const std::string rtp){printf(onRtpData\n);}private:// 打包器的句柄。RtpPack::Ptr _packer;
};} // namespace rtsp
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/89719.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!