嵌入式学习

Linux部分

由于前面已经学习了Linux,所以在这里只做简单的补充;

vi编辑器三种模式

​ 命令行模式:

​ 用户在用vi编辑时文件时,最初进入的该模式。课以进行复制、粘贴等操作。

​ 插入模式:

​ 进行文件编辑,按ESC键可以回到命令行模式。

​ 底行模式:

​ 光标位于屏幕底行。可以进行文件的保存、退出、查找、替换、列出行号等。

Vi光标命令

命令 功能
h 方向键,向左移动光标一个字符的位置,相当于键”←”
j 方向键,向左移动光标一个字符的位置,相当于键“↓”
k 方向键,向左移动光标一个字符的位置,相当于键“↑”
l 方向键,向左移动光标一个字符的位置,相当于键“→”
:N y移动光标到第N行
1G 移动光标到文件的第1行
G 移动光标到文件的最后一行

Vi的查找命令

/string 查找字符串

​ n继续查找

​ N方向继续查找

​ 支持正则表达式

Vi替换命令

利用:s命令可以实现字符串的替换

1
2
3
4
5
6
:s/str1/str2/             #s当前行
:s/str1/str2/g #g全部替换
:.,$s/str1/str2/g #.,$从该行到末尾
:1,$s/str1/str2/g #1,$从第一行到最后一行(全部)
:%s/str1/str2/g #%全部
:n1,n2s/str1/str2/g #从n1到n2行

Vi复制和剪切命令

y0:将光标至行首的字符考入剪贴板

y$:将光标至行尾的字符考入剪贴板

d0:将光标至行首的字符剪切入剪贴板

d$:将光标至行尾的字符剪切入剪贴板

:n1,n2y:块复制

:n1,n2d:块剪切

计算机结构

冯 . 诺依曼模型

​ 计算机硬件由五部分组成:输入、输出、存储器、运算器、控制器

​ 存储程序的思想:系统的运行过程就是按照一定的顺序不断执行存储器中的程序指令的过程。

存储器的分类

​ 主存储器即内存。程序中待处理的数据和处理的结构都存储在内存中。

​ 外存储器是用来长期保存数据的大容量存储器。

​ 寄存器是CPU内部的高速存储器,速度快,数目少。

什么是程序

​ 广义上讲,为了实现一个特定的目标而预先设计的一组可操作的工作步骤,称之为一个程序。

​ 程序就是系统可以识别的一组有序的指令(二进制)。储存在磁盘上,被加载到内存中执行。

程序设计语言的发展

机器语言

汇编语言

高级语言

程序设计步骤

计算机数据表示

​ 送入计算机的数字、字母、符号等信息必须转换成0、1组合的数据形式才能被计算机识别。

​ 能够进行算术运算得到明确数值概念的信息称为计算机数值数据,其余的信息成为非数值数据。

数值数据的表示

​ 十进制、二进制、十六进制、八进制

​ 基数和各数位的权

非数值数据表示

​ 非数值数据包括文字、符号、图像、语言和逻辑信息等,也都是以0、1形式存在。

​ 字符数据在机器内也被变换成二进制编码的形式。国际上普遍采用的一种编码是美国国家信息交换标准代码,简称为ASCII码。

ASCII

man ASCII

程序的编译和调试

gcc编译器

gcc(GNU compiler)是GNU推出的多平台编译器,可将C、C++源程序编译连接成可执行文件,支持以下后缀:

.c c语言源代码
.h 程序所包含的头文件
.i 已经预处理过的C源代码文件
.s 汇编语言源代码文件
.o 编译后的目标文件

gcc -o hello hello.cgcc hello.c -o hell

​ -Wall 查看警告

​ -o 输出可执行文件

​ -c 只要求编译器输出目标代码(.o文件),而不必输出可执行文件

​ -g 用于调试

​ ./hell 查看输出结果(执行)

程序调试

​ 利用_FILE_,_LINE_,_FUNCTION_实现代码跟踪调试

1
2
3
4
5
6
#include<stdio.h>
int main(int argc,char **argv)
{
printf("%s,%s,%d\n",__FILE__,__FUNCTION__,__LINE__);
return 0;
}

C语言基础

一个程序应当包含两个部分:

​ 对数据的描述。在程序中要指定数据的类型和数据的组织形式,即数据结构

​ 对操作的描述。即操作步骤,也就是算法

算法的基本概念:

​ 做任何事都有一定的步骤。步骤要按照一定的顺序进行,缺一不可,次序也不能错。广义的说,为解决一个问题而采取的方法和步骤就称之为算法。


由于以前学过C语言,现在只是来复习和拓展一下。

数据类型

基本数据类型

逻辑类型。只有两个量true和false,表示逻辑真值和逻辑假植。

整数类型。包括char,short,int和long。

浮点类型。包括float和double。

void类型。主要用于说明不返回的函数或指针。

bool类型。非零(true),零(false)。需要加头文件<stdbool.h>

char类型。使用char数据类型的变量需要特别注意,防止数据超出值域。

<limits.h>

类型名称 长度(字节) 值域
char 1 -128~127或0~255(使用/J编译选项)
signed char 1 -128~127
unsigned char 1 0~255

short类型

类型名称 长度(字节) 值域
short(signed short) 2 -32768~32767
unsigned short 2 0~65535

int类型

类型名称 长度(字节) 值域
int(signed int) 4 -2147483648~2147483647
unsigned int 4 0~4294967295

常量

整型常量

​ 常量是指在程序运行期间其数值不发生变化的数据。整型常量通常称为整数。

​ 整数可以是十进制、八进制、和十六进制数。

浮点常量

​ 浮点常量又称为实数,一般含有小数部分。

​ 在C语言中,实数只有十进制的实数,分为单精度和双精度。实数表示有两种方法,即一般形式和指数形式。

指数常量

​ 指数形式的实数一般是由尾数部分、字母e或E和指数部分组成。

字符常量

​ 字符常量是指一个单一字符,其表示是由两个单引号包括的一个字符。

​ 在C语言中,字符常量具有数值。字符常量的值就是字符的ASCII码值。

​ 可以把字符常量看做一个字节的正整数。

字符串常量

​ 所谓字符串常量是指用双引号括起来的一串字符来表示的数据。(字符串以\0结尾,像”3”与’3’,’\0’相同)。

标识常量

​ 所谓标识常量是指用标识符代替常量使用的一种常量,其名称通常是一个标识符。

#define <表示常量名称> <常量>

练习

一个水分子的质量约为3.0*10-23g,1夸脱水大约有950g,编写一个程序,要求输入水的夸脱数,然后显示这么多水中包含多少水分子?

变量

变量的基础

​ 变量在程序中用变量名表示。变量名由用户根据其用途任意命名。

​ 变量名由字母、数字、下划线组成,不能以数字开头,不能和C的关键字重名。

​ 在程序运行时,变量占据存储空间的大小由其数据类型决定。

​ 变量在内存空间中的首地址,称为变量的地址。

变量的说明

​ 变量在程序中使用时,必须预先说明它们的存储类型和数据类型。

​ 变量说明的一般形式是:

<存储类型> <数据类型 > <变量名>

​ <存储类型>是关键词auto、register、static和extern

​ <数据类型>可以是基本数据类型,也可以是自定义的数据类型

变量的存储类型

​ auto说明的变量只能在某个程序范围内使用,通常在函数体内或函数中的复合语句里。(默认是随机值)

​ 在函数体的某程序段内说明auto存储类型的变量时可以省略关键字auto。

​ register称为寄存器型,register变量是想将变量放入CPU的寄存器中,这样可以加快程序的运行速度。如申请不到就使用一般内存,同auto ;

​ register变量必须是能被CPU所接受的类型。这通常意味着register变量必须是一个单个的值,并且长度应该小于或者等于整型的长度。不能用“&”来获取register变量的地址。

​ 由于寄存器的数量有限,真正起作用的register修饰符的数目和类型都依赖于运行程序的机器。在某些情况下,把变量保存在寄存器中反而会降低程序的运行速度。因为被占用的寄存器不能再用于其它目的;或者变量被使用的次数不够多,不足以装入和存储变量所带来的额外开销。

变量的存储类型static

​ static变量称为静态存储类型的变量,既可以在函数体内,也可在函数体外说明。(默认是0)

局部变量使用static修饰,有以下特点:

​ 在内存中以固定地址存放的,而不是以堆栈方式存放

