C++11特性详解

一、简介

在C++11标准出来之前,一直是C++98/03标准占引领地位,而C++98/03标准是C++98标准在2003年将存在的一些漏洞进行了修复,但并没有核心语法的改动。相比于C++98/03,C++11则带来了数量可观的变化,其中包含了约140个新特性,以及对C++03标准中约600个缺陷的修正,这使得C++11更像是从C++98/03中孕育出的一种新语言。相比较而言,C++11能更好地用于系统开发和库开发、语法更加泛华和简单化、更加稳定和安全,不仅功能更强大,而且能更好的提升开发效率。本文主要对一些常用的新特性进行讲解。

二、列表初始化 {}

2.1 C++98中的{}

C++98标准中,允许使用{}对数组或结构体元素进行统一的列表初始化:

struct Test
{int _x;int _y;
};
int main()
{int array1[] = { 1, 2, 3, 4, 5 };  // 数组列表初始化int array2[5] = { 0 };  // 数组列表初始化Test t = { 1, 2 };  // 结构体列表初始化return 0;
}

2.2 C++11中的{} 

在新出的C++11标准中,扩大了{}的使用范围,可以对所有的内置类型和用户自定义的类型使用初始化列表{},可以使用或不使用等号(=)

struct Test
{int _x;int _y;
};
int main()
{int array1[5] = { 1, 2, 3, 4, 5 };  //使用等号int array2[5]{ 1, 2, 3, 4, 5 };  //不使用等号int x = 1;int y{ 1 };  // 这种初始化不使用等号,看着感觉怪怪的,所以还是建议使用等号进行初始化Test t1 = { 1, 2 };  //使用=Test t2{ 1,2 };  // 不使用=//STL容器的使用vector<int> v{1,2,3,4,5};map<int, int> m{{1,1}, {2,2,},{3,3},{4,4}};return 0;
}

 对于自定义类型的列表初始化:

class Date
{
public:Date(int year, int month, int day):_year(year), _month(month), _day(day){cout << _year<<"-"<<_month<<"-"<<_day << endl;}
private:int _year;int _month;int _day;
};
int main()
{Date d1(2022, 1, 1); // 原始的初始化方法// 使用C++11支持的列表初始化,这里会调用构造函数初始化Date d2{ 2022, 1, 2 };Date d3 = { 2022, 1, 3 };return 0;
}

 2.3 std::initializer_list

官方文档,点击查看

 为什么C++11会支持新的{}的列表初始化呢?

因为以上的所有类型都支持了以std::initializer_list作为参数的构造函数。

比如list,vector, map等:

 

 

 三、声明

3.1 auto

在之前的C++98标准中,auto是一个存储类型的说明符,表明变量是局部自动存储类型,但是局部域中定义局部的变量默认就是自动存储类型,使得auto没有任何的价值体现,所以,在C++11中就废除了原先的auto用法,定义了一个全新的auto。C++11中,将其用于实现自动类型的推导,要求必须进行显示初始化,让编译器将定义对象的类型设置为初始化值的类型。使用auto,能使程序的书写提供便利及整体代码的简洁性。

	int i = 10;auto p = &i;  // 推导出p的类型应该为int*auto pf = "string";  // pf:  char const*cout << typeid(p).name() << endl;cout << typeid(pf).name() << endl;map<string, string> dict = { {"sort", "排序"}, {"insert", "插入"} };//使用auto自行推导出迭代器的类型,使代码简洁map<string, string>::iterator it1 = dict.begin();  auto it2 = dict.begin();

 3.2 decltype 

auto进行类型推导时必须进行显示初始化,否则auto是无法进行推导,编译器编译不过。而关键字decltype则将变量的类型声明为表达式指定的类型。如下使用:

	const int x = 2;double y = 3.3;decltype(x * y) ret;  // 推出ret的类型是doubledecltype(&x) p;  // 推出p的类型是int *cout << typeid(ret).name() << endl;cout << typeid(p).name() << endl;

3.3 nullptr

// C++中NULL的定义
#ifndef NULL#ifdef __cplusplus#define NULL 0#else#define NULL ((void *)0)#endif
#endif// C++中nullptr的定义
#if defined(nullptr)
#define nullptr EMIT WARNING C4005
#error The C++ Standard Library forbids macroizing the keyword "nullptr". \
Enable warning C4005 to find the forbidden define.
#endif // nullptr

