0%

“C/C++ 复杂类型声明规则” 将分为两节。第一节介绍螺旋法则声明规范,第二节介绍const的修饰关系。

本节主要解决以下问题:

1
2
3
const int *a;  // a可变嘛? *a可变嘛?
int * const a; // a可变嘛? *a可变嘛?
const int * const a; // a可变嘛? *a可变嘛?

这两节内容,我都尝试以一种不用死记硬背,而是理解的方式去掌握。

本文参考自【初中生也能看懂的C/C++类型声明规则教学,很简单的!】 https://www.bilibili.com/video/BV1mB4y1L7HB/ 。里面通过一些例子介绍了螺旋法则和对const关键词的判读。我将在此基础上加上我对其的解释,使其变得更加通俗易懂。

基本解决思路:const 只修饰右边最近的东西。

例如const r, 说明r这个变量是const的,不能动。const *r,意为const (*r),说明*r,r指向的东西,是const的。r自身可以变化。

再例如,const int r, 说明这个int是const的。这个整数是常量。r是一个整型常量。

举一些例子:

例子1

1
2
int const a1 = 5;
const int a2 = 6;

在这里,两个其实是等价的。对于第一个,a1被const修饰了,所以a1不能变。a1是一个整型变量。所以连起来,a1是一个常量整型变量。对于第二个,可以理解成a2是一个const int,a2是一个常量整型变量。

所以,不同的声明形式,意义可能相同。

例子2

1
2
int const *p1 = &a1;
const int *p2 = &a1;

在这里,两个其实也是等价的。对于第一个,*p1被const修饰了,所以*p1不能变。即p1指向的是一个整型常量。对于第二个,可以理解成p2指向一个const int,p2指向的是一个常量整型变量。对于这两个来说,p1 p2自己可以变,但是他们指向的不能变。

一个声明里的多个const,意义可能相同。例如:

1
const int const *p3

这个例子里虽然有两个const,但其实还是只修饰了一个*p3p3自身没有被const修饰,可以变。

例子3

1
int *const r2;

在这里,r2直接被const修饰,所以r2不能变。r2指向一个int,那个int没有额外修饰,所以那个int是可以变的。总之,r2是不能变的,但是r2指向的int是可以变的。

例子4

1
const int *const r3;

在这里,r3直接被const修饰,所以r3不能变。r3指向一个const int,那个int没有额外修饰,所以那个int是可以变的。总之,r3是不能变的,r3指向的int也不可以变的。

例子5

1
const int **r5;

r5指向一个指向int的指针。这个被指向的指针指向的int是被const所修饰的。所以r5可以变,r5指向的那个(指向int的指针)也是可以变的,但是r5指向的指针指向的int没得变。

例子6

1
int const * const * const r6;

上面那个例子的升级版。r6指向一个指向int的指针。r6被const修饰,r6不能变。r6指向的(指向int的指针)被const修饰,即*r6不能变。r6指向的指针指向的int,**r6也被const修饰,也不能变。

“C/C++ 复杂类型声明规则” 将分为两节。第一节介绍螺旋法则声明规范,第二节介绍const的修饰关系。这两节内容,我都尝试以一种不用死记硬背,而是理解的方式去掌握。

本文参考自【初中生也能看懂的C/C++类型声明规则教学,很简单的!】 https://www.bilibili.com/video/BV1mB4y1L7HB/ 。里面通过一些例子介绍了螺旋法则。我将在此基础上加上我对其的解释,使其变得更加通俗易懂。

螺旋法则:

  • 第一步,找到变量名,如果没有变置名,找到最里面的结构

  • 第二步,向右看,读出你看到的东西但是不要跳过括号

  • 第三步,再向左看,读出你看到的东西,但是也不要跳过括号

  • 第四步,如果有括号的话,跳出一层括号

  • 第五步,重复上述过程,直到你读出最终的类型

举例1:

1
int *v[5];
  • 第一步:
1
2
int *v[5];
^

如上,我们找到了v是变量名。读作:v是……

  • 第二步,向右看,读出你看到的东西但是不要跳过括号。
1
2
int *v[5];
^^^

[5]意为是一个5个元素的数组。读作:v是一个五个元素的数组。

  • 第三步,再向左看,读出你看到的东西,但是也不要跳过括号。
1
2
int *v[5];
^^^^^

int *意为每个东西是一个指向int的指针。读作:v是一个五个元素的数组,数组每个东西指向一个int型指针。

  • 第四步,如果有括号的话,跳出一层括号。
1
int *v[5];

对于上面这个例子,无这一步。我们已经读完了:v是一个五个元素的数组,数组每个东西指向一个int型指针。

我们可以验证一下我们理解正不正确。我们可以用下列的代码给v数组赋值:

1
2
3
int a = 2023;
v[0] = &a;
printf("%p %p", &a, v[0]);

g++编译通过且运行正确,可见我们的理解没有问题。

