Linux部分
由于前面已经学习了Linux,所以在这里只做简单的补充;
vi编辑器三种模式
命令行模式:
用户在用vi编辑时文件时,最初进入的该模式。课以进行复制、粘贴等操作。
插入模式:
进行文件编辑,按ESC键可以回到命令行模式。
底行模式:
光标位于屏幕底行。可以进行文件的保存、退出、查找、替换、列出行号等。
Vi光标命令
命令 | 功能 |
---|---|
h | 方向键,向左移动光标一个字符的位置,相当于键”←” |
j | 方向键,向左移动光标一个字符的位置,相当于键“↓” |
k | 方向键,向左移动光标一个字符的位置,相当于键“↑” |
l | 方向键,向左移动光标一个字符的位置,相当于键“→” |
:N | y移动光标到第N行 |
1G | 移动光标到文件的第1行 |
G | 移动光标到文件的最后一行 |
Vi的查找命令
/string 查找字符串
n继续查找
N方向继续查找
支持正则表达式
Vi替换命令
利用:s
命令可以实现字符串的替换
1 | :s/str1/str2/ #s当前行 |
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.c
或gcc hello.c -o hell
-Wall 查看警告
-o 输出可执行文件
-c 只要求编译器输出目标代码(.o文件),而不必输出可执行文件
-g 用于调试
./hell 查看输出结果(执行)
程序调试
利用_FILE_
,_LINE_
,_FUNCTION_
实现代码跟踪调试
1 | #include<stdio.h> |
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 | #include<stdio.h> |
当执行上面程序时,容易输入一个数字,再按回车就结束了对y的输入
结果:
处理垃圾方法:
1. 用getchar()清除
1 | #include<stdio.h> |
运行结果:
第一个scanf后的getchar就把”回车”给接收了,字符y就从另外一个getchar获得,也就是回车后再次输入的字符。
2. 用格式串中空格或”%*c”
控制语句
if-else语句
if语句概述
1 | if(表达式) |
1 | #include<stdio.h> |
结果为:
注意:
语句块:当有若干条语句时,必须用{…}括起来
表达式:
- 一般情况下为逻辑表达式或关系表达式
- 也可以是任意类型(包括整形、实型、字符型、指针类型)
switch语句
switch语句的基本格式:
1 | switch(表达式) |
switch语句的使用:
每个常量表达式的值必须各不相同,否则将会出现矛盾。
当表达式的值与case后面的常量表达式值相等时,就执行此case后面的语句。
switch中表达式可以是整形、字符型表达式或枚举。
case常量只起语句标号的作用。
break语句用于强行跳出switch体,一般每个case后面应有一个break语句,default分支的break可以省略。
多个case可以执行一组语句。
循环结构
goto语句
当函数有很多个出口,使用goto把这些出口集中到一处是很方便的,特别是函数中有许多重复的清理工作的时候。
原因:
- 无条件跳转易于理解
- 可以减少嵌套
- 可以避免那种忘记更新某一个出口点的问题
- 算是帮助编译器做了代码优化
while语句
基本形式:
1 | while(表达式){ |
do while语句
基本形式:
1 | do{ |
打印出水仙花
for语句
一般形式:
1 | for(表达式1;表达式2;表达式3) |
执行过程:
- 先求解表达式1;
- 求解表达式2,若为真,则执行循环体,然后执行表达式3,再判断;若为假,则执行退出。
for语句构成循环
表达式1可以省略,但循环之前应该给循环变量赋值
表达式2可省略,将陷入死循环
表达式3可以省略,但在循环体中增加是循环变量改变的语句
for循环99乘法表
1 | #include<stdio.h> |
辅助控制语句
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 | #include<stdio.h> |
字符串函数
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 | strncpy(p,p1,n) 复制指定长度字符串 |
1 | isalpha() 检查是否为字母字符 |
指针
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 | #include<stdio.h> |
结果:
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 | #include<stdio.h> |
结果:
删除字符串中空格
1 | #include<stdio.h> |
结果:
指针函数
指针函数是指一个函数的返回值为地址量的函数
一般形式:1
2
3<数据类型> * <函数名称>(<参数说明>){
语句序列;
}
返回值:全局变量的地址/static变量的地址/字符串常量的地址
编写一个指针函数删除字符串空格
1 | #include<stdio.h> |
结果:
编写一个指针函数,实现字符串连接
1 | #include<stdio.h> |
结果:
用函数指针来编写将一个整形转换成字符串
1 | #include<stdio.h> |
结果:
递归函数
递归函数是指一个函数的函数体中直接调用了该函数自身
递归函数调用的执行过程分为两个阶段:
递归阶段:从原问题出发,按递归公式递推从未知到已知,最终达到递归终止条件
回归阶段:按递归终止条件求出结果,逆向逐步代入递归公式,回归原问题求解
编写一个递归函数n的阶乘
1 | #include<stdio.h> |
结果:
写一个递归函数,计算斐波拉契数列
1 | #include<stdio.h> |
结果:
函数指针
函数指针用来存放函数的地址,这个地址是一个函数的入口地址
函数名代表了函数的入口地址
函数指针变变量说明的一般形式如下:
<数据类型> (*<函数指针名称>) (<参数说明列表>);
<数据类型>是函数指针指向的函数的返回值类型
<参数说明列表>应该与函数指针所指向函数的形参说明保持一致
(*
<函数指针名称>)中,*
说明为指针()不可缺省,表明为函数的指针
函数指针数组
函数指针数组是一个保存若干个函数名的数组
一般形式:
<数据类型> (*<函数指针数组名称>) [<大小>])(<参数说明列表>)
排序函数
1 | #include<stdio.h> |
结果:
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
下载软件包分为四步:在使用时无需考虑软件包的版本、优先级、依赖关系等。
扫描本地存放的软件包更新列表
#apt-get update
进行软件包依赖关系检查,找到支持该软件正常运行的所有软件包
从软件源所指的镜像站点中下载相关软件包
解压软件包,并自动完成应用程序的安装和配置
重新安装软件包
#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编程的基本过程
建立shell文件——包含任意多行操作系统命令或shell命令的文本文件;
赋予shell文件执行权限——用chmod命令修改权限;
执行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 | if [ 表达式] |
如果表达式为真,则执行命令表中的命令;否则执行fi后面的语句。if和fi是条件语句的括号语句,必须成对使用;命令表中的命令可以是一条,也可以是多条。
1 | if 表达式 |
如果表达式为真,则执行命令表1中的命令,再退出if语句;否则执行命令表2中的语句,再退出if语句。
多路分支语句
1 | case 字符串变量 in |
编写一个shell脚本来判断成绩的等级
1 | #!/bin/bash |
结果:
循环语句for
当循环次数已知或确定时,使用for循环语句来多次执行一条或一组命令。循环体由语句括号do和done来限定。1
2
3
4for 变量名 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 | while 命令或表达式 |
while语句首先测试其后的命令或表达式的值,如果为真,就执行一次循环体中的命令,然后再测试该命令或表达式的值,执行循环体,直到该命令或表达式为假时退出循环。
while语句的退出状态为命令表中被执行的最后一条命令的退出状态。
shell函数调用
1 | value_name=`function_name [arg1 arg2 ... ]` |
1 | function_name [arg1 arg2 .. ] |
函数变量作用域
全局作用域:在脚本的其他任何地方都能够访问该变量
局部作用域:只能在声明变量的作用域内访问。
声明局部变量的格式:
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的错误
语法错误(syntex error)
头文件错误(Can no find include file head.h)
档案库错误(ld: -lm: No such file or directory)
未定义符号(Undefined symbol)
GCC编译过程
预处理(Pre-Processing)
编译(Compiling)
汇编(Assembling)
链接(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
2gcc 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 | #if <macro> |
结构体
在实际的处理对象中,有许多信息是由多个不同类型的数据组合在一起进行描述,而且这些不同类型的数据是互相联系组成了一个有机的整体。此时,就要用到一种新的构造类型数据——结构体(structure),简称结构。
结构体的使用为处理复杂数据结构(如动态数据结构)提供了有效手段,而且,它们为函数间传递不同类型的数据提供了方便。1
2
3
4
5
6
7struct 结构体名
{
数据类型 成员名1;
数据类型 成员名2;
:
数据类型 成员名n;
};
在大括号中的内容也称为“成员列表”或“域表”。
其中,每个成员的命名规则与变量名相同;
数据类型可以是基本变量类型和数组类型,或者是一个结构体类型;
用分号“;”作为结束符。整个结构的定义也用分号作为结束符
结构体类型中的成员名可以与程序变量名相同,二者并不代表同一对象,编译程序可以自动化对它们进行分区。
结构体类型是用户自行构造的。
它由若干不同的基本数据类型的数据构成。
它属于C语言的一种数据类型,与整型、实型相当。因此,定义它时不分配空间,只有它定义变量时才分配空间。
结构体类型变量的定义方法
先定义结构体类型再定义变量名,这是C语言中定义结构体类型变量最常见的方式
1 | struct 结构体名 |
在定义类型的同时定义变量
1 | struct 结构体名 |
直接定义结构体类型变量
1 | struct |
大小
一个结构体变量占用内存空间的实际大小,也可以利用sizeof求出。它的运算表达式为:sizeof(运算量)
//求出给定的运算量占用内存空间的字节数
其中运算量可以是变量、数组或结构体变量,可以是数据类型的名称。
结构体变量的适用形式
结构体变量名.成员名
不能将一个结构体类型变量作为一个整体加以引用,而只能对结构体类型变量中各个成员分别引用。
如果成员本身又属于一个结构体类型,则要用若干个成员运算符,一级一级地找到最低的一级成员。只能对最低级的成员进行赋值或存取以及运算。
对成员变量可以像普通变量一样进行各种运算(根据其类型决定可以进行的运算)。
在数组中,数组是不能彼此赋值的,而结构体类型变量可以相互赋值。
结构体变量的初始化
与其他类型变量一样,也可以给结构体的每个成员赋初值,这成为结构体的初始化。一中的hi在定义结构体变量时进行初始化。
struct 结构体名 变量名={初始数据表};
1 | struct 结构体名 |
结构体数组
具有相同结构体类型的结构体变量也可以组成数组,称它们为结构体数组。结构体数组的每一个数组元素都是结构体类型的数据,它们都分别是包括各个成员项。
结构体数组初始化
1 | struct 结构体名 |
在定义结构体类型同时定义结构体数组
1 | struct 结构体名 |
直接定义结构体数组
1 | struct |
结构体指针
可以设定一个指针变量用来指向一个结构体变量。此时该指针变量的值是结构体变量的起始地址,该指针称为结构体指针。
struct 结构体名 *结构指针名;
其中的结构体名必须是已经定义过的结构体类型。
当表示指针变量p所指向的结构体变量中的成员时,“(*结构体指针名).成员名”这种表示形式总是需要使用圆括号,显得不简练。因此,对于结构体指针指向的结构体成员项,给出了另外一种简洁的表示方法。
结构体指针名->成员名
与(*结构体指针名).成员名
在意义上完全等价的。
共用体
在C语言中,不同数据类型的数据可以使用共同的存储区域,这种数据构造类型称为共用体,简称共用,又称联合体。共用体在定义、说明和使用形式上与结构体相似。两者本质上的不同仅在于使用内存的方式上。
1 | union 公用体名 |
由于共用体中各成员的数据长度不同,所以公用体变量在存储时总是按其成员中数据长度最大的成员占用内存空间。结构体类型变量在存储时总是按各成员的数据长度之和占用内存空间。
在使用共用体类型变量的数据是要注意:在共用体类型变量中起作用的成员是最后一次存放的成员,在存入一个新的成员后原有的成员就失去作用。
在程序中经常使用结构体与共用体相互嵌套的形式。即共用体类型的成员可以是结构体类型,或者结构体类型的成员是共用体类型。
typedef
在C语言中,允许使用关键字typedef定义新的数据类型
typedef <已有数据类型> <新数据类型>
动态内存
C/C++定义了4个内存区间
代码区
全局变量与静态变量区
局部变量区即栈区
动态存储区即堆区
静态存储分配
通常定义变量,编译器在编译时都可以根据该变量的类型知道所需内存空间的大小,从而系统在适当的时候为他们分配确定的存储空间。
在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
动态存储分配
有些操作对象只有在程序运行时才能确定,这样编译器在编译时就无法为他们预定存储空间,只能在程序运行时,系统根据运行时的要求进行内存分配。
所有动态存储分配都在栈区中进行。从堆上分配,亦成为动态内存分配。程序在运行的时候用malloc申请任意多少的内存,程序员自己负责在任何时用free释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。
malloc/free
1 | void * malloc(size_t num) |
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 | target:dependency_files |
-Wall:表示允许发出gcc所有有用的报警信息
-c:只是编译不链接,生出目标文件“.o”
-o file:表示把输出文件输出到file里
Makefile
1 | test:f1.o f2.o main.o |
创建和使用变量
创建变量的目的:用来代替一个文本字符串
系列文件的名字
传递给编译器的参数
需要运行的程序
需要查找源代码的目录
你需要输出信息的目录
你想做其他事情
变量定义的两种方式
递归展开方式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上关系的集合。
数据之间的相互关系
- 逻辑结构:
表示数据元素之间的抽象关系(如邻接关系、从属关系等)。
有四种基本的逻辑结构:集合结构、线性结构、树形结构、图状结构
存储结构
数据的逻辑结构在计算机内的存储形式。
分为顺序存储结构、链接存储结构、索引存储结构、散列存储结构
- 数据运算
对数据进行的操作,如插入、删除、查找、排序等。