在C++中,NULL被定义成字面量0,在使用的过程中可能会带来一些问题,因为0既能表示指针常量,又能表示整形常量。所以出于清晰和安全的角度考虑,C++11中新增了nullptr,用于表示空指针,使用nullptr更为安全。

四、final 与 override

final的作用:

a. C++11中,将类标记为final后,该类会禁止被继承。

b. 用final修饰虚函数是,该虚函数将不可被重写。

override的作用(用于派生类函数):
a. override用于在派生类中显式地重写基类中的虚函数。

b.  它可以确保在派生类中正确地覆盖基类的虚函数,并且如果没有正确地重写,则会产生编译错误。

五、=default 和 =delete

 强制生成默认函数关键字default:
C++11可以更好的控制要使用的默认函数。假设你要使用某个默认的函数,但是因为一些原因这个函数没有默认生成。比如:我们提供了拷贝构造,就不会生成移动构造了,那么我们可以使用default关键字显示指定移动构造生成.

class A
{
public:A() = default;  //让编译器默认生成无参构造函数A(int num)    // 存在有参构造,编译器不会默认生成无参构造,所有需要用default让编译器生成一下:_num(num){}void fun(){cout << "A::func()" << endl;}
private:int _num;
};

禁止生成默认函数的关键字delete:
如果能想要限制某些默认函数的生成,在C++98中,是该函数设置成private,这样只要其他人想要调用就会报错。在C++11中更简单,只需在该函数声明加上=delete即可,该语法指示编译器不生成对应函数的默认版本,称=delete修饰的函数为删除函数

class A
{
public:A(int a) : _a(a){}//C++11A(const A&) = delete;  //禁止编译器生成默认的拷贝构造函数A& operator=(const A&) = delete;  //禁止编译器生成赋值运算符重载
private: int _a;//C++98,设置成privateA(const A&) = delete;A& operator=(const A&) = delete;
};

 六、可变参数模板

 介绍

可变参数模板是指可以定义0到任意个模板参数的模板,它可以用来实现高度泛化的函数或类。可变参数模板的语法是使用省略号(…)来表示一个或多个参数。比如一个我们很熟悉的函数的参数就是可变参数:printf。它不仅可以接受不同数量的参数,还能接受不同类型的参数。

模板结构:

template<class …Args>
返回类型 函数名(Args… args)
{//函数体
}

示例:

// Args是一个模板参数包,args是一个函数形参参数包
// 声明一个参数包Args...args,这个参数包中可以包含0到任意个模板参数
template<class ...Args>
void ShowList(Args... args)
{//函数体
}

 作用

  1. 函数参数的数量不确定性: 可变参数模板允许函数接受任意数量的参数,可以在函数体内对这些参数进行处理。这在实现一些工具函数、容器类或者模板库时非常有用,比如 printf 风格的输出函数。

  2. 递归模板展开: 可变参数模板可以通过递归展开,用于在编译时展开模板参数的各个部分。这在实现一些递归算法或数据结构时非常有用,例如元组、可变参数列表的操作等。

  3. 多态参数: 可变参数模板使得可以将不同类型的参数传递给同一个函数或类模板,并在函数内部根据参数的类型来执行不同的操作,从而实现更通用的函数。

  4. 日志记录和调试: 可变参数模板可以用于实现灵活的日志记录和调试函数,能够根据不同的调用情况输出不同的信息。

  5. 类型安全的变参函数: 可变参数模板相对于C语言中的可变参数函数(如 printf)来说,能够提供更好的类型安全性,因为在编译时就可以对参数类型进行检查。

如下例子展示如何使用可变参数模板来实现递归求和:

#include <iostream>// 递归模板展开,计算可变数量参数的和
template<typename T>
T sum(T value) {return value;
}template<typename T, typename... Args>
T sum(T value, Args... args) {return value + sum(args...);
}int main() {int res = sum(1, 2, 3, 4, 5);std::cout << "sum: " << res << std::endl; // 输出 15return 0;
}

 因为语法不支持使用args[i]这样方式获取可变参数,所以就通过递归的方式将参数包进行展开,然后对参数包里面的每一种参数类型再进行求和。