​ 只要程序没结束,就不会随着说明它的程序段的结束而消失,它下次再调用该函数,该存储类型的变量不再重新说明,而且还保留上次调用存入的数值

变量的存储类型extern

​ 当变量在一个文件中的函数体外说明,所有其他文件中的函数或程序段都可引用这个变量。

​ extern称为外部参照引用型,使用extern说明的变量是想引用在其它文件中函数体外部说明的变量。

​ static修饰的全部变量,其它文件无法使用


运算符

算术运算符

​ C提供的算术运算符:+,-,*,/,%,++

注意:float或double不能取余

关系运算符

逻辑运算符

位运算符

位移位运算的一般形式:

<运算量> <运算符> <表达式>其中:

​ <运算量> 必须为整型结果数值;

​ <运算符>为左移位(<<)或 右移位(>>)运算符;

​ <表达式> 也必须为整型结果数值。

赋值运算符

​ 赋值运算符为“=”,其运算的一般形式如下:

<左值表达式> = <右值表达式>

​ 赋值复合运算符其运算的一般形式如下:

<变量> <操作符>= <表达式>

C语言的特殊运算符

条件运算符”? :”

​ 是三目运算符, 其运算的一般形式是:

<表达式1> ? <表达式2> : <表达式3>

sizeof运算符

运算的一般形式:sizeof(<类型或变量名>)  

**注意:它只针对数据类型,而不针对变量!**   

C运算符的优先级


输入输出

数据输出

​ C语言中无I/O语句,I/O操作由函数实现

#include<stdio.h>

字符输出函数

​ 格式:putchar(c)

​ 参数:c为字符常量、变量或表达式

​ 功能:把字符c输出到显示器上

​ 返值:正常,为显示的代码值

格式输出函数

​ 格式:printf("格式控制串" ,输出表)

​ 功能:按指定格式向显示器输出数据

​ 输出表:要输出的数据

​ 格式控制串:包含两种信息

​ 格式shuoming:%[修饰符]格式字符,用于指定输出格式

​ 普通字符:原样输出

输出函数格式字符m.n

​ m输出数据域宽度,数据长度<m,左补空格;否则按实际输出

​ .n对实数,指定小数点后位数(四舍五入);对字符串,指定实际输出位数

字符输入函数

字符数输入函数getchar

​ 格式:getchar()

​ 功能:从键盘读一字符

​ 返值:正常,返回读取的代码值;出错或结束键盘输入,返回-1

格式输入函数

格式:scanf("格式控制串",地址表)

功能:按指定格式从键盘读入数据,存入地址表

存储单元中,并按回车键结束

返值:正常,返回输入数据个数

地址表:变量的地址,常用取地址运算符&

注意:用”%c”格式符时,空格和转义字符作为有效字符输入

输入数据时,遇到以下情况认为该数据结束:

  • 遇空格、TAB、回车

  • 遇非法输入

  • 遇宽度结束

字符串输入函数

字符串输入函数gets

​ 格式:char gets(char s)

​ 功能:从键盘输入一个以回车结束的字符串放入字符,数组中,并自动加入’\0’.

​ 说明:输入串长度应小于字符数组维数;与scanf函数不同,gets函数并不以空格作为字符串输入结束的标志。

字符串输出函数

字符串输出函数puts

​ 格式:int puts(const char *s)

​ 功能:向显示器输出字符串(输出完,换行)

​ 说明:字符数组必须以’\0’结束


输入函数留下的“垃圾”

1
2
3
4
5
6
7
8
9
10
11
12
13
#include<stdio.h>
int main(int argc, char *argv[])
{
int x;
char y;

scanf("%d", &x);
scanf("%c", &y);

printf("x=%d, y=%c\n", x, y);

return 0;
}

当执行上面程序时,容易输入一个数字,再按回车就结束了对y的输入

结果:

处理垃圾方法:

​ 1. 用getchar()清除

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include<stdio.h>
int main(int argc, char *argv[])
{
int x;
char y;

scanf("%d", &x);
getchar();
y = getchar();

printf("x=%d, y:%d, y=%c\n", x, y, y);

return 0;
}

运行结果:

第一个scanf后的getchar就把”回车”给接收了,字符y就从另外一个getchar获得,也就是回车后再次输入的字符。

​ 2. 用格式串中空格或”%*c”

控制语句

if-else语句

​ if语句概述

1
2
3
4
if(表达式)
语句块1
else
语句块2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include<stdio.h>
int main(int argc, char *argv[])
{
float score;
printf("Please input your score:");
scanf("%f", &score);

if(score < 0 || score > 100)
{
printf("error\n");
}
else{
if(score >= 90)
printf("A\n");
else{
if(score >= 70)
printf("B\n");
else{
if(score >= 60)
printf("C\n");
else
printf("D\n");
}
}
}
return 0;

}

结果为:

注意:

​ 语句块:当有若干条语句时,必须用{…}括起来

​ 表达式:

  • 一般情况下为逻辑表达式或关系表达式
  • 也可以是任意类型(包括整形、实型、字符型、指针类型)

switch语句

switch语句的基本格式:

1
2
3
4
5
6
7
8
9
10
11
switch(表达式)
{
case 常量表达式1:语句块1;
break;
case 常量表达式2:语句块2;
break;
case 常量表达式3:语句块3;
break;
...
default:语句块n+1;
}

switch语句的使用:

​ 每个常量表达式的值必须各不相同,否则将会出现矛盾。

​ 当表达式的值与case后面的常量表达式值相等时,就执行此case后面的语句。

​ switch中表达式可以是整形、字符型表达式或枚举。

​ case常量只起语句标号的作用。

​ break语句用于强行跳出switch体,一般每个case后面应有一个break语句,default分支的break可以省略。

​ 多个case可以执行一组语句。


循环结构

goto语句

当函数有很多个出口,使用goto把这些出口集中到一处是很方便的,特别是函数中有许多重复的清理工作的时候。

原因:

  • 无条件跳转易于理解
  • 可以减少嵌套
  • 可以避免那种忘记更新某一个出口点的问题
  • 算是帮助编译器做了代码优化

while语句

基本形式:

1
2
3
while(表达式){
statments;
}

do while语句

基本形式:

1
2
3
do{
statments;
}while(表达式);

打印出水仙花

for语句

一般形式:

1
2
for(表达式1;表达式2;表达式3)
{statements;}

执行过程:

  1. 先求解表达式1;
  2. 求解表达式2,若为真,则执行循环体,然后执行表达式3,再判断;若为假,则执行退出。

for语句构成循环

​ 表达式1可以省略,但循环之前应该给循环变量赋值

​ 表达式2可省略,将陷入死循环

​ 表达式3可以省略,但在循环体中增加是循环变量改变的语句

for循环99乘法表

1
2
3
4
5
6
7
8
9
10
11
12
13
#include<stdio.h>
int main(int argc,char *argv[])
{
int i;
int j;
for(i=1; i<=9;i++){
for(j=i;j<=9;j++){
printf("%d x %d=%2d ", i, j, i*j);
}
printf("\n");
}
return 0;
}

辅助控制语句

break语句

用于从循环体内跳出循环体,即提前结束循环。

break语句只能用在循环语句和switch语句中。

continue语句

结束本次循环,接着判定下一次是否执行循环

continue与break的区别:

​ continue直结束本次循环,而break终止本层循环

return语句

return语句的一般形式:return(表达式)

主要用于终止包含它的函数的执行

若终止的为主函数,则程序结束

数组概述

数组

​ 构造数据类型之一

​ 数组是具有一定顺序关系的若干个变量的集合,组成数组的各个变量称为数组的元素。

​ 数组中各元素的数据类型要求相同,用数组名和下标确定。数组可以是一维的,也可以是多维的。

一维数组

定义:所谓一维数组是指只有一个下标的数组,它在计算机的内存中是连续存储的。

C语言中,一维数组的说明一般形式:

<存储类型> <数据类型> <数组名> [<表达式>]

数组名表示内存首地址,是地址常量sizeof(数组名)是数组占用的总内村空间;编译时分配连续内存,内存字节数=数组维数*sizeof(元素数据类型);

注意事项:

C语言对数组不作越界检查,使用时要注意

关于用变量定义数组维数

一维数组的引用

​ 数组必须先定义,后使用

​ 只能逐个引用数组元素,不能一次引用整个数组

​ 数组元素表示形式:数组名[下标]

​ 其中:下标可以是常量或整型表达式

