通常c/c++语言的编译通常分为如下几个阶段:预处理,编译,汇编,链接。
预编译处理(.i、.c) -> 编译、优化程序(.s)->汇编程序(.obj、.o、.a、.ko) -> 链接程序(.exe、.elf、.axf等)
为了更好地理解gcc的工作过程,我们可以让在gcc工作的4个阶段中的任何一个阶段中停止下来,如下:
gcc -E test.c -o test.i
查看 test.i 文件中的内容,会发现 stdio.h 的内容确实都插到文件里去了,而其他应当被预处理的宏定义也都做了相应的处理。
gcc -S test.c -o test.s
gcc -c test.s -o test.o
gcc test.o - o test
我们很自然的会联想到,我们能不能分步编译,而不是在每个阶段暂停,以便于我们更好的理解编译过程,答案是可以的。
1,预处理:替换宏定义和头文件( 为什么不能同时编译多个.c 和.h? 因为.h 不用编译,gcc -E a.c -o a.i 再多来几个.c都行,但不能有.h)
gcc -E a.h a.c -o a.i // 这样会报错gcc -E a.c -o a.i // 应该去掉.h
(#include就是预编译指令,预编译阶段主要处理以#开始的预编译指令,比如删除注释,添加行号、文件名标识....用include包含某.h文件时,将被包含文件插入该预编译指令的位置。生产的.o目标文件一般与.c对应而不是gcc可以编译.h成.o )
2,编译:将代码编译为汇编文件(ccl c编译器)
gcc -S a.i -o a.s
3,汇编:将汇编文件转换成二进制文件机器码(as 汇编工具)
gcc -c a.s -o a.o
4,链接:对应用的库函数进行链接重定位 (
这里主要对部分自己有可能使用到的指令做了一些总结,文末有参考链接。
来自菜鸟教程上linux教程的vim键盘图:
(1)保存并退出::wq,ZZ, :x
(2) 保存::w
(3):e – 重新加载当前文档。:e! – 重新加载当前文档,并丢弃已做的改动
(4) :e#或ctrl+^– 回到刚才编辑的文件,很实用
(5):Sex – 水平分割一个窗口,浏览文件系统;
:Vex – 垂直分割一个窗口,浏览文件系统;可以移动光标enter进入文件编辑,:wq保存退出即可
(6):f filename 改变编辑的文件名,这时再保存相当于另存为
$:移动光标到行尾,:$:光标移动到最后一行开头
n$:移动到第n行的行尾,:n :光标移动到第n行
0:(Num):移动光标到当前的行首,:0移动到第0行行首(二者区别在于冒号,因此一个是快捷键,一个是指令)
^:移动光标到行首第一i个非空字符上去
fa:移动光标到当前行的字符a上,nfa移动光标到当前行光标位置处开始之后的第n个a字符上,F:则相反,向前数
G:移动到文件末尾的行首;
gg:跳到第一行的第一个字符,即文件第0行的行首
G$:最后一行的最后一个字符 : 先重复1的操作即按“G”,之后按“$”键,即G+shift+4
H
: 把光标移到屏幕最顶端一行。 M
: 把光标移到屏幕中间一行。 L
: 把光标移到屏幕最底端一行。
h或退格: 左移一个字符;
l或空格: 右移一个字符;
j: 下移一行;
k: 上移一行;(当然,使用左右上下箭头也是可以的)
+或Enter: 把光标移至下一行第一个非空白字符。
-: 把光标移至上一行第一个非空白字符
w: 前移一个单词,光标停在下一个单词开头;W: 移动下一个单词开头,但忽略一些标点;
e: 前移一个单词,光标停在下一个单词末尾;E: 移动到下一个单词末尾,如果词尾有标点,则移动到标点;
b: 后移一个单词,光标停在上一个单词开头;B: 移动到上一个单词开头,忽略一些标点;
ge: 后移一个单词,光标停在上
1、分配磁盘空间 只执行至步骤6
https://jingyan.baidu.com/article/eae078275b58841fed548541.html
2、制作U盘启动器
http://www.uqidong.com/help/378.html
3、设置u盘启动 按住不放
http://www.upanok.com/jiaocheng/60.html
选择UEFI:SMI USB DISK 1100, Partition 1
4、安装
选择try 进入ubuntu桌面 --> 选择桌面上的 Install Ubuntu
欢迎 --> 中文(简体)
遇到 强制UEFI安装 ,选择后退,等待几秒出现新界面
准备安装Ubuntu --> 都不选
安装类型 --> 其他选项
5、分区挂载点(重装只是在这步先选择下面的分区“-”操作,不要格式化,再分区)
http://www.metsky.com/archives/257.html
普通桌面用户推荐分区方案
/boot 400M 逻辑分区 EXT4日志文件系统 空间起始位置
/ 20G 主分区 EXT4日志文件系统 空间起始位置
Swap 16G(内存的2倍) 逻辑分区 交换空间,无挂载点 空间起始位置
/home 50G(余下空间) 逻辑分区 EXT4日志文件系统 空间起始位置
安装启动引导器的设备 选择 /boot 所在的分区
6、注意:当开机出现 grub rescue,就要当心出问题;最好参考网上方法先进入系统,千万不要通过联网解决 grub 的问题;把启动顺序改为windows,把Ubuntu 所在盘清空,再重装Ubuntu
7、安装Ubuntu系统后,无法进入Ubuntu,直接进 Windows
在Windows安装 EasyBCD,进入EasyBCD
添加新条目 --> Linux/BSD --> 类型: GRUB 2 名称: Ubuntu 16.04 驱动
在C语言中,可以使用结构体(Struct)来存放一组不同类型的数据。结构体的定义形式为:
struct 结构体名{结构体所包含的变量或数组};
结构体是一种集合,它里面包含了多个变量或数组(注意:.c文件中的C语言结构体不允许包含函数!!!)。它们的类型可以相同,也可以不同,每个这样的变量或数组都称为结构体的成员(Member)
结构体也是一种数据类型,它由程序员自己定义,可以包含多个其他类型的数据。
像 int、float、char 等是由C语言本身提供的数据类型,不能再进行分拆,我们称之为基本数据类型;而结构体可以包含多个基本类型的数据,也可以包含其他的结构体,我们将它称为复杂数据类型或构造数据类型。
结构体变量
既然结构体是一种数据类型,那么就可以用它来定义变量。例如:
struct stu stu1, stu2;
定义了两个变量 stu1 和 stu2,它们都是 stu 类型,都由 5 个成员组成。注意关键字struct不能少,如果实在是想省略struct,可以在定义结构体时使用typedef,如:。
typedef struct Complex{int read;int image;}Complex;//那么,在说明Complex变量的时候可以这样写:Complex complex; //不用加关键字struct
stu 就像一个“模板”,定义出来的变量都具有相同的性质。也可以将结构体比作“图纸”,将结构体变量比作“零件”,根据同一张图纸生产出来的零件的特性都是一样的。
你也可以在定义结构体的同时定义结构体变量:
struct stu{char *name; //姓名int num; //学号int age; //年龄char group; //所在学习小组float score; //成绩} stu1, stu2;
将变量放在结构体定义的最后即可。
如果只需要 stu1、stu2 两个变量,后面不需要再使用结构体名定义其他变量,那么在定义时也可以不给出结构体名。
理论上讲结构
C/C++同一数组中还可以保存多种数据类型?比如int,char等?这有点颠覆我们的认知,通常在定义数组的时候,比如int a[3]={0,1,2},我们都是把数组的类型确定了的,那么为何又说可以存放不同的类型的数据呢?
最开始思考这个问题的时候,是在学习《TCP/IP网络编程》的时候,在P88页的op_client.c代码的39行有这样的代码:
...#define OPSZ 4...char opmsg[BUF_SIZE];...scanf("%d",(int*)&opmsg[i*OPSZ+1]);...
opmsg保存了一些char类型和int类型的数据,用于TCP传输。
那么问题就在这儿,opmsg明明是一个char类型的数组,为什么还能强行向其中存储10进制整数?书上的解释很含糊:
4字节int型数据要保存到char数组,因而传换成int指针类型。
在其他一些高级语言中,大都存在一种数组的类型,该类型包含了若干元素,还有元素个数等其它属性。这些数组类型的对象是被当作一个整体来对待的。
但是在C/C++中,可以说,不存在这种高级语言的数组类型。C中的数组,仅仅是地址排列在一起的元素而已,不管是char、int还是复杂的struct数组,仅仅是排列在一起的该类型大小的内存而已。数组的下标引用,仅仅是指针偏移的一种易读的形式!也可以说,数组在C中只是一个名字,简称而已。
如果你定义了一堆类型相同的变量,比如int a, b, c, d, e, f, .....只要它们编译后的内存地址是连续的,那么你也可以用数组的方式使用它们:
int *p = &a;p[0] = 1; //ap[1] = 2; //bp[3] = 3; //c......
关于这个理解,参考:
http://bbs.chinaunix.net/thread-1631299-1-1.html
所以,数组实际上只是定义了一块内存块的名字,数组名代表首地址,数组类型实际上是确定每个元素所占的内存大小而已。
指针就是指针
前面也有讲到,C++中NULL是0指针常量,是0的宏定义。看下面代码:
int* pn = NULL;//int* pn = new int(1);int* pm = new int(2);int *pc = new int(3);pm = pn;pn = pc;//*pn = 4;//错误,空指针无法解引用cout << *pm;
结果会报错,pm是nullptr,怎么会这样呢?我们不是已经pn=pc了吗?,pc不是nullptr 啊?
问题还是在于之前提到的,指针之间的赋值,还是复制的,比如pn =pc,pn和pc是两个不同的指针,这点很重要!!!只是它们指向的地址是一样的而已。因此,你哪怕修改了pn为pc,pm依然指向的是之前的pn指向的那个NULL!!!
因此,哪怕pn不是空指针,改成下面这样,修改pn =pc,结果依然无法改变pm的值!
//int* pn = NULL;int* pn = new int(1);int* pm = new int(2);int *pc = new int(3);pm = pn;pn = pc;//*pn = 4; //错误,空指针无法解引用cout << *pm;// 输出1
以上的代码,可以考虑改成:
int* pn = NULL;//int* pn = new int(1);int* pm = new int(2);int* pc = new int(3);pn = pc;pm = pn;cout << *pm;
以上的代码主要应用在,pm需要等于(或者说指向)pn所指向的对象,但是通常由于一次遍历的要求,pn经常是要比pm晚出现,如果像我们开头那样写,就大错特错啦。解决办法就是,确定了pn后再对pm赋值,而不是先用pn对pm赋值,后面才确定pn。
总结:
指针依然是普通的对象,或者说变量,只不过其存储的是其他变量或者对象的地址而已!
这里主要整理下NULL和nullptr的相关内容,注意:C++中没有null!!!
某些时候,我们需要将指针赋值为空指针,以防止野指针,在博客《为何新建的指针要设为NULL》也有讲到。
有人喜欢使用NULL作为空指针常量使用,例如:
int* p = NULL;。
也有人直接使用0值作为空指针常量,例如:
int* p = 0;
C++11后,引入了nullptr,因此还可以这样写:
int *p =nullptr;
那么,它究竟有多大区别?
NULL?NULL作为空指针常量,名字很形象,可读性较强。NULL并不是C/C++语言的关键字,而是定义在头文件中的宏,比如头文件:vcruntime.h,中是这么定义的:
#ifndef NULL#ifdef __cplusplus#define NULL 0#else#define NULL ((void *)0)#endif#endif
NULL是一个宏,它的值是一个空指针常量(null pointer constant),由实现定义。C语言中常数0和(void*)0都是空指针常量;C++中(暂且忽略C++11)常数0是,而(void*)0 不是。
(void*)0是空指针常量,而C++中不是?因为C语言中任何类型的指针都可以(隐式地)转换为void*型,反过来也行,而C++中void*型不能隐式地转换为别的类型指针(例如:int*p = (void*)0;使用C++编译器编译会报错),C++中,其他类型的指针可以转为void*型指针,只是反之不行。
答:个人觉得由于(void*)0更能体现指针的意义,而常数0更多的时候是用作整数。因此,C语言中NULL定义选择了(void*)0。(仅供参考)
考虑着这样一个函数重载的情形:
#include <stddef.h>void foo(int) {} // #1void foo(char*) {} /
最近在研究一个仿射变换的代码时,发现下面没见过的的numpy数组索引方法:
#shape of input_img :(2,187,300,3)#shape of y0 and x0:(2,187,300)#shape of Ia:(2,187,300,3)Ia = input_img[np.arange(num_batch)[:,None,None], y0, x0]
这是在用numpy数组索引数组。先回顾下基础:
一维数组索引
>>> x = np.arange(10)>>> x[2]2>>> x[-2]8
二维数组索引
>>> x.shape = (2,5) # now x is 2-dimensional>>> x[1,3]8>>> x[1,-1]9
数组切片
>>> x = np.arange(10)>>> x[2:5]array([2, 3, 4])>>> x[:-7]array([0, 1, 2])>>> x[1:7:2]array([1, 3, 5])>>> y = np.arange(35).reshape(5,7)>>> y[1:5:2,::3]array([[ 7, 10, 13],[21, 24, 27]])
例:产生一个一组数组,使用数组来索引出需要的元素。让数组[3,3,1,8]取出x中的第3,3,1,8的四个元素组成一个数组view
>>> x = np.arange(10,1,-1)>>> xarray([10, 9, 8, 7, 6, 5, 4, 3, 2])>>> x[np.array([3, 3, 1, 8])]array([7, 7, 9, 2])
当然,类似切片那样,Index也可以使用负数。但是索引值不能越界!
>>> x[np.array([3,3,-3,8])]array([7, 7, 4, 2])
例1:产生一个5X7的数组,选择0,2,4行,0,1,2列的数
>>> y = np.arange(35).reshape(5,7)>>> y
最近在使用二维字典的时候,发现了一个有趣的问题, 发现网上对于二维字典的更新方式全是如下方式,其解释也全是因为二维字典的两层key和value之间会混淆:
from collections import defaultdictdef addtwodimdict(thedict, key_a, key_b, val):adict = thedict.keys()if key_a in adict:thedict[key_a].update({key_b: val})else:thedict.update({key_a:{key_b: val}})
那么,使用这种方式,再看一个代码:
from collections import defaultdictdef addtwodimdict(thedict, key_a, key_b, val):adict = thedict.keys()if key_a in adict:thedict[key_a].update({key_b: val})else:thedict.update({key_a:{key_b: val}})data =[[1000,2,3,2,5,6,7],[1001,4,6,6,3,7,2],[1002,3,6,2,1,6,7],[1003,3,4,6,5,8,0]]def my_dict(data):graph_dict = defaultdict(lambda: defaultdict(lambda: 0)) # 声明一个二维default dictfor i in range(len(data)):val1=data[i][1]val2=data[i][2]addtwodimdict(graph_dict,val1,val2,data[i][0])addtwodimdict(graph_dict,val2,val1,data[i][0])return graph_dict
经常会遇到embedding的概念,要搞清楚embeding先要弄明白他和one hot encoding的区别,以及他解决了什么one hot encoding不能解决的问题。
独热编码(One-hot encoding)向量是高维且稀疏的。假如说我们在做自然语言处理(NLP)的工作,并且有一个包含 2000 个单词的字典。这意味着当我们使用独热编码时,每个单词由一个含有 2000 个整数的向量来表示,并且其中的 1999 个整数都是 0。在大数据集下这种方法的计算效率是很低的。
但是one-hot编码的优势在于,计算方便快捷、表达能力强。
有没有一种办法将独热编码转换一下,化稀疏为密集,提高其效率?这个转换过程就是embedding,嵌套!最开始主要是应用在nlp自然语言处理中的。正如Keras 文档里是这么写embedding的:“把正整数(索引)转换为固定大小的稠密向量”。
转换方法可类比下面(实际上就是个转换函数,可以有很多其他方式):
直观上就可以发现,编码矩阵已经减小了一半了!
达到了“降维”的效果。而其中转换的矩阵,可以理解为转换表,过渡矩阵,whatever~
更详细的可以参考:
https://blog.csdn.net/weixin_42078618/article/details/82999906