举例2

1
int (*func)()
  • 第一步:
1
2
int (*func)()
^^^^

如上,我们找到了func是变量名。读作:func是……

  • 第二步:向右看,读出你看到的东西但是不要跳过括号。

向右边遇到括号了,不跳出括号,skip。

  • 第三步,再向左看,读出你看到的东西,但是也不要跳过括号。
1
2
int (*func)()
^

单独一个*意为“指向”。读作:func指向……

  • 第四步,如果有括号的话,跳出一层括号。向右看。
1
2
int (*func)()
^^

这样一组括号是函数的意思。括号内为空,说明这个函数没有参数。整个(*func)的左边是int,它是这个函数的返回值,是int。读作:func指向一个接收空参且返回值为int的函数。

我们可以验证一下我们理解正不正确。我们可以用下列的代码用匿名函数给func赋值:

1
2
3
4
5
int (*func1)();
func1 = []()->int{
return 2023;
};
printf("%d", func1());

g++编译通过且运行正确,可见我们的理解没有问题。

总结&一个网站推荐

螺旋法则五步骤;

当读的时候,看到[]意为一个数组, ()意为是一个函数,那么在主体的左侧肯定配套还会有一个返回值(当然,这个返回值也可能是一个复杂复合类型)。 *如果单独出现,意为指向,如果和类型出现,如int*,那就可以理解为指向int。

这里顺便推荐一个可以辅助阅读的网站:https://cdecl.org/ 。这个网站可以将表达式翻译成人话(英语),帮助大家的理解。但是总体来说只要会了螺旋法则,啥声明都能理解确切含义。

本文是一个Python强大的计算库,NumPy(以下简称np)的入门介绍。在本文中,你会了解到:

  1. 什么是numpy?
  2. 为什么要用numpy?
  3. 从python内置list到numpy,有什么不同?
  4. 怎么安装与学习numpy?

因为网上numpy基础教程非常多,而且基本都覆盖了核心内容。因此,我不会再重复介绍numpy的一些基本操作,我只会留一些链接在第四部分。相反,我会从一些high-level的角度,介绍numpy,给读者构建一个大体印象。

1. 什么是numpy?

