嵌入式c++软件开发笔记第五讲

简介: 嵌入式c++软件开发笔记第五讲

类型推导

意义

理解编译器类型推导规则有利于高效的使用C++

从明显或冗余的类型拼写中解放出来。这样使得c++软件更具有适配性

类型推导的场景

调用模板函数

使用auto

decltype表达式

神秘的decltype(auto)

万能引用(未定义引用)

C++11提出万能引用的原因–能够接受左值和右值

定义:templatevoid func(T&& param){}

万能引用语境:

必须是函数模板

必须发生了模板类型推断

辨识万能引用

类型参数T必须紧跟&&

void MyFuncTemp2(std::vector&& v1)//并不是万能引用

类模板里的成员函数形参为T&&,并不是万能引用

剥夺万能引用

const属性会剥夺万能引用:templatevoid func(const T&& param){}

总结

万能引用并不是一种新的类型,只存在函数模板中;

函数模板的类型推导原则

ParamType是一个指针或引用 ,但不是万能引用

若实参类型expr是引用,则引用类型会被忽略,保持cv属性;T不会被推导为引用类型

template
void func(T& param);
int x = 27;
const int cx = x;
const int& rx = x;

f(x); // x为int,因此param的类型为int&而T为int

f(cx); // cx为const int,因此param的类型为const int&而T为const int

f(rx); // rx为const int&,忽略引用后为const int,因此param的类型为const int&而T为const int

ParamType是一个万能引用

如果expr是一个左值,T和ParamType都被推导成左值引用

如果expr是一个右值,T是由expr的类型决定、ParamType被推导成右值引用。

引用折叠、转发、完美转发

引用折叠

折叠场景:ParamType是一个万能引用,实参expr是一个左引用或者右引用

折叠规则

所有右值折叠到右值引用上仍然是一个右值引用。(A&& && 变成 A&&)

所有的其他引用类型之间的折叠都将变成左值引用。 (只要有左值引用就是左值引用)(A& & 变成 A&; A& && 变成 A&; A&& & 变成 A&)

转发

把函数模板将收到的参数及参数类型不变的传递给其他函数,叫做转发

若函数模板里的调用的函数形参为右值引用,该如何传递参数?

方法1:使用std::move

方法2:使用完美转发std::forward

template

void func(T&& param);

int x = 27;

const int& rx = x;

f(x); // 由于x为左值,因此T和param都被推导为int& – ①

f(rx); // 由于rx为左值,因此T和param都被推导为const int& – ①

f(27); // 由于27是右值,其类型为int,与T&&进行对比得到T类型为int而param类型为int&& – ②

ParamType不是指针或引用(值传递)

如果expr是一个引用,则忽略引用部分

如果expr是个const或者volatile,也要忽略掉const或volatile

template

void func(T param);

int x = 27;

const int cx = x;

const int& rx = x;

f(x); // x类型为int,因此T和param的类型也为int

f(cx); // cx类型为const int,忽略掉const,因此T和param的类型仍为int

f(rx); // rx类型为const int&,忽略掉const和&,因此T和param的类型还是int

模板类型推导时,数组和函数参数会退化为指针。

void someFunc(int, double);

template

void func1(T param);

template

void func2(T& param);

func1(someFunc); // param被推导为函数指针,类型为void(*)(int, double)

func2(someFunc); // param被推导为函数指针,类型为void(&)(int, double)

auto类型推导

原则:

当不声明为指针或引用时,auto 的推导结果和初始化表达式抛弃引用和 cv(const 和 volatile 限定符的统称) 限定符后类型一致。

指针:auto 的推导结果将保持初始化表达式的 cv 属性

引用:忽略引用,忽略cv属性

auto限制

auto不能用于函数参数

auto不能用于非静态成员变量

auto无法定义数组

auto无法推导出模板参数

特殊推导规则

推导的类型是std::initializer_list

auto x = {1, 2, 3} // x的类型为std::initializer_list

decltype

作用

decltype关键字返回给定变量或表达式的类型,并且可以用这个返回的类型来声明变量。

注:decltype不会真正计算表达式的值或者调用函数

推导规则

decltype(类型)

变量–变量的类型(引用属性和const属性保留)

引用、指针–指针推断指针,引用推断引用—解指针decltype推断出引用;

