一句话概括
每个instance都有一个isa,这个isa,里面含有所有的方法列表,ios提供库函数增加,修改,即实现了动态添加方法
用 python 制作超燃动态排序视频
在开始之前,先贴张图,之前网上一段时间下面这种排序风格视频很火,下面这张图当作是视频其中的一帧。
制作这样视频的原理:就是把不同的帧组合在一起拼接成视频;把不同时间的排序图拼接在一起,拼接在一起形成一个随时间快速变化的动画,转化成视频,为了观看效果加一首很燃的BGM,最后的效果很赞。
这种视频 python 也能做,基本上分为三大部分,主要用到的就是两个库函数 pandas做数据处理,matplotlib绘制表、制作视频动画
1,数据预处理
这一部分细分为:数据读取,随机生成颜色代码,城市地区与颜色映射关系构造*、。
本次选取的数据为从1500年到2018年各地区的人数统计(提醒一下,是各城市所在区域人口数量),数据源链接:1500-2018各地区人口数量
# 导入库函数
import random
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
import matplotlib.animation as animation
from IPython.display import HTML
import matplotlib
#防止动漫内存太大,报错
matplotlib.rcParams['animation.embed_limit'] = 2**128
原数据是这样的,数据之间以”,(逗号)“隔开,我们需要的只是其中的几列,所以这里利用pandas 中的 usecols做一些列提取;
#pandas读取数据,且去列名分别为name,group,year和value的值;
url = 'https://gist.githubusercontent.com/johnburnmurdoch/4199dbe55095c3e13de8d5b2e5e5307a/raw/fa018b25c24b7b5f47fd0568937ff6c04e384786/city_populations'
df = pd.read_csv(url, usecols=['name', 'group', 'year', 'value'])
df.head()
因为每个地区标记一个颜色,这里需要构造一个随机颜色代码生成函数:
#导入random函数,randomcolor用于生成颜色代码
# randomcolor生成颜色代码原理,
# 【1-9/A-F】15个数字随机组合成6位字符串前面再加上一个“#”号键
import random
def randomcolor():
colorlist = ['1','2','3','4','5','6','7','8','9','A','B','C','D','E','F']
color =''
for i in range(6):
color += random.choice(colorlist)
return '#'+ color
最后构造两个字典:一个是城市与颜色之间的,一个是城市与所在区域(亚洲、欧洲等)之间的,形成一一映射关系方便后续处理
#对地区列表进行去重,分类;
area_list1 = set(df['name'])
# color_list用于存放随机生成颜色代码个数
# 因为后面区域个数 要与颜色个数保持一致,这里用了len函数;
color_list =[]
for i in range(len(area_list1)):
str_1 = randomcolor()
color_list.append(str_1)
str_1 = randomcolor()
#area_list转化为列表
area_list_1 = [i for i in area_list1]
print(color_list)
print(area_list_1)
构造映射关系:
#colors表示 所在城市:颜色 一一对应字典形式;
colors =dict(zip(area_list_1,color_list))
print(colors)
#group_lk为 城市:所在区域 --对应字典形式;
group_lk = df.set_index('name')['group'].to_dict()
print(group_lk)
2,图表绘制
这一部分主要是利用matplotlib 写了一个在某一年中各地区人口分布的直方图绘制函数,在代码每一步中有详细注释,想实现的可以参照一下代码:
# 用plt加理图表,figsize表示图标长宽,ax表示标签
fig, ax = plt.subplots(figsize=(15, 8))
#dras_barchart生成current_year这一年各城市人口基本情况;
def draw_barchart(current_year):
#dff对year==current_year的行,以value从升序方式排序,取后十名也就是最大值;
dff = df[df['year'].eq(current_year)].sort_values(by='value',ascending = True).tail(12)
# 所有坐标、标签清除
ax.clear()
#显示颜色、城市名字
ax.barh(dff['name'],dff['value'],color = [colors[x] for x in dff['name']])
dx = dff['value'].max()/200
#ax.text(x,y,name,font,va,ha)
# x,y表示位置;
# name表示显示文本;
# va,ba分别表示水平位置,垂直放置位置;
for i ,(value,name) in enumerate(zip(dff['value'], dff['name'])):
ax.text(value-dx,i,name,size=14,weight=600,ha ='right',va = 'bottom')
ax.text(value-dx,i-.25,group_lk[name],size = 10,color ='#444444',ha ='right',va = 'baseline')
ax.text(value+dx,i ,f'{value:,.0f}',size = 14,ha = 'left',va ='center')
#ax.transAxes表示轴坐标系,(1,0.4)表示放置位置
ax.text(1,0.4,current_year,transform = ax.transAxes,color ='#777777',size = 46,ha ='right',weight=800)
ax.text(0,1.06,'Population (throusands)',transform = ax.transAxes,size=12,color='#777777')
#set_major_formatter表示刻度尺格式;
ax.xaxis.set_major_formatter(ticker.StrMethodFormatter('{x:,.0f}'))
ax.xaxis.set_ticks_position('top')
ax.tick_params(axis='x',colors='#777777',labelsize=12)
ax.set_yticks([])
#margins表示自动缩放余额;
ax.margins(0,0.01)
# 设置后面的网格
ax.grid(which='major',axis='x',linestyle='-')
#刻度线和网格线是在图标上方还是下方,True为下方
ax.set_axisbelow(True)
ax.text(0,1.15,'The most population cities in the word from 1500 to 2018',
transform=ax.transAxes,size=24,weight=600,ha='left',va='top')
ax.text(1,0,'by@zeroing1',transform = ax.transAxes,color ='#777777',ha = 'right',
bbox = dict(facecolor='white',alpha = 0.8,edgecolor='white'))
#取消图表周围的方框显示
plt.box(False)
#绘制2018年各城市人口情况
draw_barchart(2018)
图表如下:
3,制作的图表转化为视频、动画
用到的功能是 matplotlib 的 animation 函数,下面这个是生成一个jshtml页面,可以在线预览
#将原来的静态图拼接成动画
fig, ax = plt.subplots(figsize=(15, 8))
animator = animation.FuncAnimation(fig, draw_barchart, frames=range(1500, 2019))
#保存到jshtml
HTML(animator.to_jshtml())
展示效果如下:
当然,也可以直接生成视频保存到本地,但在此之前请确保你的电脑已经配置好 FFmpeg,然后运行下面的代码,否则的话无法生成
#生成video,并保存至指定文件夹中
animator.to_html5_video()
animator.save('E:/ceshi/country_populations1.mp4')
然后可以加上合适的背景音乐,一个超燃的动态排序视频就完成了!
https://www.zhihu.com/video/1211027088029147136
显示一个月的提醒列表
使用C语言的字符串库
这一周学习了指针的高级应用,并通过《C语言程序设计现代方法》中例题“显示一个月的提醒列表”程序编写学习字符串库函数等。
题目:用户需要输入一系列提醒,每条提醒都要有一个前缀来说明是一个月中的哪一天。当用户输入的是0而不是有效的日期是,程序会显示出录入的全部提醒列表,按日期排序。下面是代码加个人理解注释:/*Prints a one-month reminder list */ #include <stdio.h> #include <string.h> #define _CRT_SECURE_NO_WARNINGS #define MAX_REMIND 50 /*maximum number of reminders*/ #define MSG_LEN 60 /*max length of reminder message*/ int read_line(char str[], int n); int main(void)//形式参数任意类型 { char reminders[MAX_REMIND][MSG_LEN + 3]; char day_str[3], msg_str[MSG_LEN + 1]; int day, i, j, num_remind = 0; for (;;) { //强制执行for循环 if (num_remind == MAX_REMIND) { printf("--No space left --\n"); break; } printf("Enter day and reminder: "); scanf("%2d", &day);//1个月只需要输入某一天就行 if (day == 0) break; sprintf(day_str, "%2d", day);//打印到字符串,所以day_str[3]多留一位给空字符,起到强制转换格式作用 read_line(msg_str, MSG_LEN); for (i = 0; i < num_remind; i++) if (strcmp(day_str, reminders[i]) < 0)//比较输入的日期和提醒数组中第一列是否有对应,不同为-1 break; for (j = num_remind; j > i; j--) strcpy(reminders[j], reminders[j-1]);//复制字符串--把找到的位置后面的字符串全都往后移动一个位置 strcpy(reminders[i], day_str);//把找到的日期放到day_str数组最后一个空位 strcat(reminders[i], msg_str);//字符串拼接--把提醒事项字符串放到日期后面 //**即使i=0或者没有匹配提醒事项的日期,也要将输入的日期赋给数组,此时没有提醒事项可以用回车跳出msg_str的输入 num_remind++; } printf("\nDay Reminder\n"); for (i = 0; i < num_remind; i++) printf(" %s\n", reminders[i]); return 0; } int read_line(char str[], int n) { int ch, i = 0;//ch i是函数内部变量,结束后不保存值 while ((ch = getchar()) != '\n') //条件语句之前先从缓存中每次读取1个字符给ch if (i < n) str[i++] = ch; //相当于将提醒事项一个个敲进str[]数组,最后按回车(转义字符)结束 str[i] = '\0';//空字符NULL return i; }
程序理解
把字符串存储在二维的字符数组中,数组的每一行包含一个字符串。在程序读入日期以及相关提醒后,通过使用strcmp函数进行比较来查找数组从而确定这一天所在的位置。然后,程序会使用strcpy函数把此位置之后的所有字符串往后移动一位。最后,程序会把这一天复制到数组中。并且调用strcat函数来把提醒附加到这一天后面(日期和提醒在此之前是分开存放的。)
VS2017报错的地方
1、关闭SDL检查,启用控制台程序;
2、#define _CRT_SECURE_NO_WARNINGS:用于避免检查scanf、strcpy等字符串函数,因为可能会内存泄漏,应使用scanf_s、strcpy_s等;
3、main函数不过定义,应该在项目属性中c/c+±–预编译头—关闭;不能同时存在.c和.cpp文件。程序运行
Enter day and reminder: 24 Susan's birthday Enter day and reminder: 5 6:00 - Dinner with Marge and Russ Enter day and reminder: 26 Movie - "Chinatown" Enter day and reminder: 7 10:30 - Dental appointment Enter day and reminder: 12 Movie - "Dazed and Confused" Enter day and reminder: 5 Saturday class Enter day and reminder: 12 Saturday class Enter day and reminder: 0 Day Reminder 5 Saturday class 5 6:00 - Dinner with Marge and Russ 7 10:30 - Dental appointment 12 Saturday class 12 Movie - "Dazed and Confused" 24 Susan's birthday 26 Movie - "Chinatown" F:\VS2017\Tixingliebiao\Debug\Tixingliebiao.exe (进程 15540)已退出,返回代码为: 0。 若要在调试停止时自动关闭控制台,请启用“工具”->“选项”->“调试”->“调试停止时自动关闭控制台”。 按任意键关闭此窗口...
动态分配字符串的数组
优点:字符串存储在字符数组中,可能很难预测这些数组需要的长度,通过动态地分配字符串,可以推迟到程序运行时才做决定。
如上文,我们定义了50行60列的数组用来存放提醒列表的字符串,内存空间浪费了。使用malloc函数动态分配内存空间。/*Prints a one-month reminder list */ #include <stdio.h> #include <string.h> #include <stdlib.h> #define _CRT_SECURE_NO_WARNINGS #define MAX_REMIND 50 /*maximum number of reminders*/ #define MSG_LEN 60 /*max length of reminder message*/ int read_line(char str[], int n); int main(void)//形式参数任意类型 { char *reminders[MAX_REMIND];//定义指针指向一维的数组 char day_str[3], msg_str[MSG_LEN + 1]; int day, i, j, num_remind = 0; for (;;) { //强制执行for循环 if (num_remind == MAX_REMIND) { printf("--No space left --\n"); break; } printf("Enter day and reminder: "); scanf("%2d", &day);//1个月只需要输入某一天就行 if (day == 0) break; sprintf(day_str, "%2d", day);//打印到字符串,所以day_str[3]多留一位给空字符,起到强制转换格式作用 read_line(msg_str, MSG_LEN); for (i = 0; i < num_remind; i++) if (strcmp(day_str, reminders[i]) < 0)//比较输入的日期和提醒数组中第一列是否有对应,不同为-1 break; for (j = num_remind; j > i; j--) reminders[j]=reminders[j - 1]; reminders[i] = malloc(2 + strlen(msg_str)+1); if (reminders[i] == NULL) { printf("--No space left --\n"); break; } strcpy(reminders[i], day_str);//把找到的日期放到day_str数组最后一个空位 strcat(reminders[i], msg_str);//字符串拼接--把提醒事项字符串放到日期后面 //**即使i=0或者没有匹配提醒事项的日期,也要将输入的日期赋给数组,此时没有提醒事项可以用回车跳出msg_str的输入 num_remind++; } printf("\nDay Reminder\n"); for (i = 0; i < num_remind; i++) printf(" %s\n", reminders[i]); return 0; } int read_line(char str[], int n) { int ch, i = 0;//ch i是函数内部变量,结束后不保存值 while ((ch = getchar()) != '\n') //条件语句之前先从缓存中每次读取1个字符给ch if (i < n) str[i++] = ch; //相当于将提醒事项一个个敲进str[]数组,最后按回车(转义字符)结束 str[i] = '\0';//空字符NULL return i; }
定义了char *reminders[MAX_REMIND],char类型的指针指向数组,用来分配内存用。
如果指针指向动态分配的内存块,就可以忽略它是指针的湿式,直接当做数组名字用,但是本例是需要数组先进行赋值,然后根据运行结果再分配内存,所以定义时要保留指针数组。
[看书+软件操作+博客总结]的方式学习很有收获。
1.C语言动态内存管理
在C语言中,我们主要对内存的动态管理主要使用的是malloc和free两个C标准库函数,所以我们主要来介绍一下malloc/free
malloc
malloc函数原型
void* __cdecl malloc(size_t _Size);
malloc 不保证成功 要检测return
malloc首先会扫描之前由free()所释放的空闲内存块列表,以求找到尺寸大于或等于要求的一块空闲内存。如果这一内存块的尺寸正好与要求相当,就将它返回给调用者,如果是一块较大的内存,那么将对其进行分割,在将一块大小相当的内存返回给调用者的同时,把较小的那块空闲内存块保留在空闲列表中。
malloc需要分配的内存会比实际的size多36byte
mallco如果分配失败,则返回一个空指针(NULL)
当不需要再使用申请的内存时,记得释放
malloc不会调用构造函数函数
free
free是将之前用malloc分配的空间还给程序或者是操作系统
free不会调用析构函数
不能free所申请空间的一部分
所申请的空间只能free一次,如果free两次以上会出错。(释放空指针例外,释放空指针其实也等于啥也没做,所以释放空指针释放多少次都没有问题)
free()释放的是指针指向的内存!注意!释放的是内存,不是指针!指针并没有被释放,指针仍然指向原来的存储空间。
在使用malloc对一个含有析构函数的类申请空间,且用delete[]来释放是,程序会出现崩溃
在使用new[]对一个含有析构函数的类申请空间是,用free释放,程序会出现崩溃
C语言内存管理方式在C++中可以继续使用,同时C++又提出了自己的内存管理方式:C++中通过new
和delete
运算符进行动态内存管理。
在使用关键字new
后会先申请空间,在调用构造函数,在使用delete
时,会先调用构造函数,在释放空间。
而关键字new delete
在底层会调用自己的重定义函数operator new()
和 operator delete()
,而在重定义new
和delete
中,还是会调用malloc
和free
,因此new
和delete
是对malloc
和free
的封装,并且在一定程度上比malloc
和free
安全。
因此,一般情况下使用 new
和 delete
进行内存申请。
new
new是一个关键字
new不会出错,不需要检验返回值,即不需要判空
new在底层调用了 void* operator new(size_t size),而operator在底层有调用了malloc,原型如下:
void* operator new(size_t nSize)
{
return malloc(nSize);
}
整个调用过程为:
new
-> 计算字节数
->operator new(字节数)
-> malloc(字节数)
->构造函数
new会自动计算字节数
new先申请空间,在调用构造函数。
delete
delete是一个关键字
delete在底层调用了operator delete(), operator delete()调用了free,原型如下:
void operator delete(void* p)
{
free(p);
}
delete的整个调用过程如下
delete
-> 调用析构函数,清理对象中的资源
-> dperator delete(p)
-> free(p)
delete会自动调用析构函数
delete先调用析构函数,在释放空间
new[]
new[]在底层调用了 operator new[];
new[]整体的调用过程如下:(test为一个类)
new test[N]
-> operator new[](sizeof(test)*N + 4)
->operator new(size)
-> malloc(size)
->用N次构造函数
new[]会比new多申请4个字节的空间,用来存放申请空间的个数N
将对象个数填写到空间的前4个字节后,将空间的首地址向后偏移4个字节
同样的new[],会自动的计算空间大小
delete[]
但存在析构函数时,new[] 会对申请4个字节,且向后偏移4个字节,delete[]会向前偏移4个字节
因此使用delete或者free会崩溃(不会向前偏移,只释放部分空间)
new/delete 和 malloc/free之间的区别
new能够自动计算需要分配的内存空间,而malloc需要手工计算字节数。例如,int* p1=new int[2] , int* p2=malloc(2*sizeof(int))。
new/delete直接带具体类型的指针,malloc/free返回void类型的指针。
new是类型安全的,而malloc不是。例如,int* p=new float[2],编译时就会报错;而int* p=malloc(2*sizeof(float)),编译时编译器就无法指出错误来。
new一般由两步构成,分别是new操作和构造。new操作对应于malloc,但new操作可以重载,可以自定义内存分配策略,不做内存分配,甚至分配到费内存设备上,而malloc不可以。
new将调用构造函数,而malloc不能;delete将调用析构函数,而free不能。
malloc/free需要库文件stdlib.h支持,new/delete则不需要库文件支持。
new[]/delete[] 和 new/delete的区别
类存在显示析构函数时,会多开辟4个字节的空间
new[] 对多次调用构造函数。
delete会多次调用析构函数
一句话概括
每个instance都有一个isa,这个isa,里面含有所有的方法列表,ios提供库函数增加,修改,即实现了动态添加方法