根据numpy官网( https://numpy.org/doc/stable/user/whatisnumpy.html )的介绍,numpy是一个python的科学计算库(如其名,numpy=numeric python)。他提供了多维数组等实用对象,和大量对数组的快速操作与算法。

NumPy is the fundamental package for scientific computing in Python. It is a Python library that provides a multidimensional array object, various derived objects (such as masked arrays and matrices), and an assortment of routines for fast operations on arrays.

2. 为什么要用numpy?

2.1 方便

python的内置list仅提供了一些简单操作,如append增加元素,sort排序元素等。如果要做一些复杂运算,就会略显吃力。numpy集成了大量的数学、数组操作函数,不需要自己造轮子了。例如直接使用np.corr()就可以计算相关系数,直接使用np.linalg.inv()就可以计算矩阵的逆矩阵,直接调用np.fft模块可以进行一些傅立叶变换相关的操作。

2.2 快速高效

高效是numpy的核心亮点之一。我想可以分为程序运行高效和程序员编程高效。

运行高效:因为python是一种解释型语言,其运行速度比c/c++语言会慢几十倍左右。numpy的底层代码(见 https://github.com/numpy/numpy )都是使用c语言写的,并被高度优化过。因此其相同任务运行速度远快于python的list,和c++水平大抵相同。举个例子,两个2,000,000长的向量相加,纯用python list和for循环需要0.173s,而使用numpy则只需要0.008s。

编程高效:numpy里的许多运算符也被重载过,进行一般四则运算也很方便。如两个np.ndarray a, b, 可以直接通过加号a+b实现向量的逐位相加,而若是两个list a,b, a+b只是将两个列表拼接起来。另外,numpy也实现了-, *, &进行逐位相减、逐位向乘、逐位与的操作。下面是例子展示。

1
2
3
4
5
6
7
8
9
10
11
# a, b are list
a = [1, 2, 3]
b = [3, 2, 1]
a+b
#OUTPUT: [1, 2, 3, 3, 2, 1]

# a, b are ndarray
a = np.array([1, 2, 3])
b = np.array([3, 2, 1])
a+b
#OUTPUT: np.array([4, 4, 4])

2.3 Python数据科学之基础

许多python的科学计算、数据科学库都是以numpy为数据容器的基础根基的。例如sklearn(机器学习库)中的算法使用numpy来实现,用户传入的均为numpy.ndarray数据;matplotlib(画图库)中接收numpy.ndarray数组绘图,等等。因此numpy是要用python玩数据科学等领域的基础。学会numpy,可以做以下事情:

  1. 复现论文中的算法

  2. 研究一些数据科学库的源码,实现细节

  3. 掌握一个以python为接口的高性能计算的核心工具,做任何想做的事情

  4. ……

3. 从python list到numpy

从python的内置list列表到numpy的ndarray有一些“同”也有一些“不同”。这里简单进行一个介绍,以使读者可以对numpy数组有一个更清晰的大体影响。

numpy.ndarray对象更像是其他编程语言(如C++/Java)里的数组(这样的数组结构才可以做到快速操作)。一旦构建,就要声明数组大小形状和数据类型。例如在C++中声明一个二维10*10的整型数组可以是:

1
int array[10][10];

而与之对应,在numpy里声明这样一个数组是:

1
array = np.ndarray(shape=(10, 10), dtype=int)

而python中的list实现本质是一个动态数组,其长度会根据运行时来决定。

接下来,我便由此列几点numpy数组和list的不同:

  • 改变大小。

因为list是动态数组,其长度会根据运行时来决定,因此list有append、pop等方法改变列表长短。而numpy.ndarray数组则“一旦构建,就有固定的大小”。在运行时不能改变大小(有几个元素),只能改变形状(如每行每列有几个元素)。np.append()方法虽说叫append,但是其本质也是要构建一个新的数组,而不是在原来的上做添加。

  • 统一数据类型。

一个python list中可以存放不同类型,例如[1, 1.2f, 'str']

一个numpy数组具有统一的数据类型,这使得其indexing索引、存储效率远高于list。一个数组内只能是一个类型,例如np.int32, int, np.float32, float等。如果需要存储不同类型,则需要将类型设为object。但是这样许多numpy函数便不支持使用了。

但是作为python语言的模块,numpy的设计还是非常清晰易懂的。大体上就是创建数组对象,然后对其进行运算或函数操作。语法上也非常相近。例如,numpy也支持list的切片索引(如a[::2],且其功能玩法更多)。

4. 怎么安装与学习numpy?

4.1 安装

网上的教程非常多,涵盖Windows,macOS和Linux系统。只需在网上找到相关教程即可。使用anaconda、miniconda、pip安装都可以。

4.2 学习

以个人经验来看,numpy的学习胜在多用。只要用得多,自然而然就熟练、学会了。numpy已经包含了大部分需要用的函数,如有对应需求,一定要先去网上搜索看看有无相关函数(现在还有chatGPT之类的可以问了)。

对于新手朋友我在这里推荐几个学习资源链接:

这里会摆放我在其他平台上写的文章的链接(因为我懒得搬过来了),欢迎点击阅读。

Cheatsheet

来自mpl官网的 https://matplotlib.org/cheatsheets/cheatsheets.pdf 的官方cheatsheet,但是pdf不方便即时查看,遂导出成图片,可以存到相册。图片可以单击后放大查看。

page1:

Cheatsheet

page2:

Cheatsheet

这里面覆盖了大部分使用mpl时会用到的函数,详细使用方法可见官网(https://matplotlib.org/)。

本文后续将会挑一些进行额外解释。

colormap class

colormap是把图变好看的灵魂。属于matplotlib.colors.Colormap()类. 这个类实现了__call__()方法,因此可以传入一个介于0-1之间的数,其返回一个四元组,表示一个RGBA颜色。这个元组可以被mpl的各类画图函数接收。

基础用法:

1
2
3
4
5
6
7
8
9
10
11
12
import matplotlib.pyplot as plt

cmap = plt.get_cmap('magma')

print(cmap(0))
print(cmap(0.5))
print(cmap(1))

# OUTPUT:
(0.001462, 0.000466, 0.013866, 1.0)
(0.716387, 0.214982, 0.47529, 1.0)
(0.002258, 0.001295, 0.018331, 1.0)

实践用法:画一条渐变的线。官网有例子 https://matplotlib.org/stable/gallery/lines_bars_and_markers/multicolored_line.html#sphx-glr-gallery-lines-bars-and-markers-multicolored-line-py, 我这是另一种实现方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
import matplotlib.pyplot as plt
import numpy as np

cmap = plt.get_cmap('magma')

t = np.linspace(0, 10, 100)
xt = np.sin(t)
normalized_t = t / t.max() # make it into [0, 1]

for i in range(len(t)-1): # draw piece by piece
plt.plot(t[i:i+2], xt[i:i+2], color=cmap(normalized_t[i]))

plt.show()

效果:

Cheatsheet

This blog is built with Hexo! This is the originally first post, automatically generated by Hexo. I modified it to a Quick Reference Handbook (QRH) for using this.

Quick Start

Create a new post

1
$ hexo new "My New Post"

More info: Writing

Clean cache

1
2
3
$ hexo clean
# shortcut:
$ hexo c

Generate static files

1
2
3
$ hexo generate
# shortcut:
$ hexo g

More info: Generating

Run server (local testing)

1
2
3
$ hexo server
# shortcut:
$ hexo s

More info: Server

Deploy to remote sites

1
2
3
$ hexo deploy
# shortcut:
$ hexo d

More info: Deployment