2019-06-19 16:05:45    106    0    0

gcc(g++)基础

(1) 编译过程

通常c/c++语言的编译通常分为如下几个阶段:预处理,编译,汇编,链接。

  1. 预编译处理(.i、.c) -> 编译、优化程序(.s)->汇编程序(.obj、.o、.a、.ko) -> 链接程序(.exe、.elf、.axf等)

title

(2)理解编译过程

为了更好地理解gcc的工作过程,我们可以让在gcc工作的4个阶段中的任何一个阶段中停止下来,如下:

第一步:进行预编译,使用 -E 参数

gcc -E test.c -o test.i
查看 test.i 文件中的内容,会发现 stdio.h 的内容确实都插到文件里去了,而其他应当被预处理的宏定义也都做了相应的处理。

第二步:生成汇编源文件

gcc -S test.c -o test.s

第三步:将 test.s 编译为目标代码,使用 -c 参数

gcc -c test.s -o test.o

第四步:将生成的目标文件链接成可执行文件

gcc test.o - o test

(3)分步编译

我们很自然的会联想到,我们能不能分步编译,而不是在每个阶段暂停,以便于我们更好的理解编译过程,答案是可以的。

1,预处理:替换宏定义和头文件( 为什么不能同时编译多个.c 和.h? 因为.h 不用编译,gcc -E a.c -o a.i 再多来几个.c都行,但不能有.h)

  1. gcc -E a.h a.c -o a.i // 这样会报错
  2. gcc -E a.c -o a.i // 应该去掉.h