一维数组的初始化

初始化方式:在定义数组时,为数组元素赋初值

说明:

​ 数组不初始化,其元素值为随机数

​ 对static数组元素不赋初值,系统会自动赋以0值

​ 只给部分数组元素赋初值

二维数组的定义

定义方式:(声明时列数不能省略,行数可以)

数据类型 数组名[常量表达式][常量表达式];

元素的个数=行数*列数

数组元素的存放顺序:

​ 0 :a[0][0]

​ 1:a[0][1]

​ 2:a[1][0]

​ 3:a[1][1]

原因:内存是一维的

二维数组:按行序优先

二维数组元素的引用

形式:数组名[下标][下标]

二维数组元素的初始化

​ 分行初始化

​ 按元素排列顺序初始化


查找一个三行四列的最大值和行号以及列号

杨辉三角的前10行

t

字符数组和字符串

字符数组

字符数组是元素的数据类型为字符类型的数组

字符数组

​ 逐个字符赋值

​ 用字符串常量

字符串

C语言中无字符串常量,用字符数组处理字符串,字符串结束标志’\0’;

输入一个字符串,让其逆序输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
#include<stdio.h>
#define N 20
int main(int argc, char *argv[])
{
char arr[N] = {0};
int i, n;

printf("Please input a string:");
gets(arr);
n = sizeof(arr) / sizeof(char);

for(i=n-1;i >= 0;i--){
putchar(arr[i]);
}
putchar('\n');
return 0;
}

字符串函数

C库中实现了很多字符串处理函数

#include<string.h>

几个常见的字符串处理函数

求字符串长度的函数strlen

字符串拷贝函数strcpy

字符串连接函数strcat

字符串比较函数strcmp

字符串长度函数strlen

格式:strlen(字符数组)

功能:计算字符串长度

返值:返回字符串实际长度,不包括’\0’在内

\xhh表示十六进制数代表的符号

\ddd表示8进制

n =sizeof(arr) / sizeof(char)

区别

字符串拷贝函数strcpy

格式:strcpy(字符数组1,字符串2)

功能:将字符串2,拷贝到字符数组1中去

返值:返回字符数组1的首地址

说明:

-字符数组1必须足够大

-拷贝时’\0’一同拷贝

字符串连接函数strcat

格式:strcat(字符数组1,字符数组2)

功能:把字符数组2连到字符数组1后面

返值:返回字符数组1的首地址

说明:

-字符数组1必须足够大

-连接前,两串均以’\0’结束;连接后,串1的’\0’取消,新串最后加’\0’

字符串比较函数strcmp

格式:strcmp(字符串1,字符串2)

功能:比较两个字符串

比较规则:对两串从左向右逐个字符比较(ASCII码),直到遇到不同字符或’\0’结束

返值:返回int型整数

a. 若字符串1< 字符串2,返回负整数

b. 若字符串1> 字符串2,返回正整数

c. 若字符串1==  字符串2,返回零

1
2
3
4
5
6
strncpy(p,p1,n) 复制指定长度字符串
atrncat(p,p1,n) 附加指定长度字符串
strcasecmp  忽略大小写比较字符串
strncmp(p,p1,n) 比较指定长度字符串
strchr(p,c)  在字符串中查找指定字符
strstr(p,p1)  查找字符串

1
2
3
4
isalpha() 检查是否为字母字符
isupper() 检查是否为大写字母字符
islower() 检查是否为小写字母字符
isdigit() 检查是否为数字

指针

C程序设计中使用指针的作用:

使程序简洁、紧凑、高效

有效地表示复杂的数据结构

动态分配内存

得到多于一个的函数返回值1

在C语言中,内存单元的地址称为指针,专门用来存放地址的变量,称为指针变量

地址和变量

在计算机内存中,每一个字节单元,都有一个编号,称为地址。

变量是对程序中数据存储空间的抽象。

指针变量的说明

一般形式:

<存储类型> <数据类型> *<指针变量名>;

指针初始化:指针在说明的同时,也可以被赋予初值,称为指针的初始化。

指针的存储类型是指针变量本身的存储类型。

指针说明时指定的数据类型不是指针变量本身的数据类型,而是指针目标的数据类型。简称为指针的数据类型。

指针指向的内存区域中的数据称为指针的目标

如果它指向的区域是程序中的一个变量的内存空间,则这个变量称为指针的目标变量(指针的目标)。

如果p为一个指针,那么它的内容是地址量;*p是指针指向的对象,它的内容是数据;&p是指针变量占用的存储区域的地址,是个常量。

指针的赋值运算指的是通过赋值运算符向指针变量送一个地址值

向一个指针变量赋值时,送的值必须是地址常量或指针变量,不能是普通的整数(除了赋零以外)

指针赋值运算常见的有以下几种形式

把一个普通变量的地址赋给一个具有相同数据类型的指针。

把一个已有地址值的指针变量赋给具有相同数据类型的另一个指针变量。

把一个数组的地址赋给具有相同数据类型的指针。

指针运算

指针运算是以指针变量所存放的地址量作为运算量的实质就是地址的计算。

指针运算的实质是地址的计算。

指针运算的种类是有限的,它只能进行赋值运算】算术运算和关系运算

指针运算

指针的算术运算:

运算符 计算形式 意义
+ px+n 指针向地址大的方向移动n个数据
- px-n 指针向地址小的方向移动n个数据
++ px++ 指针向地址大的方向移动1个数据
px– 指针向地址小的方向移动1个数据
- px-py 两个指针之间相隔数据元素的个数

注意:

不同数据类型的两个指针实际加减整数运算是无意义的

px+n表示的实际位置的地址量是:

(px) + sizeof(px的类型) *n

px-n表示的实际位置的地址量是:

(px) - sizeof(px的类型) *n

两指针相减运算

px-py运算的结果是两指针指向的地址位置之间相隔数据的个数,因此两指针相减不是两指针持有地址值相减的结果。

s两指针相减的结果值不是地址量,而是一个整数值,表示两指针之间相隔数据的个数。

指针的关系运算

运算符 说明 例子
> 大于 px > py
< 小于 px < py
>= 大于等于 px >= py
<= 小于等于 px <= py
!= 不等于 px != py
== 等于 px == py

两指针之间的关系运算表示它们指向的地址位置之间的关系。指向地址大的指针大于指向地址小的指针。

指针与一般整数变量之间的关系运算没有意义。但可以和零进行等于或不等于的关系运算,判断指针是否为空。


指针与数组

在C语言中,数组的指针是指数组在内存中的起始地址,数组元素的地址是指数组元素在内存中的起始地址

一维数组的数组名为一维数组的指针(起始地址)

设置指针变量px的地址值等于数组指针x(即指针变量px指向数组的首元素)

注意:

指针变量和数组在访问数组中元素时,一定条件下其使用方法具有相同的形式,因为指针变量和数组名都是地址量;但指针变量和数组的指针(或叫数组名)在本质上不同,指针变量是地址

变量,而数组的指针是地址常量。

指针与二维数组

多维数组就是具有两个或两个以上下标的数组,在C语言中,二维数组的元素连续存储,按行优先存

可以把我二维数组看作由多个一维数组组成

二维数组名代表数组的起始地址,数组名加1,是移动一行元素。因此,二维数组名常被称为行地址。

行指针(数组指针)

存储行地址的指针变量,叫做行指针变量。

<存储类型> <数据类型> (*<指针变量名>)[表达式]

方括号中的常量表达式表示指针加1,移动几个数据。

当用行指针操作二维数组时,表达式一般写成1行的元素个数,即列数。

字符指针与字符串

C语言通过使用字符数组来处理字符串

通常,我们把char数据类型的指针变量称为字符指针变量。字符指针变量与字符数组有着密切关系,它也被用来处理字符串。

初始化字符指针是把内存中字符串的首地址赋予指针,并不是把该字符串复制到指针中

在C编程中,当一个字符指针指向一个字符串常量时,不能秀爱指针指针指向的对象的值

练习:不用任何字符串函数,编程实现字符串连接函数的功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include<ctype.h>
int main(int argc, char *argv[])
{
char ch[30] = "hello world";
char *q, *p = "hello world";
int i = 0;
q = p;
while(*(ch+i) != '\0'){
i++;
}
while(*p != '\0'){
*(ch+i) = *p;
i++;
p++;

}
*(ch+i) = *p;
p = q;
puts(ch);
puts(p);
return 0;
}