七、lambda表达式

介绍

Lambda表达式允许在需要函数对象(函数指针、函数符号)的地方使用内联函数,而不需要再单独的去定义一个函数或函数对象来实现某种简单的功能。

格式:

[capture](parameters) mutable -> return_type 
{CODE;  // Lambda函数的代码
}

  • capture:捕获列表,用于捕获外部变量。编译器根据[]来判断接下来的代码是否为lambda函数,捕捉列表能够捕捉上下文的变量供lambda函数使用。
  • parameters:与普通函数的参数列表类似,表示传递给Lambda函数的参数,没有参数需要传递就可以空着不写。
  • mutable:默认情况下,lambda函数总是一个const函数,mutable可以取消其常量
    性。使用该修饰符时,参数列表不可省略(即使参数为空)
  • return_type:Lambda函数的返回类型。没有返回值时此部分可省略,返回值类型明确情况下,也可省略,由编译器对返回类型进行推导
  • CODE:Lambda函数的实际代码。在该函数体内,除了可以使用其参数外,还可以使用所有捕获到的变量

lambda的参数列表和返回值类型都是可选部分,而捕捉列表和函数体可以为空。因此C++11中最简单的lambda函数为:[]{}; 但是该lambda函数不能做任何事情。
捕获列表说明:

  • [] 不捕获任何变量
  • [var]:表示值传递方式捕捉变量var(传值捕获)
  • [=]:表示值传递方式捕获所有父作用域中的变量(包括this)
  • [=,&foo] 按值捕获外部作用域中所有变量,并按引用捕获foo变量
  • [&var]:表示传引用捕捉变量var
  • [&]:表示引用传递捕捉所有父作用域中的变量(包括this)
  • [this]:表示值传递方式捕捉当前的this指针

注意:

  • 父作用域指包含lambda函数的语句块
  • 语法上捕捉列表可由多个捕捉项组成,并以逗号分割。比如:[=, &a, &b]:以引用传递的方式捕捉变量a和b,值传递方式捕捉其他所有变量; [&,a, this]:值传递方式捕捉变量a和this,引用方式捕捉其他变量
  • 捕捉列表不允许变量重复传递,否则就会导致编译错误。比如:[=, a]:=已经以值传递方式捕捉了所有变量,捕捉a重复
  • 在块作用域以外的lambda函数捕捉列表必须为空。
  • 在块作用域中的lambda函数仅能捕捉父作用域中局部变量,捕捉任何非此作用域或者非局部变量都会导致编译报错。

示例:

int main()
{// 最简单的lambda表达式, 该lambda表达式没有任何意义[]{};// 省略参数列表和返回值类型,返回值类型由编译器推导为intint a = 3, b = 4;[=]{return a + 3; };// 省略了返回值类型,无返回值类型auto fun1 = [&](int c){b = a + c; };fun1(10);cout<<a<<" "<<b<<endl;    // 3 13// 各部分都很完善的lambda函数auto fun2 = [=, &b](int c)->int{return b += a+ c; };cout<<fun2(10)<<endl;  // 26// 复制捕捉xint x = 10;auto add_x = [x](int a) mutable { x *= 2; return a + x; };cout << add_x(10) << endl;  // 30return 0;
}

通过lambda写一个自定义排序: 

//商品类
struct Goods
{string _name;  // 名字double _price; // 价格int _eval; // 评价
};//按价格的升序
struct Cmpup
{bool operator()(const Goods& g1, const Goods& g2){return g1._price < g2._price;}
};//按价格降序
struct Cmpdest
{bool operator()(const Goods& g1, const Goods& g2){return g1._price > g2._price;}
};int main()
{vector<Goods> v = { { "苹果", 2.1, 300 }, { "香蕉", 3.3, 100 }, { "橙子", 2.2, 1000 }, { "菠萝", 1.5, 1 } };sort(v.begin(), v.end(), Cmpup());    //按价格升序排序sort(v.begin(), v.end(), Cmpdest()); //按价格降序排序// 使用lambda表达式自定义排序sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {return g1._price < g2._price; }); sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {return g1._price > g2._price; });sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {return g1._eval < g2._eval; });sort(v.begin(), v.end(), [](const Goods& g1, const Goods& g2) {return g1._eval > g2._eval; });return 0;
}