(#include就是预编译指令,预编译阶段主要处理以#开始的预编译指令,比如删除注释,添加行号、文件名标识....用include包含某.h文件时,将被包含文件插入该预编译指令的位置。生产的.o目标文件一般与.c对应而不是gcc可以编译.h成.o )

2,编译:将代码编译为汇编文件(ccl c编译器)

  1. gcc -S a.i -o a.s

3,汇编:将汇编文件转换成二进制文件机器码(as 汇编工具)

  1. gcc -c a.s -o a.o

4,链接:对应用的库函数进行链接重定位 (

2019-06-18 13:45:55    227    0    0

这里主要对部分自己有可能使用到的指令做了一些总结,文末有参考链接。
来自菜鸟教程上linux教程的vim键盘图:
title

3.文档操作

(1)保存并退出::wqZZ, :x
(2) 保存::w
(3):e – 重新加载当前文档。:e! – 重新加载当前文档,并丢弃已做的改动
(4) :e#ctrl+^– 回到刚才编辑的文件,很实用
(5):Sex – 水平分割一个窗口,浏览文件系统;
:Vex – 垂直分割一个窗口,浏览文件系统;可以移动光标enter进入文件编辑,:wq保存退出即可
(6):f filename 改变编辑的文件名,这时再保存相当于另存为

4.光标移动

(1)移动到“头”和“尾”

$:移动光标到行尾,:$:光标移动到最后一行开头
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: 把光标移到屏幕最底端一行。

(2)基本移动

h退格: 左移一个字符;
l空格: 右移一个字符;
j: 下移一行;
k: 上移一行;(当然,使用左右上下箭头也是可以的)

+Enter: 把光标移至下一行第一个非空白字符。
-: 把光标移至上一行第一个非空白字符

w: 前移一个单词,光标停在下一个单词开头;W: 移动下一个单词开头,但忽略一些标点;
e: 前移一个单词,光标停在下一个单词末尾;E: 移动到下一个单词末尾,如果词尾有标点,则移动到标点;

b: 后移一个单词,光标停在上一个单词开头;B: 移动到上一个单词开头,忽略一些标点;
ge: 后移一个单词,光标停在上

2019-05-28 21:59:57    173    0    0

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 驱动

2019-05-27 15:23:22    312    0    0

在C语言中,可以使用结构体(Struct)来存放一组不同类型的数据。结构体的定义形式为:

  1. struct 结构体名{
  2. 结构体所包含的变量或数组
  3. };

结构体是一种集合,它里面包含了多个变量或数组(注意:.c文件中的C语言结构体不允许包含函数!!!)。它们的类型可以相同,也可以不同,每个这样的变量或数组都称为结构体的成员(Member

结构体也是一种数据类型,它由程序员自己定义,可以包含多个其他类型的数据。

int、float、char 等是由C语言本身提供的数据类型,不能再进行分拆,我们称之为基本数据类型;而结构体可以包含多个基本类型的数据,也可以包含其他的结构体,我们将它称为复杂数据类型或构造数据类型。
结构体变量
既然结构体是一种数据类型,那么就可以用它来定义变量。例如:

  1. struct stu stu1, stu2;

定义了两个变量 stu1 和 stu2,它们都是 stu 类型,都由 5 个成员组成。注意关键字struct不能少,如果实在是想省略struct,可以在定义结构体时使用typedef,如:。

  1. typedef struct Complex{
  2.   int read;
  3.   int image;
  4. }Complex;
  5. //那么,在说明Complex变量的时候可以这样写:
  6. Complex complex; //不用加关键字struct

stu 就像一个“模板”,定义出来的变量都具有相同的性质。也可以将结构体比作“图纸”,将结构体变量比作“零件”,根据同一张图纸生产出来的零件的特性都是一样的。

你也可以在定义结构体的同时定义结构体变量:

  1. struct stu{
  2. char *name; //姓名
  3. int num; //学号
  4. int age; //年龄
  5. char group; //所在学习小组
  6. float score; //成绩
  7. } stu1, stu2;

将变量放在结构体定义的最后即可。

如果只需要 stu1stu2 两个变量,后面不需要再使用结构体名定义其他变量,那么在定义时也可以不给出结构体名。

理论上讲结构

2019-05-23 14:11:47    323    0    0

一.引言

C/C++同一数组中还可以保存多种数据类型?比如int,char等?这有点颠覆我们的认知,通常在定义数组的时候,比如int a[3]={0,1,2},我们都是把数组的类型确定了的,那么为何又说可以存放不同的类型的数据呢?

二.引入问题

最开始思考这个问题的时候,是在学习《TCP/IP网络编程》的时候,在P88页的op_client.c代码的39行有这样的代码:

  1. ...
  2. #define OPSZ 4
  3. ...
  4. char opmsg[BUF_SIZE];
  5. ...
  6. scanf("%d",(int*)&opmsg[i*OPSZ+1]);
  7. ...

opmsg保存了一些char类型和int类型的数据,用于TCP传输。
那么问题就在这儿,opmsg明明是一个char类型的数组,为什么还能强行向其中存储10进制整数?书上的解释很含糊:
4字节int型数据要保存到char数组,因而传换成int指针类型

三.C/C++数组实际上没有所谓的类型

在其他一些高级语言中,大都存在一种数组的类型,该类型包含了若干元素,还有元素个数等其它属性。这些数组类型的对象是被当作一个整体来对待的。

但是在C/C++中,可以说,不存在这种高级语言的数组类型。C中的数组,仅仅是地址排列在一起的元素而已,不管是char、int还是复杂的struct数组,仅仅是排列在一起的该类型大小的内存而已。数组的下标引用,仅仅是指针偏移的一种易读的形式!也可以说,数组在C中只是一个名字,简称而已。

如果你定义了一堆类型相同的变量,比如int a, b, c, d, e, f, .....只要它们编译后的内存地址是连续的,那么你也可以用数组的方式使用它们:

  1. int *p = &a;
  2. p[0] = 1; //a
  3. p[1] = 2; //b
  4. p[3] = 3; //c
  5. ......

关于这个理解,参考:
http://bbs.chinaunix.net/thread-1631299-1-1.html

所以,数组实际上只是定义了一块内存块的名字,数组名代表首地址,数组类型实际上是确定每个元素所占的内存大小而已

四.数组与指针有相似的地方,但数组不是指针

指针就是指针

2019-04-23 16:18:41    349    0    0

前面也有讲到,C++中NULL是0指针常量,是0的宏定义。看下面代码:

  1. int* pn = NULL;
  2. //int* pn = new int(1);
  3. int* pm = new int(2);
  4. int *pc = new int(3);
  5. pm = pn;
  6. pn = pc;
  7. //*pn = 4;//错误,空指针无法解引用
  8. cout << *pm;

结果会报错,pm是nullptr,怎么会这样呢?我们不是已经pn=pc了吗?,pc不是nullptr 啊?

问题还是在于之前提到的,指针之间的赋值,还是复制的,比如pn =pc,pn和pc是两个不同的指针,这点很重要!!!只是它们指向的地址是一样的而已。因此,你哪怕修改了pn为pc,pm依然指向的是之前的pn指向的那个NULL!!!
因此,哪怕pn不是空指针,改成下面这样,修改pn =pc,结果依然无法改变pm的值!

  1. //int* pn = NULL;
  2. int* pn = new int(1);
  3. int* pm = new int(2);
  4. int *pc = new int(3);
  5. pm = pn;
  6. pn = pc;
  7. //*pn = 4; //错误,空指针无法解引用
  8. cout << *pm;// 输出1

以上的代码,可以考虑改成:

  1. int* pn = NULL;
  2. //int* pn = new int(1);
  3. int* pm = new int(2);
  4. int* pc = new int(3);
  5. pn = pc;
  6. pm = pn;
  7. cout << *pm;

以上的代码主要应用在,pm需要等于(或者说指向)pn所指向的对象,但是通常由于一次遍历的要求,pn经常是要比pm晚出现,如果像我们开头那样写,就大错特错啦。解决办法就是,确定了pn后再对pm赋值,而不是先用pnpm赋值,后面才确定pn

总结:
指针依然是普通的对象,或者说变量,只不过其存储的是其他变量或者对象的地址而已!

2019-04-22 22:54:46    446    0    0

这里主要整理下NULLnullptr的相关内容,注意:C++中没有null!!!

某些时候,我们需要将指针赋值为空指针,以防止野指针,在博客《为何新建的指针要设为NULL》也有讲到。
有人喜欢使用NULL作为空指针常量使用,例如:
int* p = NULL;
也有人直接使用0值作为空指针常量,例如:
int* p = 0;
C++11后,引入了nullptr,因此还可以这样写:
int *p =nullptr;

那么,它究竟有多大区别?

什么是NULL?

NULL作为空指针常量,名字很形象,可读性较强。NULL并不是C/C++语言的关键字,而是定义在头文件中的宏,比如头文件:vcruntime.h,中是这么定义的:

  1. #ifndef NULL
  2. #ifdef __cplusplus
  3. #define NULL 0
  4. #else
  5. #define NULL ((void *)0)
  6. #endif
  7. #endif

NULL是一个宏,它的值是一个空指针常量(null pointer constant),由实现定义。C语言中常数0和(void*)0都是空指针常量;C++中(暂且忽略C++11)常数0是,而(void*)0 不是。

为什么C中(void*)0是空指针常量,而C++中不是?

因为C语言中任何类型的指针都可以(隐式地)转换为void*型,反过来也行,而C++中void*型不能隐式地转换为别的类型指针(例如:int*p = (void*)0;使用C++编译器编译会报错),C++中,其他类型的指针可以转为void*型指针,只是反之不行。

既然C/C++标准中,常数0都可作为空指针常量,为什么不统一使用0?

答:个人觉得由于(void*)0更能体现指针的意义,而常数0更多的时候是用作整数。因此,C语言中NULL定义选择了(void*)0。(仅供参考)

C++11中为什么要引入nullptr?

考虑着这样一个函数重载的情形:

  1. #include <stddef.h>
  2. void foo(int) {} // #1
  3. void foo(char*) {} /
2019-04-03 16:17:06    1354    0    0

最近在研究一个仿射变换的代码时,发现下面没见过的的numpy数组索引方法:

  1. #shape of input_img :(2,187,300,3)
  2. #shape of y0 and x0:(2,187,300)
  3. #shape of Ia:(2,187,300,3)
  4. Ia = input_img[np.arange(num_batch)[:,None,None], y0, x0]

这是在用numpy数组索引数组。先回顾下基础:

一、单个元素索引

一维数组索引

  1. >>> x = np.arange(10)
  2. >>> x[2]
  3. 2
  4. >>> x[-2]
  5. 8

二维数组索引

  1. >>> x.shape = (2,5) # now x is 2-dimensional
  2. >>> x[1,3]
  3. 8
  4. >>> x[1,-1]
  5. 9

数组切片

  1. >>> x = np.arange(10)
  2. >>> x[2:5]
  3. array([2, 3, 4])
  4. >>> x[:-7]
  5. array([0, 1, 2])
  6. >>> x[1:7:2]
  7. array([1, 3, 5])
  8. >>> y = np.arange(35).reshape(5,7)
  9. >>> y[1:5:2,::3]
  10. array([[ 7, 10, 13],
  11. [21, 24, 27]])

二、使用数组索引数组

例:产生一个一组数组,使用数组来索引出需要的元素。让数组[3,3,1,8]取出x中的第3,3,1,8的四个元素组成一个数组view

  1. >>> x = np.arange(10,1,-1)
  2. >>> x
  3. array([10, 9, 8, 7, 6, 5, 4, 3, 2])
  4. >>> x[np.array([3, 3, 1, 8])]
  5. array([7, 7, 9, 2])

当然,类似切片那样,Index也可以使用负数。但是索引值不能越界!

  1. >>> x[np.array([3,3,-3,8])]
  2. array([7, 7, 4, 2])

三、索引多维数组

例1:产生一个5X7的数组,选择0,2,4行,0,1,2列的数

  1. >>> y = np.arange(35).reshape(5,7)
  2. >>> y
2019-03-25 17:12:25    268    0    0

最近在使用二维字典的时候,发现了一个有趣的问题, 发现网上对于二维字典的更新方式全是如下方式,其解释也全是因为二维字典的两层key和value之间会混淆

  1. from collections import defaultdict
  2. def addtwodimdict(thedict, key_a, key_b, val):
  3. adict = thedict.keys()
  4. if key_a in adict:
  5. thedict[key_a].update({key_b: val})
  6. else:
  7. thedict.update({key_a:{key_b: val}})

那么,使用这种方式,再看一个代码:

  1. from collections import defaultdict
  2. def addtwodimdict(thedict, key_a, key_b, val):
  3. adict = thedict.keys()
  4. if key_a in adict:
  5. thedict[key_a].update({key_b: val})
  6. else:
  7. thedict.update({key_a:{key_b: val}})
  8. 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]]
  9. def my_dict(data):
  10. graph_dict = defaultdict(lambda: defaultdict(lambda: 0)) # 声明一个二维default dict
  11. for i in range(len(data)):
  12. val1=data[i][1]
  13. val2=data[i][2]
  14. addtwodimdict(graph_dict,val1,val2,data[i][0])
  15. addtwodimdict(graph_dict,val2,val1,data[i][0])
  16. return graph_dict
2019-03-04 12:08:45    259    0    0

经常会遇到embedding的概念,要搞清楚embeding先要弄明白他和one hot encoding的区别,以及他解决了什么one hot encoding不能解决的问题。

独热编码(One-hot encoding)向量是高维且稀疏的。假如说我们在做自然语言处理(NLP)的工作,并且有一个包含 2000 个单词的字典。这意味着当我们使用独热编码时,每个单词由一个含有 2000 个整数的向量来表示,并且其中的 1999 个整数都是 0。在大数据集下这种方法的计算效率是很低的。
但是one-hot编码的优势在于,计算方便快捷、表达能力强。

有没有一种办法将独热编码转换一下,化稀疏为密集,提高其效率?这个转换过程就是embedding,嵌套!最开始主要是应用在nlp自然语言处理中的。正如Keras 文档里是这么写embedding的:“把正整数(索引)转换为固定大小的稠密向量”。

转换方法可类比下面(实际上就是个转换函数,可以有很多其他方式):
title

直观上就可以发现,编码矩阵已经减小了一半了!
达到了“降维”的效果。而其中转换的矩阵,可以理解为转换表,过渡矩阵,whatever~

更详细的可以参考:
https://blog.csdn.net/weixin_42078618/article/details/82999906

https://blog.csdn.net/anshuai_aw1/article/details/83586404

https://spaces.ac.cn/archives/4122