结果:

指针数组

所谓指针数组是指

由若干个具有相同存储类型和数据类型的指针变量构成集合

形式:<存储类型> <数据类型> *<指针数组名>[大小]

指针数组名表示该数组指针数组的起始地址

多级指针

多级指针的定义:

把一个指向指针变量的指针变量,称为多级指针变量

对于指向处理数据的指针变量称为一级指针变量,简称一级指针,而把指向一级指针变量的指针变量称为二级指针变量,简称二级指针

二级指针变量的说明形式如下

<存储类型> <数据类型> **<指针名>;

多级指针的运算

指针变量加1,是向地址大的方向移动一个目标数据。多级指针运算也是以其目标变量为单位进行偏移。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include<stdio.h>
int main(int argc, char *argv[])
{
int a[] = {3, 6, 9};
int *p[2] = {&a[0], &a[1]};
int **q;
q = p;
printf("%d %d\n", a[0], a[1]);
printf("%d %d\n", *p[0], *p[1]);
printf("%d %d %d\n", a[0], *p[0], **q);
printf("%d %d %d\n", a[1], *p[1], **(q+1));

return 0;
}

结果:

void 指针

void指针是一种不确定数据类型的指针变量,它可以通过强制类型转换让该变量指向任何数据类型的变量

一般形式:void *<指针变量名称>;

对于void指针,在没有强制类型转换之前,不能进行任何指针的算术运算

const变量

一般形式:const <数据类型> 变量名 = [<表达式>];

常量化变量是为了使得变量的值不能修改

变量有const修饰时,若想用指针间接访问变量,指针也要有const修饰。

常量化指针目标是限制通过指针改变其目标的数值,但<指针变量>存储的地址值可以修改

const修饰指针

形式:const <数据类型> *<指针变量名称>[= <指针运算表达式>]

函数的基本用法

函数是一个完成特定功能的代码模块,其程序代码独立,通常要求有返回值,也可以是空值。

一般形式:

1
2
3
4
5
<数据类型> <函数名称>(<形式参数说明>)
{
语句序列;
return[(<表达式>)];
}

<数据类型>是整个函数的返回值类型。return[(<表达式>)]语句中表达式的值,要和函数的<数据类型>保持一致。如无返回值应该写为void型

<形式参数说明>是逗号”,”分隔的多个变量的说明形式

大括号对{<语句序列>},称为函数体;<语句序列>是大于等于零个语句构成的

函数的说明就是指函数原型,其中<形式参数说明>可以缺省说明的变量名称,但类型不能缺省

函数的使用也叫函数的调用,形式:

函数名称(<实际参数>),实参就是在使用函数时,调用函数传递给被调用函数的数据。需要确切的数据

函数调用可以作为一个运算量出现在表达式中,也可以单独形成一个语句。对于无返回值的函数来讲,只能形成一个函数调用语句。

练习定义求x的n次方值的函数?

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include<stdio.h>
float Caculate(int n, float x)
{
float s=1;
int i=0;
while(i < n){
s *=x;
i++;
}
return s;
}
int main(void)
{
int a;
float b, s;
scanf("%f", &b);
scanf("%d", &a);
s = Caculate(a,b);
printf("%f\n", s);
return 0;
}

C语言控制语句

函数的参数传递

函数之间的参数传递方式:

  • 全局变量

  • 复制传递方式

  • 地址传递方式

全局变量

全局变量就是在函数体外说明的变量,在程序中的每个函数里都是可见的

全局变量一经定以后就会在程序的任何地方可见。函数调用的位置不同,程序的执行结果可能会受到影响。不建议使用。

复制传递方式

调用函数将实参传递给被调用函数,被调用函数将创建同类型的形参并用实参初始化

形参是新开辟的存储空间,因此,在函数中改变形参的值,不会影响到实参

地址传递方式

按地址传递,实参为变量的地址,而形参为同类型的指针

被调用函数中对形参的操作,将直接改变实参的值(被调用函数对指针的目标操作,相当于对实参本身的操作)

编写一个函数,统计字符串中小写字母的个数,并把字符串中的小写字母转化成大写字母

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include<stdio.h>
int str_fun(char *p);
int main(int argc, char argv[])
{
char s[] = "ghjkkh3459hj";
int n;
n = str_fun(s);
printf("%d %s\n", n, s);
return 0;
}
int str_fun(char *p)
{
int c = 0;
while(*p){
if(*p <= 'z' && *p >= 'a'){
c++;
*p -= 32;
}
p++;
}
return c;
}

数组在函数间传参

全局数组传递方式

复制传递方式

实参为数组的指针,形参为数组名(本质是一个指针变量)

地址传递方式

实参为数组的指针,形参为同类型的指针变量

编写函数,计算一个一维整型数组的所有元素的和

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include<stdio.h>
int array_sum(int data[], int n);
int main(int argc, char *argv[])
{
int a[] = {1, 3, 7, 9};
int sum = 0;
int n;
n = sizeof(a) / sizeof(int);
sum = array_sum(a, n);
printf("%d\n", sum);
return 0;
}
int array_sum(int data[], int n)
{
int ret = 0;
int i;
for(i = 0;i < n;i++){
ret +=data[i];
}
return ret;
}

结果:

删除字符串中空格

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include<stdio.h>
void del_space(char *str);
int main(int argc, char *argv[])
{
char s[] = "h el h";
puts(s);
del_space(s);
puts(s);
return 0;
}

void del_space(char *str)
{
char *s2;
s2 = str;
while(*str){
if(*str ==' '){
str++;
}
else{
*s2 = *str;
str++;
s2++;
}
}
*s2 = '\0';
}

结果:

指针函数

指针函数是指一个函数的返回值为地址量的函数

一般形式:

1
2
3
<数据类型> * <函数名称>(<参数说明>){
语句序列;
}

返回值:全局变量的地址/static变量的地址/字符串常量的地址

编写一个指针函数删除字符串空格

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include<stdio.h>
#include<string.h>
char *del_space(char *s);
int main(int argc, char *argv[])
{
char *r;
char str[] = "how are you ";
r = del_space(str);

puts(str);
return 0;
}

char *del_space(char *s)
{
char *p = s;
char *r = s;

while(*s){
if(*s ==' '){
s++;
}
else{
*p = *s;
s++;
p++;
}
}
*p = '\0';
return r;
}

结果:

编写一个指针函数,实现字符串连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include<stdio.h>
#include<string.h>
char *pstrcat(char *dest, const char *src);
int main(int argc, char *argv[])
{
char dest[30] = "ghhhhhb";
char src[] = "ljghg";
strcat(dest, src);
puts(strcat(dest, src));
puts(dest);

return 0;
}

char *pstrcat(char *dest, const char *src)
{
char *r = dest;
while(*dest){
dest++;
}
while(*src){
*dest = *src;
dest++;
src++;
}
*dest = '\0';
return r;
}

结果:

用函数指针来编写将一个整形转换成字符串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#include<stdio.h>
#include<string.h>
char *itoa(int n);
int main(int argc, char *argv[])
{
int n;
char *s;
printf("input:");
scanf("%d", &n);
s = itoa(n);
puts(s);

return 0;
}
char *itoa(int n)
{
int r, i = 0, j;
static char p[30];
while(n){
r = n % 10;
n /= 10;
p[i] = r + '0';
i++;
}
p[i] = '\0';
j = i-1;
i = 0;
while(i < j){
r = p[i];
p[i] = p[j];
p[j] = r;
i++;
j--;

}
return p;
}

结果:

递归函数

递归函数是指一个函数的函数体中直接调用了该函数自身

递归函数调用的执行过程分为两个阶段:

递归阶段:从原问题出发,按递归公式递推从未知到已知,最终达到递归终止条件

回归阶段:按递归终止条件求出结果,逆向逐步代入递归公式,回归原问题求解

编写一个递归函数n的阶乘

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include<stdio.h>
int fun(int n);
int main(int argc, char *argv[])
{
int n;
int s=0;

printf("Please input n:");
scanf("%d", &n);
s = fun(n);
printf("%d\n", s);
return 0;
}
int fun(int n)
{
if( n==0 || n==1){
return 1;
}
return n * fun(n-1);
}

结果:

写一个递归函数,计算斐波拉契数列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include<stdio.h>
int fun(int n);
int main(int argc, char *argv[])
{
int n = 0;
while(n <= 10){
printf("%d ", fun(n));
n++;
}
printf("\n");
return 0;
}
int fun(int n)
{
if( n==0 || n==1){
return 1;
}
return fun(n-2) + fun(n-1);
}