八、包装器 std::function

函数包装器 `std::function` 是C++11中的一个重要特性,它提供了一种能够存储和调用可调用对象(函数、函数指针、成员函数、lambda表达式等)的通用方式。std::function 是一个模板类,它可以用来封装各种不同类型的函数或可调用对象,从而实现了一种统一的接口来进行函数调用。

使用的头文件

#include <functional>

 格式:

#include <functional>std::function<return_type(parameter_types)> func;
  • return_type:函数的返回类型。
  • parameter_types:函数的参数类型列表。
  • funcstd::function 类型的对象,可以用来存储和调用可调用对象。

示例:

#include <functional>
using namespace std;// 普通函数
void printHello() {std::cout << "Hello, ";
}void printWorld() {std::cout << "world!" << std::endl;
}int add(int a, int b) {return a + b;
}int main() {// 存储不同的函数std::vector<std::function<void()>> tasks;tasks.push_back(printHello);tasks.push_back(printWorld);for (const auto& task : tasks) {task(); // 调用存储的函数  会分别调用printHello和printWorld两个函数}// 存储普通函数std::function<int(int, int)> func = add;std::cout << func(3, 4) << std::endl;  // 输出 7// 存储lambda表达式std::function<int(int)> square = [](int x) -> int {return x * x;};std::cout << square(5) << std::endl;    // 输出 25return 0;
}

九、 右值引用

介绍 

在了解右值引用前,需要先知道什么是左值引用?

左值是一个表示数据的表达式(如变量名或解引用的指针),我们可以获取它的地址+可以对它赋值,左值可以出现赋值符号的左边,右值不能出现在赋值符号左边。定义时const修饰符后的左值,不能给他赋值,但是可以取它的地址。左值引用就是给左值的引用,给左值取别名。

左值引用的语法:&

比如:// 以下的p、b、c、*p都是左值
int* p = new int(0);
int b = 1;
const int c = 2;
// 以下几个是对上面左值的左值引用
int*& rp = p;
int& rb = b;
const int& rc = c;
int& pvalue = *p;

右值引用与左值引用就差不多相反:右值也是一个表示数据的表达式,如:字面常量、表达式返回值,函数返回值(这个不能是左值引用返回)等等,右值可以出现在赋值符号的右边,但是不能出现出现在赋值符号的左边,右值不能取地址,右值不可以直接修改。右值引用又可以分为两类:纯右值、将亡值。右值引用就是对右值的引用,给右值取别名。

所以:无论左值引用还是右值引用,都是给对象取别名。

右值引用语法:&&

// 以下几个都是常见的右值
10;  // 这种就是纯右值
x + y;  //这种就是将亡值,其实就是中间变量的过渡,过渡之后就消亡
fmin(x, y);  //也是将亡值// 以下几个都是对右值的右值引用
int&& rr1 = 10;
double&& rr2 = x + y;
double&& rr3 = fmin(x, y);
// 这里编译会报错:error C2106: “=”: 左操作数必须为左值
10 = 1;
x + y = 1;
fmin(x, y) = 1;

左、右值引用比较

  • 左值引用只能引用左值,不能引用右值。
  • 但是const左值引用既可引用左值,也可引用右值。
  • 左值引用必须进行初始化
int main()
{// 左值引用只能引用左值,不能引用右值。int a = 10;int& ra1 = a; // ra为a的别名//int& ra2 = 10; // 编译失败,因为10是右值// const左值引用既可引用左值,也可引用右值。const int& ra3 = 10;const int& ra4 = a;return 0;
}
  • 右值引用只能右值,不能引用左值。
  • 但是右值引用可以move以后的左值
  • 右值引用必须进行初始化
int main()
{// 右值引用只能右值,不能引用左值。int&& r1 = 10;// error C2440: “初始化”: 无法从“int”转换为“int &&”// message : 无法将左值绑定到右值引用int a = 10;int&& r2 = a;// 右值引用可以引用move以后的左值int&& r3 = std::move(a);return 0;
}

十、完美转发

