深圳做英文网站公司/廊坊网站
为什么使用 C++
前面几篇文章也写了一些代码,相信大家也有了一些感受,C++是一门极其自由、功能非常强大、语法又非常复杂的编程语言。那为什么还要用C++,在机器学习当中,C++如此之流行,包括我们知道的TensorFlow,Coffee,还有腾讯出的NCNN这个深度学习框架,他们都是使用C++编写的。这里面最大的考虑其实就是尽可能发挥硬件的性能。C++ 编写出来的程序执行效率远远高于Java、C#,更胜Python,其实大家要知道,Python这门语言执行速度往往都是在排行榜里面垫底的。跟JavaScript还是有一拼的。
如何权衡 C++ 和 Python
那可能有人要考虑。那我到底要不要去权衡Python和C++的使用呢,从我的观念上来看这个问题是这个样子的。很多之前从事数据科学或者研究领域的研究人员他们原来可能会很多的去使用MATLAB,那么Python提供了很简单的编程语法。也是非常灵活、非常流行的一门胶水语言,所以他们寻找到了一种很好的替代方案。在编程世界当中,使用Python去做。但是呢,在实际的工业产品当中,使用Python,据我了解还是比较少的。因为考虑到性能、产出种种方面的考虑。还有产品质量方面的考虑,往往呢,会使用纯C++的程序去进行编写。但又考虑到说,那我只提供C++,肯定是不够的吗,让所有人去学习C++也不现实,所以呢,往往会在C++程序、机器学习或者深度学习框架的基础上,封装Python的编程接口。这样通过Python的调用,来调用C++代码,来执行一些业务逻辑。所以大概的结构是这个样子的。
另一方面,大家应该已经很熟悉咱们前面提到的 numpy了吧。Python里面常用的矩阵运算的Library。大家注意了numpy是在Python里面使用,但是numpy也是基于C++编写的。所以,C++在机器学习领域实在是上帝赠送的礼物,也非常的重要。
指针
今天咱们去来看一看C++里面的指针。
指针的解释
*:星号,指针里面的一种类型表示形式。指针也有不同的解释方式,从我的理解,我希望大家可以把指针当作一种特定的类型。跟其他的int、float、double是一个概念。都把它认为是一种类型。指针是一种类型。大家注意这个概念。从这个角度理解指针这件事更好理解,否则的话这件是还挺难讲的。
&:这个与符号在这里不是与符号,在某些情况下有特定的含义,C++里叫取地址符。
今天咱们就来讲讲这个指针和取地址符怎么用,又是怎么回事。
指针的写法
看上图,后两种写法也可以,但是我没有用,因为我们把指针看作一种类型:类型为整数的指针。
指针在内存当中的样子
a本身是一个变量,会指向对象真正的首地址。这就是一个最基本的指针的定义。
指针到底有什么用
这个问题非常关键,因为在C++当中为了保证效率,我们常常去操纵内存。为什么今天在咱们会讲指针这件事呢。因为在C++当中一个非常重要的概念是我们经常会去直接操作内存当中的数据。而指针就是非常好的方法。
先看类型:
a是类型为A的指针类型;对应如下面的int型,浮点型。
再看值:
而b对应变量的值为5,c对应6,d对应1.1,a对应的同样是一个变量,它在32bit机器里面(在64bit情况下可能不一样,我们讲的是通常的一个概念),a存的是一个内存地址。这个地址对应heap里面该对象的值。
所以大家可以看到,a里面是内存地址,它指向的是堆里的一块内存区域。通常来说是指向堆里面的东西,那我能不能指向栈里面的东西,当然可以。C++非常自由,什么都可以做。
这样我们把A*
看成一个整体,a就是一个指针类型,这样好理解一些。通过new(C++中的语法)分配一块内存,类型是A。而大小呢,就如图示这么大。
指针代码演示
指针的定义与取地址
// 1. Basic pointer usageint32_t* a = new int32_t(5);int32_t b = *a;// 取地址符int32_t* c = &*a; // &*a <==> a// c==aif (c == a) {std::cout<< "they are identical" << std::endl;}
// Prints they are identical
指针的初始化
// 2. 指针是有类型的int32_t* d = nullptr; // NULL // 不指向内存当中的任何区域/* C++11 里面用nullptr,之前用NULL,大家感兴趣可以自己查查nullptr和NUll的区别 *//*在C++当中,凡事都要初始化why?C++ 标准委员会 <- STL(standard templete librrary)各家厂商的编译器不同:LLVM(Apple) GCC(g++),VC,IBM/Intelint32_t e; // 一些编译器会缺省的把e初始化成0,e.g. VCint32_t e; // 而在其他编译器上,e可以是0,也可以是随机数字因此,为了保证在各个平台都不出错,我们要初始化我们的变量。*/int32_t e; // 不好int32_t f = 0; // 初始化float* g = nullptr;
指针和数组 — 内存的分配与释放
void ProcessAnArray(int32_t* array)
{// array 会退化成一个指针// array <==> int32_t* arrayPtr;std::cout << (sizeof(array)) << std::endl; // 64位下一个指针占8字节
}// 指针和数组int32_t arrayA[2] = {0, 1}; // arrayA为什么不用自己去清理内存?/*补充:在C++当中,若对象和内存分配在栈(stack)上,那么这些内存和数据就会被自动清理,什么时候被自动清理?退栈的时候,或当前作用域结束*/{// 没有名字的作用域int32_t arrayA_2[2] = {0, 1};}// 当程序运行到这里,arrayA_2 已经被释放了,已经退出那个作用域// 引入指针int32_t* arrayB = new int32_t(2); // arrayB is a pointer ,not an arrayarrayB[0] = 0; arrayB[1] = 1;// 内存自己管理delete arrayB;// trickyint32_t* h = arrayA;int32_t* i = h++;// 指针退化问题int32_t arrayC[3] = {0, 1, 2};std::cout << (sizeof(arrayC)) << std::endl; // 4 * 3 = 12 // 一个int类型占4字节ProcessAnArray(arrayC); // 退化问题// Prints
// 12
// 8
函数指针
typedef void(*FPtr) (void);void MyFunc()
{}// 4.函数指针// 我们定义的函数,是不是也是在内存当中,它应该也对应一个地址// 函数指针也是一个指针,但是它指向那个函数的首地址FPtr fptr = MyFunc;// in C++ 11,还可以这样去定义函数指针using comp = void(*)(int32_t, int32_t);using comp2 = int32_t(*)(float_t, int32_t);// 直接去使用 comp 和 comp2
二维数组的创建与销毁
// 1.创建指针数组,为其申请空间int row = 2;int column = 2;// 分配一个指针数组,将其保存在ptr中int ** ptr = new int *[row];// 为指针数组的每个元素分配一个数组for (int i = 0; i < row; i++) {ptr[i] = new int(column);}// 使用空间for (int j = 0; j < row; j++) {for (int k = 0; k < column; k ++) {ptr[j][k] = rand() % 100;std::cout << ptr[j][k] <<" "<< std::endl;}}for (int j = 0; j < row; j ++) {std::cout << "\n";for (int k = 0; k < column; k ++) {ptr[j][k] = rand() % 100;std::cout << ptr[j][k] << std::endl;}}// 释放空间 --> 先释放指针数组的每一个元素指向的数组,再释放该指针数组for (int i = 0; i < row; i++) {delete ptr[i];ptr[i] = nullptr;}delete [] ptr;ptr = nullptr;// Prints
/*
7
49
73
58 30
7244
78
*/