结果:

函数指针

函数指针用来存放函数的地址,这个地址是一个函数的入口地址

函数名代表了函数的入口地址

函数指针变变量说明的一般形式如下:

<数据类型> (*<函数指针名称>) (<参数说明列表>);

<数据类型>是函数指针指向的函数的返回值类型

<参数说明列表>应该与函数指针所指向函数的形参说明保持一致

(*<函数指针名称>)中,*说明为指针()不可缺省,表明为函数的指针

函数指针数组

函数指针数组是一个保存若干个函数名的数组

一般形式:

<数据类型> (*<函数指针数组名称>) [<大小>])(<参数说明列表>)

排序函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include<stdio.h>
#include<stdlib.h>
int compare(const void *, const void *);
int main(int argc, char *argv[])
{
int s[] = {89, 23, 4, 7, 23, 3};
int n, i;
n = sizeof(s) / sizeof(int);
qsort(s, n, sizeof(int), compare);
for(i=0;i<n;i++)
printf("%d ", s[i]);
puts("");

return 0;
}
int compare(const void *p, const void *q)
{
return (*(int *)p - *(int *)q);
}

结果:

unix的起源

1969年,由KenThompson在AT&T贝尔实验室发现的。使用的是汇编语言。

1970年,KenThompson和DennisRitchie使用C语言对整个系统进行加工和编写,使得Unix能够很容易的移植到其他硬件的计算机上。

GNU&GPL

GNU由Richard Stallman在1984年创建

GPL:General Public License

Linux

LInux是一种操作系统

1991年,芬兰赫尔幸基大学的学生Linus Torvals 为了能在家里的PC机上使用与学校一样的操作系统,开始编写了类UNIX。

Linux发行版本

Solaris,debian,redhat,ubuntu,SUSE,deepin等多个版本。deepin是国产深度操作系统。在国内用做服务器的大部分是redhat,centos,桌面版多半是ubuntu,零基础推荐深度系统(deepin)。

Linux体系结构

Linux操作系统的组件

  • Linux内核

  • Shell

  • 文件系统

  • 实用程序


选择命令终端窗口

目前,在桌面环境下的命令终端仿真器程序有很多,它们各有特色,都拥有各自的用户群。目前流行的终端窗口有:Xterm、Gnome-terminal、Konsole、Rxvt等。

Ubuntu默认安装的命令终端有Gnome-terminal、Xterm。

流行的两种软件包管理机制

Debian Linux首先提出的“软件包”的管理机制———Deb软件包

将用用程序的二进制文件、配置文档、main/info帮助页面等文件合并打包在一个文件中,用户使用软件包管理器直接操作软件包,完成获取、安装、卸载、查询等操作。

Redhat Linux基于这个理念推出了自己的软件包管理机制———Rpm软件包

随着Linux操作系统规模的不断扩大,系统中软件包间复杂的依赖关系,导致Linux用户麻烦不断。

Debian Linux开发出了APT软件管理器

  • 检查和修复软件包依赖关系

  • 利用Internet网络帮助用户主动获取软件包

APT工具再次促进了Deb软件包更为广泛地使用,成为Debian Linux的亮点。

软件包类型

ubuntu有两种类型的软件包:

  • 二进制软件包(deb)

  • 源码包(deb-src)

二进制软件包:它包含可执行文件、库文件、配置文件、man/info页面、版权声明和其它文档。

源码包:包含软件源代码、版本修改说明、构建指令以及编译工具等。先由tar工具归档为.tar.gz文件,然后再打包成.dsc文件。

在linux上无法根据后缀名来判断文件类型,使用file命令查看文件类型。

软件包的命名

Filename_Version-Reversion_Architecture.deb

软件包名称_软件版本-修改版本_体系架构

dpkg命令

dpkg -i 软件包名称.deb

参数 说明
-i 安装一个在本地文件系统上存在的Debian软件包
-r 移除一个已经安装的软件包
-P 移除已安装软件包及配置文件
-L 列出安装的软件包清单
-s 显示软件包的安装状态

APT工作原理

Ubuntu采用集中式的软件仓库机制,将各式各样的软件包分门别类地存放在软件仓库中,进行有效地组织和管理。然后,将软件仓库置于许多的镜像服务器中,并保持基本一致。镜像服务器就是用户软件园(reposity)。

在ubuntu系统中,使用软件源配置文件/etc/apt/sources.list列出最合适访问的镜像站点地址。

软件源配置文件只是告知ubuntu系统可以访问的镜像站点地址。但那些镜像站点拥有什么软件源并不清楚。为这些软件资源列一个清单(建立索引文件),以便本地主机查询。这就是APT软件包管理器的工作原理。

软件源

根据软件包的开发组织对该软件的支持程度,以及遵从的开源程度,划分为四类:

  • 核心:官方维护的开源软件

  • 公共:社区维护的开源文件

  • 受限:官方维护的非开源文件

  • 多元化:非官方维护的非开源软件

刷新软件源

修改了配置文件——/etc/apt/sources.list,目的只是告知软件源镜像站点的地址。但那些所指向的镜像站点所具有的软件资源并不清楚,需要将这些资源列个清单,以便本地主机知晓可以申请哪些资源。

使用”apt-get update”命令会扫面每个软件源服务器,并为该服务器所具有软件包资源建立索引文件,存放在本地的/var/lib/apt/lists/目录中。

子命令 描述
update 下载更新软件包列表信息
upgrade 将系统中所有软件包升级到最新的版本
install 下载所需软件包并进行安装配置
remove 卸载软件包
autoremove 将不满足依赖关系的软件包自动卸载
source 下载源码包
build-dep 为源码包构建所需的编译环境
dist-upgrade 发布版升级
clean 删除缓存区中所有已下载的包文件

修复软件包依赖关系

由于故障而中断软件安装过程,可能会造成关联的软件只有部分安装之后,用户就会发现该软件既不能重装又不能删除。

apt-get check检查软件包依赖关系

apt-get -f install修复依赖关系

在处理依赖关系上,apt-get会自动下载并安装具有依赖关系的软件包,但不会处理与安装软件包存在推荐和建议关系的软件包。

安装软件包

使用#apt-get install下载软件包分为四步:在使用时无需考虑软件包的版本、优先级、依赖关系等。

  1. 扫描本地存放的软件包更新列表  #apt-get update

  2. 进行软件包依赖关系检查,找到支持该软件正常运行的所有软件包

  3. 从软件源所指的镜像站点中下载相关软件包

  4. 解压软件包,并自动完成应用程序的安装和配置

重新安装软件包

#apt-get install 软件包名称 --reinstall

卸载软件包

不完全卸载

apt-get remove 软件包名称会关注那些与被删除的软件包相关的其他软件包,删除一个软件包时,将会连带删除与该软件包有依赖关系的软件包。

完全卸载

apt-get --purge remove 软件包名称命令在卸载软件包的同时,还删除该软件包所使用的配置文件。

查询软件包描述信息

使用#apt-cache show 软件包名称命令获取指定软件包的详细信息,包括软件包安装状态、优先级、适用架构、版本、存在依赖关系的软件包,以及功能描述。该命令可以同时显现多个软件包的详细信息。

获取软件包安装状态

使用“apt-cache policy 软件包名称”可以获取软件包当前的安装状态

shell简介

英文单词shell可直译为“贝壳”。“贝壳”是动物作为外在保护的一种工具。Linux中的shell就是Linux内核的一个外层保护工具,并负责完成用户与内核之间的交互。

命令是用户向系统内核发出控制请求,与之交互的文本流。

shell是一个命令行解释器,将用户命令解析为操作系统所能理解的指令,实现用户与操作系统的交互。

当需要重复执行若干命令,可以将这些命令集合起来,加入一定的控制语句,编辑成为shell脚本文件,交给shell批量执行。

选择shell

目前流行的shell主要有几种:

  • Bourne Shell(简称sh)

  • C Shell(简称csh)

  • Korn Shell(简称ksh)

  • Bourne Again Shell:能够提供环境变量以配置用户shell环境,支持历史记录,内置算术功能,支持通配符表达式,将常用命令内置简化。

shell命令格式

username@hostname:direction$

username:用户名,显示当前登录用户的账户名;

hostname:主机名,显示登录主机名;