模板中的&&不代表右值引用,而是万能引用,其既能接收左值又能接收右值。模板的万能引用只是提供了能够接收同时接收左值引用和右值引用的能力,但是引用类型的唯一作用就是限制了接收的类型,后续使用中都退化成了左值,我们希望能够在传递过程中保持它的左值或者右值的属性, 这就需要用到完美转发。它解决了在函数模板中传递参数时丢失信息的问题, 完美转发使得函数模板能够将参数原封不动地传递给其他函数,包括其参数类型和值类型,同时保留参数的左值或右值属性。

std::forward()   // 完美转发在传参的过程中保留对象原生类型属性
void Fun(int &x){ cout << "左值引用" << endl; }void Fun(const int &x){ cout << "const 左值引用" << endl; }void Fun(int &&x){ cout << "右值引用" << endl; }void Fun(const int &&x){ cout << "const 右值引用" << endl; }// std::forward<T>(t)在传参的过程中保持了t的原生类型属性。
template<typename T>
void PerfectForward(T&& t)
{Fun(std::forward<T>(t));
}int main()
{PerfectForward(10); // 右值int a;PerfectForward(a); // 左值PerfectForward(std::move(a)); // 右值const int b = 8;PerfectForward(b); // const 左值PerfectForward(std::move(b)); // const 右值return 0;
}

 

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/103946.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

linux部署clickhouse(单机)

一、下载安装 1.1、下载地址 阿里巴巴开源镜像站-OPSX镜像站-阿里云开发者社区阿里巴巴开源镜像站&#xff0c;免费提供Linux镜像下载服务&#xff0c;拥有Ubuntu、CentOS、Deepin、MongoDB、Apache、Maven、Composer等多种开源软件镜像源&#xff0c;此外还提供域名解析DNS、…

支持向量机SVM原理

目录 支持向量机SVM原理 SVM原理 从线性分类器说起 SVM的目标是最大化分类间隔 转化为对偶问题求解 支持向量机SVM原理 【数之道】支持向量机SVM是什么&#xff0c;八分钟直觉理解其本质_哔哩哔哩_bilibili SVM是由Vapnik等人于1995年提出的&#xff0c;在之后的20多年里…

【数据结构OJ题】链表的回文结构

原题链接&#xff1a;https://www.nowcoder.com/practice/d281619e4b3e4a60a2cc66ea32855bfa?tpId49&&tqId29370&rp1&ru/activity/oj&qru/ta/2016test/question-ranking 目录 1. 题目描述 2. 思路分析 3. 代码实现 1. 题目描述 2. 思路分析 在做这道…

嵌入式Linux开发实操(十二):PWM接口开发

# 前言 使用pwm实现LED点灯,可以说是嵌入式系统的一个基本案例。那么嵌入式linux系统下又如何实现pwm点led灯呢? # PWM在嵌入式linux下的操作指令 实际使用效果如下,可以通过shell指令将开发板对应的LED灯点亮。 点亮3个LED,则分别使用pwm1、pwm2和pwm3。 # PWM引脚的硬…

React前端开发架构:构建现代响应式用户界面

在当今的Web应用开发中&#xff0c;React已经成为最受欢迎的前端框架之一。它的出色性能、灵活性和组件化开发模式&#xff0c;使得它成为构建现代响应式用户界面的理想选择。在这篇文章中&#xff0c;我们将探讨React前端开发架构的核心概念和最佳实践&#xff0c;以帮助您构建…

猜数游戏-Rust版

cargo new guessing_game 创建项目 输入任意内容&#xff0c;并打印出来 main.rs: use std::io; // 像String这些类型都在预先导入的prelude里&#xff0c;如果要使用的不在prelude里&#xff0c;则需要显式导入fn main() { println!("猜数"); println!("…

Spring 自动装配机制详解

文章目录 一、手动装配二、自动装配1. XML 方式2. 注解方式 一、手动装配 首先知道 Spring 装配是干了件啥事&#xff1f;我的理解&#xff0c;它就是用来解决 bean 之间依赖关系的一个手段。 比如说我这里有一个 People 类和一个 Dog 类&#xff0c;People 依赖 Dog&#xff…

11.redis持久化

1.redis持久化 Redis的所有数据都是保存在内存中&#xff0c;因此redis重启后数据就丢失了&#xff0c;所以需要不定期的通过异步方式保存到磁盘上(这称为“半持久化模式”)&#xff1b;或者把每一次数据变化都写入到一个append only file(aof)里面(这称为“全持久化模式”)。 …