表达式–注:表达式如果可以做左值,则返回左值引用,

函数—返回值的类型

主要用途

问题–链接

解决办法–链接

神秘的decltype(auto)-C++14

应用于函数返回值类型推导

auto VS decltype

1.auto忽略顶层const,decltype保留顶层const;

2.引用操作,auto推断出原有类型,decltype推断出引用;

3.解引用操作,auto推断出原有类型,decltype推断出引用;

4.auto推断时会实际执行,decltype不会执行,只做分析

可调用对象:通过函数调用符操作的对象称之为可调用对象

可调用对象

普通函数

类成员函数/静态函数

函数指针

类的成员函数指针

可转换为函数指针的类

函数对象(仿函数)

lambda表达式

本质

实际是匿名函数,能够捕获一定范围的变量;

与普通函数不同,可以在函数内部定义;

格式及说明

捕获列表->返回类型{函数体};

说明:

返回类型可以由编译器推导出,所以可以省略;

参数可以有默认值

C++14的lambda形参可以使用auto声明

没有参数时,()也可以省略

lambda调用方法和普通函数一样

捕获列表

作用:捕获外部作用域一定范围的变量

[ ]:不捕获任何变量,但不包括静态局部变量,lambda表达式可以直接使用

[&]:捕获外部作用域中所有变量,并作为引用在函数体里使用;

[=]:捕获外部作用域中所有变量,并作为副本(传值)在函数体里使用;但不能修改外部作用域变量的值;

[this]:一般用于类中,捕获当前类中的this指针,让lambda表达式与成员函数具有一样的权限,访问成员变量;(若使用[&]/[=],默认捕获this)

[变量名]:捕获列表中列出的变量,多个变量用,分隔

[=,&变量名]:按值捕获外部变量,按引用捕获指定变量

[&,变量名]:按引用捕获外部变量,按值捕获指定变量

lambda表达式延迟调用注意事项

切记不能发生“引用悬挂”–即按引用捕获会导致包含指向局部变量的引用,当lambda离开局部作用域时,会导致引用的那个变量被释放,lambda里的那个引用发生"悬挂"

this陷阱–定义局部变量