direction:目录名,显示当前所处的路径,当在根目录下显示”/“,当前在用户主目录下显示为“~”;

一条命令的三要素之间用空格隔开;

若将多个命令在一行书写,用分号(;)将命令隔开;

如果一条命令不能在一行书写完,在行尾使用反斜杠(\)标明该条命令结束;

使用Tab键补全命令

查询命令历史

history [numberline]

显然history只能记录有限条的历史命令,默认保留500条命令

Bash Shell 将历史命令容量保存在环境变量HISTSIZE中。

echo $HISTSIZE显示历史命令容量

HISTSIZE=number修改历史命令容量

通配符

当需要命令处理一组文件,用户不必一一输入文件名,可以使用shell通配符。

通配符 含义 实例
* 匹配任意长度的字符串 1*.txt代表12.txt,1344.txt等
匹配一个长度的字符 1?.txt代表12.txt ,13.txt等
[…] 匹配其中指定的一个字符 1[ort].txt代表1o.txt,1r.txt,1t.txt
[-] 匹配指定的一个字符范围 1[a-z].txt代表1a.txt,1b.txt到1z.txt
[^…] 除了其中指定的字符,均可匹配 1[^otr].txt除了1r.txt,1o.txt,1t.txt外

管道

管道可以把一系列命令连接起来,意味着第一个命令的输出将作为第二个命令的输入,通过管道传递给第二个命令,第二个命令的输出又将作为第三个命令的输入,以此类推。就像通过使用“|”符连成一个管道。

输入/输出重定向

输入/输出重定向是改变shell命令或程序的标准输入/输出目标,重新定向到新的目标。

linux中默认的标准输入定义为键盘,标准输出定义为终端窗口。

用户可以为当前操作改变输入或输出,迫使某个特定命令的输入或输出来源为外部文件。

重定向符 含义
>file 将file文件重定向为输出源,新建模式
>>file 将file文件重定向为输出源,追加模式
<file 将file文件重定向为输入源
2>/&> 将由命令产生的错误信息输入到文件中

命令置换

命令置换是将一个命令的输出作为另一个命令的参数

1
command1 `command2`

其中,命令2的输出将作为命令1的参数。

echo

echo命令用于在标准输出———显示器上显示一段文字,一般起到提示作用。echo命令的一般语法格式:echo [-n] information

选项-n表示输出文字后不换行。提示信息字符串可以加引号,也可以不加。

/etc/passwd文件

/etc/passwd文件是系统能够识别的用户清单。当用户登录时,系统查询这个文件,确定用户的 UID并验证用户口令

/etc/group文件

包含了UNIX组的名称和每个组中成员列表

每一行代表一个组,包含4个字段;

adduser配置文件

/etc/adduser.conf

pstree

将所有行程以树状图显示,树状图将会以pid(如果有指定)或是以init这个基本进程为根,如果有指定使用者id,则树状图会只显示该使用者所拥有的进程。

文件系统类型

磁盘文件类型:指本地主机中实际可以访问到的文件系统

网络文件系统:是可以远程访问的文件系统

专有/虚拟文件系统:不驻留在磁盘上的文件系统

目前Ext4是Linux系统广泛使用的一种文件格式。在Ext3基础上,对有效性保护、数据完整性、数据访问速度、向下兼容性等方面做了改进。

最大特点是日志文件系统:可将整个磁盘的写入动作完整地记录在磁盘的某个区域上,以便在必要时回溯追踪。

SCSI与IDE设备命名

sata硬盘的设备名称是“/dev/sda”

IDE硬盘的设备名称是“/dev/hda”

如果很在意系统的高性能和稳定性,应该使用SCSI硬盘

Linux分区的命名方式

字母和数字相结合

前两个字母表示设备类型

  • hd 代表IDE硬盘

  • sd 代表SCSI或SATA硬盘

第三个字母说明具体的设备

a表示第一个硬盘

b表示第二个硬盘

交换分区

将内存中的内容写入硬盘或从硬盘中读出,称为内存交换

交换分区最小必须等于计算机的内存

可以创建多于一个的交换分区

尽量把交换分区放在硬盘分区的起始位置

链接文件

硬链接:是利用Linux中为每个文件分配的物理编号————inode建立链接。因此,硬链接不能跨越文件系统

软链接:是利用文件的路径名建立链接。通常建立软链接使用绝对路径而不是相对路径,以最大限度增加可移植性。

需要注意的是,如果是修改硬链接的目标文件名,链接依然有效;如果修改软链接的目标文件名,则链接将断开;对于一个已存在的链接文件执行移动或删除操作,有可能导致链接的断开。假如删除目标文件后,重新创建一个同名文件,软链接将恢复,硬链接不再有效,因为文件的inode已经改变。

配置IP地址

配置IP地址的方法有两种:

  • 配置静态IP:在主机进入网络之前,事先为主机设置固定的IP地址;

  • 配置动态IP:选择DHCP网络服务,在主机进入网络之后,动态随机获取IP地址。

动态IP地址获取

sudo dhclient

动态IP的获取过程

  • 客户端寻找DHCP服务器

  • 服务器提供可分配的IP地址

  • 客户端接受IP地址租借

  • 服务器确认租借IP

网络重启

sudo /etc/init.d/networking restart

IP地址存放地点

interfaces配置文件:/etc/network/interfaces

DNS客户端配置文件

resolv.conf配置文件:/etc/resolv.conf中

shell脚本的本质

shell脚本语言是解释型语言

shell脚本的本质:shell命令的有序集合

shell编程的基本过程

  1. 建立shell文件——包含任意多行操作系统命令或shell命令的文本文件;

  2. 赋予shell文件执行权限——用chmod命令修改权限;

  3. 执行shell文件——直接在命令行上调用shell程序;

shell变量

shell允许用户建立变量存储数据,但不支持数据类型(整形、字符、浮点型),将任何赋给变量的值都解释为一串字符

Bourne Shell有如下四种变量:

  • 用户自定义变量

  • 位置变量即命令行参数

  • 预定义变量

  • 环境变量

用户自定义变量

在shell编程中通常使用全大写变量,方便识别

$COUNT=1

变量调用:在变量前加$

$echo $HOME

使用unset命令删除变量的赋值

$unset COUNT

shell程序和语句

shell程序由零或多条shell语句构成。shell语句包括三大类:说明性语句、功能性语句和结构性语句

说明性语句:以#号开始到该行结束,不被解释执行

功能性语句:任意shell命令、用户程序或其它shell程序

结构性语句:条件测试语句、多路分支语句、循环语句、循环控制语句等

expr命令

算术运算命令expr主要用于进行简单的整数运算,包括加+、减-、乘\*、除/、求模%等操作。

test语句

test语句可测试三种对象:字符串 整数 文件属性

每种测试对象都有若干测试操作符

字符串测试

s1 = s2 测试两个字符串的内容是否完全一样
s1 != s2 测试两个字符串的内容是否有差异
-z s1 测试s1字符串的长度是否为0
-n s1 测试s1字符串的长度是否不为0

整数测试

a -eq b 测试a与b是否相等
a -ne b 测试a与b是否不相等
a -gt b 测试a是否大于b
a -ge b 测试a是否大于等于b
a -lt b 测试a是否小于b
a -le b 测试a是否小于等于b

文件测试

-d name 测试name是否为一个目录
-e name 测试一个文件是否存在
-f name 测试name是否为普通文件
-L name 测试name是否为符号链接
-r name 测试name文件是否存在且为可读
-w name 测试name文件是否存在且为可写
-x name 测试name文件是否存在且为可执行
-s name 测试name文件是否存在且其长度不为0
f1 -nt f2 测试文件f1是否比文件f2更新
f1 -ot f2 测试文件f1是否比文件f2更旧

结果:

结构性语句

结构性语句主要根据程序的运行状态、输入数据、变量的取值、控制信号以及运行时间等因素来控制程序的运行流程。

主要包括:条件测试语句、多路分支语句、循环语句、循环控制语句和后台执行语句等。

条件语句

1
2
3
if         [ 表达式]
then 命令表
fi

如果表达式为真,则执行命令表中的命令;否则执行fi后面的语句。if和fi是条件语句的括号语句,必须成对使用;命令表中的命令可以是一条,也可以是多条。

1
2
3
4
if 表达式
then 命令表1
else 命令表2
fi

如果表达式为真,则执行命令表1中的命令,再退出if语句;否则执行命令表2中的语句,再退出if语句。