基于微信小程序的物流管理系统3txar

在此基础上&#xff0c;结合现有物流管理体系的特点&#xff0c;运用新技术&#xff0c;构建了以 springboot为基础的物流信息化管理体系。首先&#xff0c;以需求为依据&#xff0c;对目前传统物流管理基础业务进行了较为详尽的了解和分析。根据需求分析结果进行了系统的设计&…

类的加载过程二:Linking

1、验证&#xff08;Verify&#xff09; 目的在于确保Class文件的字节流中包含信息符合当前虚拟机要求&#xff0c;保证被加载类的正确性&#xff0c;不会危害虚拟机自身安全。主要包括四种验证&#xff0c;文件格式验证&#xff0c;元数据验证&#xff0c;字节码验证&#xff…

无涯教程-PHP - XML GET

XML Get已用于从xml文件获取节点值。以下示例显示了如何从xml获取数据。 Note.xml 是xml文件&#xff0c;可以通过php文件访问。 <SUBJECT><COURSE>Android</COURSE><COUNTRY>India</COUNTRY><COMPANY>LearnFk</COMPANY><PRICE…

全流程R语言Meta分析核心技术

​Meta分析是针对某一科研问题&#xff0c;根据明确的搜索策略、选择筛选文献标准、采用严格的评价方法&#xff0c;对来源不同的研究成果进行收集、合并及定量统计分析的方法&#xff0c;最早出现于“循证医学”&#xff0c;现已广泛应用于农林生态&#xff0c;资源环境等方面…

musl libc ldso 动态加载研究笔记:01

前言 musl 是一个轻量级的标准C库&#xff0c;建立在系统调用之上&#xff0c;可以认为是【用户态】的C 库&#xff0c;与 glibc 或者 uClibc 属于同一类。 基于 musl 的 gcc 工具链包括交叉编译工具链&#xff0c;可以用于编译 Linux 或者其他的操作系统&#xff0c;如当前 L…

08.SpringBoot请求相应

文章目录 1 请求1.1 Postman1.2 简单参数1.2.1 原始方式1.2.2 SpringBoot方式1.2.3 参数名不一致 1.3 实体参数1.3.1 简单实体对象1.3.2 复杂实体对象 1.4 数组集合参数1.4.1 数组1.4.2 集合 1.5 日期参数1.6 JSON参数1.7 路径参数 2 响应2.1 ResponseBody注解2.2 统一响应结果…

计算机终端核心安全配置规范

声明 本文是学习 政务计算机终端核心配置规范. 而整理的学习笔记,分享出来希望更多人受益,如果存在侵权请及时联系我们 范围 本标准提出了政务计算机终端核心配置的基本概念和要求&#xff0c;规定了核心配置的自动化实现方法&#xff0c;规范了核心配置实施流程。 本标准适…

1139. 最大的以 1 为边界的正方形;2087. 网格图中机器人回家的最小代价;1145. 二叉树着色游戏

1139. 最大的以 1 为边界的正方形 核心思想&#xff1a;枚举正方向的右下角坐标&#xff08;i&#xff0c;j&#xff09;&#xff0c;然后你只需要判断四条边的连续一的最小个数即可&#xff0c;这里是边求连续一的个数同时求解结果。 087. 网格图中机器人回家的最小代价 核心…

拼多多商品详情API接入站点,实时数据json格式示例

作为国内最大的电商平台之一&#xff0c;拼多多数据采集具有多个维度。 有人需要采集商品信息&#xff0c;包括品类、品牌、产品名、价格、销量等字段&#xff0c;以了解商品销售状况、热门商品属性&#xff0c;进行市场扩大和重要决策&#xff1b; 商品数据&#xff1a;拼…

stm32之16.外设定时器——TIM3

----------- 源码 void tim3_init(void) {NVIC_InitTypeDef NVIC_InitStructure;TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;//使能TIM3的硬件时钟RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);//配置TIM3的定时时间TIM_TimeBaseStructure.TIM_Period 10000-1…

Websocket原理和实践

一、概述 1.websocket是什么&#xff1f; WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket使得客户端和服务器之间的数据交换变得更加简单&#xff0c;允许服务端主动向客户端推送数据。在WebSocket API中&#xff0c;浏览器和服务器只需要完成一次握手&…