void print()
{
//int x = m_x;
//int y = m_y;
s_print_history.push_back(x1 = m_x, x2 = m_y{std::cout << “(X:” << x1<< “,Y:” << x2<<")" << std::endl;});
std::cout << “(X:” << m_x << “,Y:” << m_y <<")" << std::endl;
}

尽量避免使用复杂的lambda

lambda里避免有全局变量或静态变量或者比当前类生命周期更长的变量

std::function包装器

本质–

一个类模板,用于包装可调用对象。可以容纳除了类成员(函数)指针之外的所有可调用对象

作用

可以用统一的方式来保存或传递可调用对象。

意义

实现了一套类型消除机制,可用统一的方式处理不同类型的可调用对象。

std::function进一步深化以数据为中心(封装)的面向对象思想(连函数都对象化了)

案例–#include

链接

std::bind适配器

本质

std::bind也是一个函数模板,返回值是一个仿函数,也是可调用对象

作用

将多元的可调用对象与其参数一起绑定成一个仿函数对象。

将多元(设参数个数为n)的可调用对象转成一元或(n-1)元的可调用对象,即只绑定部分参数。

bind可以绑定的对象

普通函数(functions)

函数对象(仿函数,function objects)

类的成员函数(member functions。注意:_1必须是某个对象的地址)

类的数据成员(data members注意:_1必须是某个对象的地址)

使用案例

链接

注意:bind预先绑定的参数需要传具体的变量或值进去,对于预先绑定的参数是按值传递的。

可变参数

initializer_list 列表初始化

本质:用于表示某种特定类型的值的数组,和vector一样,initializer_list也是一种模板类型。

使用:使用一个花括号来初始化变量e.g.:std::vectora{1,2,3,4,5};

初始化区别:int c = 3.3; //这里会进行默认类型转换

int b = {3.3}; //这里编译器会给出警告(也有可能是错误)

优点:代码更加健壮

C++11允许构造函数和其他函数把初始化列表当做参数

#include
#include
class MyNumber
{
public:
MyNumber(const std::initializer_list &v) {
for (auto itm : v) {
mVec.push_back(itm);
}
}
void print() {
for (auto itm : mVec) {
    std::cout << itm << " ";
}

4

}
private:
std::vector mVec;
};
int main()
{
MyNumber m = { 1, 2, 3, 4 };
m.print(); // 1 2 3
return 0;

}

萃取技术

作用:提取一些信息出来

萃取接口:https://en.cppreference.com/w/cpp/types

案例–链接

C++ STL/boost

STL概述

STL六大组件

Container(容器) 各种基本数据结构

Iterator(迭代器) 连接containers和algorithms

Algorithm(算法) 各种基本算法如sort、search…等

Adapter(适配器) 可改变containers、Iterators或Function object接口的一种组件

Function object(函数对象)

Allocator(分配器)

STL六大组件关系

容器

分类

顺序容器(sequence Containers)

放进去在哪里,这个元素就排在哪里。比如array,vector,deque,list,forward_list;

关联容器(Associative Containers)

树,哈希表,元素是 键/值 对 ,特别适合做查找。你能控制插入的内容,但一般来讲你不能控制插入的位置。set,multiset,map,multimap;

无序(散列)容器(Unordered Containers)

哈希表;c++11里推出:元素的位置不重要,重要的是这个元素是否在这个容器里边。

unordered_set,unordered_multiset,unordered_map,unordered_multimap;
vector
list
forward_list(c++11)
deque(双端) == vector(尾部)
priority_queue
set/multiset
map/multimap
(无序)unordered_set,unordered_multiset、unordered_map,unordered_multimap

容器选择原则

如果你需要高效的随机存取,而不在乎插入和删除的效率,使用vector

如果你需要大量的插入和删除,而不关心随即存取,则应使用list

如果你需要随即存取,而且关心两端数据的插入和删除,则应使用deque

如果你要存储一个数据字典,并要求方便地根据key找value,那么map是较好的选择

如果你要查找一个元素是否在某集合内存中,则使用set存储这个集合比较好

容器的函数操作请去网站查询,不做过多分析

https://zh.cppreference.com/w/%E9%A6%96%E9%A1%B5

分配器

概述

和容器紧密关联,一起使用

作用:内存分配器,扮演内存池的角色,通过大量减少对malloc()的调用,来节省内存,甚至还有一定的分配效率的提高

allocator这个c++标准库提供的缺省的内存分配器压根就没有采用内存池的工作机制。估计底层原封不动的调用了malloc();

分配器的使用

allocator分配器,是个类模板,我们写代码时极少会直接用到这个allocator这种容器的分配器;

迭代器

迭代器是指针的泛化,它允许程序员以相同的方式处理不同的数据结构(容器)

适配器

作用:转接头

容器适配器

stack:堆栈,是属于阉割版的deque;queue:队列,是属于阉割版的deque;

算法适配器(函数适配器)

bind1st,bind2nd;c++11,名字被修改为bind:

迭代器适配器

reverse_iterator

算法

算法概述

STL的算法是全局函数

头文件#include #include

算法的区间

所有算法处理的都是半开区间[begin, end)

算法词法

find_if(按照某个条件来查找)

函数对象/仿函数(functors)

常用算法

遍历算法

for_each

for_each: 用指定函数依次对指定范围内所有元素进行迭代访问。该函数不得修改序列中的元素。

void show(const int &iItem)
{
cout << iItem;
}
main()
{
int iArray[] = {0,1,2,3,4};
vector vecInt(iArray,iArray+sizeof(iArray)/sizeof(iArray[0]));
for_each(vecInt.begin(), vecInt.end(), show);
//结果打印出0 1 2 3 4
}
transform
查找算法
adjacent_find
binary_search
count
count_if
假设vector vecIntA,vecIntA包含1,3,5,7,9元素
//先定义比较函数
bool GreaterThree(int iNum)
{
if(iNum>=3)
{
return true;
}
else
{
return false;
}
}
int iCount = count_if(vecIntA.begin(), vecIntA.end(), GreaterThree);
//此时iCount == 4
find
find_if

find_if: 使用输入的函数代替等于操作符执行find。返回被找到的元素的迭代器。

假设vector vecIntA,vecIntA包含1,3,5,3,9元素

vector::it = find_if(vecInt.begin(),vecInt.end(),GreaterThree);

此时 *it==3, *(it+1)==5, *(it+2)==3, *(it+3)==9

排序算法

merge

sort

sort: 以默认升序的方式重新排列指定范围内的元素。若要改排序规则,可以输入比较函数。

//学生类
Class CStudent:
{
public:
CStudent(int iID, string strName)
{
m_iID=iID;
m_strName=strName;
}
public:
int m_iID;
string m_strName;
}
//学号比较函数
bool Compare(const CStudent &stuA,const CStudent &stuB)
{
return (stuA.m_iID<strB.m_iID);
}
void main()
{
vector vecStu;
vecStu.push_back(CStudent(2,“老二”));
vecStu.push_back(CStudent(1,“老大”));
vecStu.push_back(CStudent(3,“老三”));
vecStu.push_back(CStudent(4,“老四”));
sort(vecStu.begin(),vecStu.end(),Compare);

// 此时,vecStu容器包含了按顺序的"老大对象",“老二对象”,“老三对象”,“老四对象”

}

random_shuffle

reverse

拷贝和替换算法

copy

replace

replace_if

replace_if : 将指定范围内所有操作结果为true的元素用新值替换。

用法举例:

replace_if(vecIntA.begin(),vecIntA.end(),GreaterThree,newVal)

其中 vecIntA是用vector声明的容器

GreaterThree 函数的原型是 bool GreaterThree(int iNum)

//把大于等于3的元素替换成8

vector vecIntA;

vecIntA.push_back(1);

vecIntA.push_back(3);

vecIntA.push_back(5);

vecIntA.push_back(3);

vecIntA.push_back(9);

replace_if(vecIntA.begin(), vecIntA.end(), GreaterThree, 8);        // GreaterThree的定义在上面。

swap

算数和生成算法

accumulate

fill

集合算法

学习网站

https://en.cppreference.com/w/

http://www.cplusplus.com/

目录
相关文章
|
28天前
|
开发框架 Linux C语言
C、C++、boost、Qt在嵌入式系统开发中的使用
C、C++、boost、Qt在嵌入式系统开发中的使用
32 1
|
28天前
|
算法 Linux 程序员
嵌入式工程师以及C++程序员到公司就业需要掌握那些技术?
嵌入式工程师以及C++程序员到公司就业需要掌握那些技术?
|
1月前
|
Java 编译器 C++
C++入门指南:类和对象总结笔记(下)
C++入门指南:类和对象总结笔记(下)
29 0
|
1月前
|
存储 编译器 C语言
C++入门: 类和对象笔记总结(上)
C++入门: 类和对象笔记总结(上)
34 0
|
1月前
|
数据处理 C++ UED
如何作为一个嵌入式软件工程师博主获得铁粉:C/C++ 技术分享之道
如何作为一个嵌入式软件工程师博主获得铁粉:C/C++ 技术分享之道
47 0
|
1月前
|
存储 编译器 程序员
嵌入式系统中C++基础知识精髓
嵌入式系统中C++基础知识精髓
42 0
|
22小时前
|
Linux 程序员 图形学
C++语言在现代软件开发中的应用与实践
C++语言在现代软件开发中的应用与实践
7 2
|
2天前
|
安全 Java 程序员
【C++笔记】从零开始认识继承
在编程中,继承是C++的核心特性,它允许类复用和扩展已有功能。继承自一个基类的派生类可以拥有基类的属性和方法,同时添加自己的特性。继承的起源是为了解决代码重复,提高模块化和可维护性。继承关系中的类形成层次结构,基类定义共性,派生类则根据需求添加特有功能。在继承时,需要注意成员函数的隐藏、作用域以及默认成员函数(的处理。此外,继承不支持友元关系的继承,静态成员在整个继承体系中是唯一的。虽然多继承和菱形继承可以提供复杂的设计,但它们可能导致二义性、数据冗余和性能问题,因此在实际编程中应谨慎使用。
5 1
【C++笔记】从零开始认识继承
|
1月前
|
编译器 C语言 C++
C++入门指南:类和对象总结笔记(中)
C++入门指南:类和对象总结笔记(中)
49 0
|
1月前
|
安全 算法 C++
了解C++ 软件开发中的鲁棒性
了解C++ 软件开发中的鲁棒性
38 0