多路分支语句

1
2
3
4
5
6
7
8
9
10
11
12
case 字符串变量 in
模式1)
命令表1
;;
模式2 | 模式3)
命令表2
;;
.......
模式n)
命令表n
;;
esac

编写一个shell脚本来判断成绩的等级

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#!/bin/bash

echo -n "Please input score:"
read S
if [ $S -lt 0 -o $S -gt 100 ]
then
echo "not in [0-100]"
exit
fi
G=`expr $S / 10`
case $G in
9|10)
echo "$S A"
;;
6|7|8)
echo "$S B"
;;
*)
echo "$S C"
esac

结果:

循环语句for

当循环次数已知或确定时,使用for循环语句来多次执行一条或一组命令。循环体由语句括号do和done来限定。

1
2
3
4
for  变量名  in  单词表
do
命令表
done

变量依次取单词表中的各个单词,每取一次单词,就执行一次循环体中的命令,循环次数由单词表中的单词数确定。命令表中的命令可以是一条,也可以是由分号或换行符分开的多条。

1
2
3
4
5
6
7
8
9
10
11
12
#!/bin/bash
sum=0
for i in `seq 1 100`
do
sum=`expr $sum + $i`
done
echo $sum

for ((i=1;i<=10;i++))
do
echo $i
done

结果:

循环语句

1
2
3
4
while  命令或表达式
do
命令表
done

while语句首先测试其后的命令或表达式的值,如果为真,就执行一次循环体中的命令,然后再测试该命令或表达式的值,执行循环体,直到该命令或表达式为假时退出循环。

while语句的退出状态为命令表中被执行的最后一条命令的退出状态。

shell函数调用

1
value_name=`function_name [arg1 arg2 ... ]`
1
2
function_name [arg1 arg2 .. ]
echo $?

函数变量作用域

全局作用域:在脚本的其他任何地方都能够访问该变量

局部作用域:只能在声明变量的作用域内访问。

声明局部变量的格式:

Local variable_name =value

GNU工具

编译工具:把一个源程序编译为一个可执行程序

调试工具:能对执行程序进行源码或汇编级调试

软件工程工具:用于协助多人开发或大型软件项目的管理

其他工具:用于把多个目标文件链接成可执行文件的链接器,或者用作格式转换的工具

GCC简介

全称为GNU CC,GNU项目中符合ANSI C标准的编译系统

编译如C、C++、Object C、Java、Fortran、Pascal、Modula-3和Ada等多种语言

GCC是可以在多种硬体平台上编译出可执行程序的超级编译器,其执行效率与一般的编译器相比平均效率要高20%~30%

一个交叉平台编译器,适合在嵌入式领域的开发编译

编译器的主要组件

分析器

汇编器

链接器

标准C库

GCC的基本用法和选项

-c 只编译,不连接成为可执行文件.c到.o
-o 确定输出文件的名称
-g 产生符号调试工具(gdb)
-O 对程序进行优化编译、连接
-O2 比-o更好的优化编译、连接
-I dirname 将dirname所指出的目录加入到程序头文件目录列表中,是在预编译过程中使用的参数
-L dirname 将dirname所指出的目录加入到程序函数档案库文件的目录列表中,是在链接过程中使用的参数。

GCC的错误

  1. 语法错误(syntex error)

  2. 头文件错误(Can no find include file head.h)

  3. 档案库错误(ld: -lm: No such file or directory)

  4. 未定义符号(Undefined symbol)

GCC编译过程

  1. 预处理(Pre-Processing)

  2. 编译(Compiling)

  3. 汇编(Assembling)

  4. 链接(Linking)

生成预处理代码

$gcc -E test.c -o test.i

生成汇编代码

$gcc -S test.i -o test.s

生成目标代码

$gcc -c test.s -o test.o

$as test.s -o test.o

生成可执行程序

将目标程序链接库资源,生成可执行程序

1
2
gcc test.s -o test
./test

调试器——gdb

首先使用gcc对test.c进行编译,注意一定要加选项‘-g’

$gcc -g test.c -o test

gdb test

(gdb) l 查看文件
(gdb) b 6 设置断点
(gdb) info b 查看断点情况
(gdb) r 运行代码
(gdb) p n 查看变量值
(gdb) n /(gdb) s 单步运行
(gdb) c 恢复程序运行
(gdb) help [command] 帮助

Gdb的使用切记点

在gcc编译选项中一定要加入’-g’

只有在代码处于“运行”或“暂停”状态时才能查看变量值

设置断点后程序在指定行之前停止

条件编译

编译器根据条件真假决定是否编译相关的代码

常见的条件编译有两种方法

  • 根据宏是否定义

    1
    2
    3
    4
    5
    #ifdef <macro>
    ......
    #else
    ......
    #endif
  • 根据宏的值

1
2
3
4
5
#if <macro>
......
#else
......
#endif

结构体

在实际的处理对象中,有许多信息是由多个不同类型的数据组合在一起进行描述,而且这些不同类型的数据是互相联系组成了一个有机的整体。此时,就要用到一种新的构造类型数据——结构体(structure),简称结构。

结构体的使用为处理复杂数据结构(如动态数据结构)提供了有效手段,而且,它们为函数间传递不同类型的数据提供了方便。

1
2
3
4
5
6
7
struct 结构体名
{
数据类型 成员名1;
数据类型 成员名2;

数据类型 成员名n;
};

在大括号中的内容也称为“成员列表”或“域表”。

其中,每个成员的命名规则与变量名相同;

数据类型可以是基本变量类型和数组类型,或者是一个结构体类型;

用分号“;”作为结束符。整个结构的定义也用分号作为结束符

结构体类型中的成员名可以与程序变量名相同,二者并不代表同一对象,编译程序可以自动化对它们进行分区。

  1. 结构体类型是用户自行构造的。

  2. 它由若干不同的基本数据类型的数据构成。

  3. 它属于C语言的一种数据类型,与整型、实型相当。因此,定义它时不分配空间,只有它定义变量时才分配空间。

结构体类型变量的定义方法

先定义结构体类型再定义变量名,这是C语言中定义结构体类型变量最常见的方式

1
2
3
4
5
struct  结构体名
{
成员列表;
};
struct 结构体名 变量名;

在定义类型的同时定义变量

1
2
3
4
struct 结构体名
{
成员列表;
}变量名;

直接定义结构体类型变量

1
2
3
4
struct 
{
成员列表;
}变量名;

大小

一个结构体变量占用内存空间的实际大小,也可以利用sizeof求出。它的运算表达式为:sizeof(运算量)//求出给定的运算量占用内存空间的字节数

其中运算量可以是变量、数组或结构体变量,可以是数据类型的名称。

结构体变量的适用形式

结构体变量名.成员名

  1. 不能将一个结构体类型变量作为一个整体加以引用,而只能对结构体类型变量中各个成员分别引用。

  2. 如果成员本身又属于一个结构体类型,则要用若干个成员运算符,一级一级地找到最低的一级成员。只能对最低级的成员进行赋值或存取以及运算。

  3. 对成员变量可以像普通变量一样进行各种运算(根据其类型决定可以进行的运算)。

  4. 在数组中,数组是不能彼此赋值的,而结构体类型变量可以相互赋值。

结构体变量的初始化

与其他类型变量一样,也可以给结构体的每个成员赋初值,这成为结构体的初始化。一中的hi在定义结构体变量时进行初始化。

struct 结构体名 变量名={初始数据表};

1
2
3
4
struct 结构体名
{
成员列表;
}变量名={初始化数据表};

结构体数组

具有相同结构体类型的结构体变量也可以组成数组,称它们为结构体数组。结构体数组的每一个数组元素都是结构体类型的数据,它们都分别是包括各个成员项。

结构体数组初始化

1
2
3
4
5
struct 结构体名
{
成员列表;
};
struct 结构体名

在定义结构体类型同时定义结构体数组

1
2
3
4
struct 结构体名
{
成员列表;
}数组名[元素个数];

直接定义结构体数组

1
2
3
4
struct
{
成员列表;
}数组名[元素个数];

结构体指针

可以设定一个指针变量用来指向一个结构体变量。此时该指针变量的值是结构体变量的起始地址,该指针称为结构体指针。

struct 结构体名 *结构指针名;

其中的结构体名必须是已经定义过的结构体类型。

