欢迎关注我的公众号 [极智视界],获取我的更多经验分享
大家好,我是极智视界,本文介绍一下 从数组求和来看C++的发展。
邀您加入我的知识星球「极智视界」,星球内有超多好玩的项目实战源码下载,链接:t.zsxq.com/0aiNxERDq
从一个案例来看 C++ 的发展。
定义一个数组 {4, 2, 6, 7},求数组里所有数的和,并将之打印
如果是 python 的话,实现非常简洁 (但你要清楚的是,代码简洁并不代表程序效率高):
- python
arr = [4, 2, 6, 7]
print(sum(arr))
那用 C++ 该如何实现呢。大家知道 C++ 是由 C语言发展而来,所以先来看一下 C语言的实现。
- 古代:C语言
#include <stdlib.h>
#include <stdio.h>
int main(){
size_t nums = 4;
int *arr = (int *)malloc(nums * sizeof(int));
arr[0] = 4;
arr[1] = 2;
arr[2] = 6;
arr[3] = 7;
int sum = 0;
for(size_t i = 0; i < nums; i++){
sum += arr[i];
}
printf("%d\n", sum);
free(v);
return 0;
}
C语言看起来明显要比 python 麻烦不少吧,多了 malloc内存管理、指针逐个赋值、循环加和的过程。
再来看 C++98。
- 近代:C++98 引入了 STL 容器库
#include <iostream>
#include <vector>
int main(){
std::vector<int> arr(4);
arr[0] = 4;
arr[1] = 2;
arr[2] = 6;
arr[3] = 7;
int sum = 0;
for(size_t i = 0; i < arr.size(); i++){
sum += arr[i];
}
std::cout << sum << std::endl;
return 0;
}
C++98 引入了 vector 这个模板类,自带内存管理,所以相比C语言不需要自己进行手动管理内存了。输出函数这里采用了运算符重载的特性,不用像C语言 printf 那样需要显式指定是要用 %d 还是 %f 了,方便挺多。不过在数组赋值那一块还是和C语言一样的。
接着来看 C++11。
- 近现代:C++11 引入了 {} 初始化表达式
#include <iostream>
#include <vector>
int main(){
std::vector<int> arr = {4, 2, 6, 7};
int sum = 0;
for(int i = 0; i < arr.size(); i++){
sum += arr[i];
}
std::cout << sum << std::endl;
return 0;
}
这样,通过花括号就把 C++98 中对数组的初始化给优化地更加简洁了。
另外,C++11 还引入了一个很方便的、基于范围的 for循环,来看看通过基于范围的 for循环怎么来进一步做简化。
- 近现代:C++11 引入了 range-based for-loop
#include <iostream>
#include <vector>
int main(){
std::vector<int> arr = {4, 2, 6, 7};
int sum = 0;
for(int a: arr){
sum += a;
}
std::cout << sum << std::endl;
return 0;
}
很明显,for 循环变得更加简洁了。
接着通过 C++11 的 for_each 还可以继续改造。
- 近现代:C++11 引入了 for_each 算法模板
#include <iostream>
#include <vector>
#include <algorithm>
int sum = 0;
void func(int a){
sum += a;
}
int main(){
std::vector<int> arr = {4, 2, 6, 7};
std::for_each(arr.begin(), arr.end(), func);
std::cout << sum << std::endl;
return 0;
}
for_each 是一个算法模版,可以套用到任何支持迭代器的容器。而 for_each 有个不太方便的地方,就是它的 func 是一个外部函数,需要去声明一个全局的 sum 就比较麻烦。对此,C++11 又引入了 lambda 表达式,这样就可以把 func 写在局部了,这样 sum 就可以为一个局部变量。
- 近现代:C++11 引入了 lambda 表达式
#include <iostream>
#include <vector>
#include <algorithm>
int main(){
std::vector<int> arr = {4, 2, 6, 7};
int sum = 0;
std::for_each(arr.begin(), arr.end(), [&] (int a){
sum += a;
});
std::cout << sum << std::endl;
return 0;
}
接着来到了 C++14,在 C++14 中允许 lambda 使用 auto 自动推断类型了。
- 现代:C++14 的 lambda 允许用 auto 自动推断类型
#include <iostream>
#include <vector>
#include <algorithm>
int main(){
std::vector<int> arr = {4, 2, 6, 7};
int sum = 0;
std::for_each(arr.begin(), arr.end(), [&] (auto a){
sum += a;
});
std::cout << sum << std::endl;
return 0;
}
使用了 auto 后,好处就在于 既可以用于 int 类型,也可以用于 float 类型等,要是哪天 arr 的数据类型变化了,auto 也不需要改就能够适应,这样就更加通用了。
继续,来到了 C++17,加入了编译期参数推断,什么意思呢,来看。
- 当代:C++17 CTAD / compile-time argument deduction / 编译期参数推断
#include <iostream>
#include <vector>
#include <algorithm>
int main(){
std::vector arr = {4, 2, 6, 7};
int sum = 0;
std::for_each(arr.begin(), arr.end(), [&] (auto a){
sum += a;
});
std::cout << sum << std::endl;
return 0;
}
看 arr 的声明,在 vector 后可以省略 的类型定义了,因为右值都是整型,所以会自动推断为整型。
另外,在 C++17 里边还引入了一些常用的数值算法,放在 这个头里面,比如这里可以用 reduce 归约来求和。
- 当代:C++17 引入了常用的数值算法
#include <iostream>
#include <vector>
#include <numeric>
int main(){
std::vector arr = {4, 2, 6, 7};
int sum = std::reduce(arr.begin(), arr.end());
std::cout << sum << std::endl;
return 0;
}
当然,reduce 不仅可以用于加法,还可以用于乘法,如果咱们是要求 arr 中每个数的乘积,使用 reduce 的话可以是这样:
#include <iostream>
#include <vector>
#include <numeric>
int main(){
std::vector arr = {4, 2, 6, 7};
int sum = std::reduce(arr.begin(), arr.end(), 1, std::plus{});
std::cout << sum << std::endl;
return 0;
}
更加拓展一点,reduce 内的函数还可以自定义,而自定义函数可以用 lambda 表达式来实现,比如还是要求 arr 中每个数的和,使用 reduce 内自定义函数,可以是这样:
#include <iostream>
#include <vector>
#include <numeric>
int main(){
std::vector arr = {4, 2, 6, 7};
int sum = std::reduce(arr.begin(), arr.end(), 0, [] (int x, int y){
return x + y;
});
std::cout << sum << std::endl;
return 0;
}
继续进化,在 C++20 中引入了区间 ranges,来看看 ranges 将带来什么样的实现变化。
- 未来:C++20 引入区间 ranges
#include <iostream>
#include <vector>
#include <numeric>
#include <ranges>
#include <cmath>
int main(){
std::vector arr = {4, 2, 6, 7, 0, -2, -5};
int sum = 0;
for(auto &&a: arr
| std::views::filter([] (auto &&x) {return x >= 0;})){
sum += a;
}
std::cout << sum << std::endl;
return 0;
}
通过 ranges 可以有一个筛选的功能在里面,甚至还可以通过 ranges 对筛选出来的元素再直接做一些操作再抛出来用,比如如果想要对以上数组 arr 中大于等于 0 的元素开个根号再求和,通过 ranges 可以这样写:
#include <iostream>
#include <vector>
#include <numeric>
#include <ranges>
#include <cmath>
int main(){
std::vector arr = {4, 2, 6, 7, 0, -2, -5};
int sum = 0;
for(auto &&a: arr
| std::views::filter([] (auto &&x) {return x >= 0;})
| std::views::transform([] (auto &&x) {return sqrtf(x);})){
sum += a;
}
std::cout << sum << std::endl;
return 0;
}
另外,不得不说 C++ 一直也在学习一些 python 的特性,在往 python 靠拢,本来 C++ 中是通过 include 来包含头文件的,而在 C++ 20 中引入了模块 module 的概念。
- 未来:C++20 引入模块 module
import <iostream>;
import <vector>;
import <numeric>;
import <ranges>;
import <cmath>;
int main(){
std::vector arr = {4, 2, 6, 7, 0, -2, -5};
int sum = 0;
for(auto &&a: arr
| std::views::filter([] (auto &&x) {return x >= 0;})){
sum += a;
}
std::cout << sum << std::endl;
return 0;
}
看到这里,是不是会让你有一种错觉:我是在写 C++ 吗?[捂脸]
在 C++20 中,还对 auto 进行了再次的升级,允许函数参数为 auto 了。
- 未来:C++20 允许函数参数为自动推断 auto
import <iostream>;
import <vector>;
import <numeric>;
import <ranges>;
import <cmath>;
int func(auto &&a){
int sum = 0;
for(auto &&a: arr
| std::views::filter([] (auto &&x) {return x >= 0;})){
sum += a;
}
return sum;
}
int main(){
std::vector arr = {4, 2, 6, 7, 0, -2, -5};
int sum = func(arr);
std::cout << sum << std::endl;
return 0;
}
这里,是不是感觉会有一点熟悉呢?func 是不是看起来是和模板函数 template 的作用比较像。
另外在 20 里面还引入了一些多线程的特性,比如 std::future、协程之类的。虽然 C++20 挺香的,但是还没有普遍落地,甚至连 CMake 对 C++20 modules 的支持也不够好,所以暂时还是可以把 C++20 作为拓展来进行学习,项目中还是用 C++17 及以下的较多。
好了,以上分享了 从数组求和来看 C++ 的发展,希望我的分享能对你的学习有一点帮助。
【公众号传送】
畅享人工智能的科技魅力,让好玩的AI项目不难玩。邀请您加入我的知识星球, 星球内我精心整备了大量好玩的AI项目,皆以工程源码形式开放使用,涵盖人脸、检测、分割、多模态、AIGC、自动驾驶、工业等。不敢说会对你学习有所帮助,但一定非常好玩,并持续更新更加有趣的项目。 t.zsxq.com/0aiNxERDq