当表示指针变量p所指向的结构体变量中的成员时,“(*结构体指针名).成员名”这种表示形式总是需要使用圆括号,显得不简练。因此,对于结构体指针指向的结构体成员项,给出了另外一种简洁的表示方法。

结构体指针名->成员名(*结构体指针名).成员名在意义上完全等价的。

共用体

在C语言中,不同数据类型的数据可以使用共同的存储区域,这种数据构造类型称为共用体,简称共用,又称联合体。共用体在定义、说明和使用形式上与结构体相似。两者本质上的不同仅在于使用内存的方式上。

1
2
3
4
union 公用体名
{
成员列表;
};

由于共用体中各成员的数据长度不同,所以公用体变量在存储时总是按其成员中数据长度最大的成员占用内存空间。结构体类型变量在存储时总是按各成员的数据长度之和占用内存空间。

在使用共用体类型变量的数据是要注意:在共用体类型变量中起作用的成员是最后一次存放的成员,在存入一个新的成员后原有的成员就失去作用。

在程序中经常使用结构体与共用体相互嵌套的形式。即共用体类型的成员可以是结构体类型,或者结构体类型的成员是共用体类型。

typedef

在C语言中,允许使用关键字typedef定义新的数据类型

typedef <已有数据类型> <新数据类型>

动态内存

C/C++定义了4个内存区间

  • 代码区

  • 全局变量与静态变量区

  • 局部变量区即栈区

  • 动态存储区即堆区

静态存储分配

通常定义变量,编译器在编译时都可以根据该变量的类型知道所需内存空间的大小,从而系统在适当的时候为他们分配确定的存储空间。

在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。

动态存储分配

有些操作对象只有在程序运行时才能确定,这样编译器在编译时就无法为他们预定存储空间,只能在程序运行时,系统根据运行时的要求进行内存分配。

所有动态存储分配都在栈区中进行。从堆上分配,亦成为动态内存分配。程序在运行的时候用malloc申请任意多少的内存,程序员自己负责在任何时用free释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。

malloc/free

1
2
void * malloc(size_t num)
void free(void *p)

malloc函数本身并不识别要申请的内存是什么类型,它只关心内存的总字节数。申请到的是一块连续的内存,有时可能会比所申请的空间大。其有时会申请不到内存,返回NUll。malloc返回值的类型是void ,所以在调用malloc时要显示地进行类型转换,将void 转换成所需要的指针类型。如果free的参数是NULL的话,没有任何效果。释放一块内存中的一部分是不被允许的。

注意:

删除一个指针p(free(p);),实际意思是删除了p所指的目标(变量或对象等),释放了它所占的堆空间,而不是删除p本身,释放堆空间后,p成了空悬指针,动态分配失败。返回一个空指针(NULL),表示发生了异常,堆资源不足,分配失败。

malloc 与free是配对使用的,free只能释放堆空间。如果malloc返回的指针值丢失,则分配的堆空间无法回收,称内存泄漏,同一空间重复释放也是危险的,因为该空间可能以另分配,所以必须妥善保存malloc返回的指针,以保证不发生内存泄漏,也必须保证不会重复释放堆内存空间。

动态分配的变量或对象的生命期。无名对象的生命期并不依赖于建立它的作用域,比如在函数中建立的动态对象在函数返回后任可使用。我们也称堆空间作为自由空间(free store)就是这个原因。但必须记住释放该对象所占堆空间,并只能释放一次,在函数内建立,而在函数外释放是一件很容易失控的事,往往会出错。

野指针

不是NULL指针,是指向“垃圾”内存的指针。“野指针”是很危险的。

“野指针”的成因主要有两种:

  • 指针变量没有被初始化

  • 指针p被free之后,没有置为NULL,让人误以为p是个合法的指针。

  • 指针操作超越了变量的作用范围。这种情况让人防不胜防。

make简介

工程管理器,是指管理较多的文件,也是个“自动编译管理器”,这里的“自动”是指它能够根据文件时间戳自动发现更新过的文件而减少编译的工作量,同时,它通过读入Makefiile文件的内容来执行大量的编译工作。Make将只编译改动的代码文件,而不用完全编译。

Makefile基本结构

Makefile是Make读入的唯一配置文件

由make工具创建的目标体(target),通常是目标文件或可执行文件,要创建的目标体所依赖的文件(dependency_file),创建每个目标文件体时需要运行的命令(command)。

注意:命令行前面必须是一个“TAB键”,否则编译错误为:*** missing separator. Stop.

Makefile格式:

1
2
target:dependency_files
<TAB> command

-Wall:表示允许发出gcc所有有用的报警信息

-c:只是编译不链接,生出目标文件“.o”

-o file:表示把输出文件输出到file里

Makefile

1
2
3
4
5
6
7
8
9
10
test:f1.o f2.o main.o
gcc f1.o f2.o main.o -o test
f1.o:f1.c
gcc -c -Wall f1.c -o f1.o
f2.o:f2.c
gcc -c -Wall f2.c -o f2.o
main.o:main.c
gcc -c -Wall main.c -o main.o
clean:
rm *.o test

创建和使用变量

创建变量的目的:用来代替一个文本字符串

  1. 系列文件的名字

  2. 传递给编译器的参数

  3. 需要运行的程序

  4. 需要查找源代码的目录

  5. 你需要输出信息的目录

  6. 你想做其他事情

变量定义的两种方式

  • 递归展开方式VAR=var

  • 简单方式VAR: =var

变量使用($(VAR))

用“$$”来表示“$”

类似于编程语言中的宏

用这种方式定义的变量,会在变量的定义点,按照被引用的变量的当前值进行展开

这种定义变量的方式更适合在大量的编程项目中使用,因为它更像我们一般的编程语言

自动变量

$* 不包含扩展名的目标文件名称
$+ 所有依赖文件,以空格分开,并以出现的先后为序,可能包含重复的依赖文件
$< 第一个依赖文件的名称
$? 所有时间戳比目标文件晚的依赖文件,以空格分开
$@ 目标文件的完整名称
$^ 所有不重复的目标依赖文件,以空格分开
$% 如果目标是归档成员,则该变量表示目标的归档成员名称

Make使用

直接运行make

选项 含义
-C dir读入指定目录下的Makefile
-f file读入当前目录下的file文件作为Makefile
-i 忽略所有的命令执行错误
-I dir指定被包含的Makefile所在目录
-n 只打印要执行的命令,但不执行这些命令
-p 显示make变量数据库和隐含规则
-s 在执行命令时不显示命令
-w 如果make在执行过程改变目录,打印当前目录名

Makefile的隐含规则

  • 编译C程序的隐含规则

<n>.o”的目标的依赖目标会自动推导为“<n>.c”,并且其生成命令是$(CC) -c $(CPPFLAGS) $(CFLAGS)

  • 链接Object文件的隐含规则

<n>”目标依赖于“<n>.o”,通过运行C的编译器来运行链接程序生成(一般是“ld”),其生成命令是:$(CC) $(LDFLAGS) <n>.o

$(LOADLIBES) $(LDLIBS这个规则对于只有一个源文件的工程有效,同时也对多个Object文件(由不同的源文件生成)的也有效。

Makefile的VPATH

VPATH:(虚路径)在make需要去找寻依赖关系时,在文件前加上路径,把路径告诉make。


数据结构

数据结构概念

数据结构是研究组成数据的数据元素的关系的学科。

数据结构研究的目的

通过研究数据元素的关系,帮助我们在开发软件的时候更好的组建数据模型,让数据在内存中的操作更加的流畅高效。

  • 逻辑关系

  • 存储关系

  • 运算关系

数据

数据即信息的载体,是能够输入到计算机中并且能被计算机识别、存储和处理的符号总称。

数据元素

数据元素是数据的基本单位,又称之为记录(Record)

数据项

数据元素由若干数据项组成,数据项是数据中的最小单位。

数据类型

数据类型是对数据元素取值范围与运算的限定。

数据结构(DS)可用形式化语言描述,即DS是一个二元组:DS = (D, R),其中D为数据元素的集合,R为D上关系的集合。

数据之间的相互关系

  1. 逻辑结构:

表示数据元素之间的抽象关系(如邻接关系、从属关系等)。

有四种基本的逻辑结构:集合结构、线性结构、树形结构、图状结构

  1. 存储结构

    数据的逻辑结构在计算机内的存储形式。

分为顺序存储结构、链接存储结构、索引存储结构、散列存储结构

  1. 数据运算

对数据进行的操作,如插入、删除、查找、排序等。

0%