一、Shell编程基础
1.Shell基础
1.Shell脚本语言是解释性语言,其本质是Shell命令的有序集合
2.shell 脚本以.sh结尾,用bash命令执行(或者./filename.sh),基本格式为:
#!/bin/bash
echo "hello world!"
...
2.Shell变量和环境变量
1.命名规则:Variable=value #中间无空格。eg:count=1
#!/bin/bash
ls -l
#命令置换符``
echo `date`
a=100;
b=200;
#$是取变量取的取值
echo "a="$a
echo "b=$b"
2.Shell变量类型:
用户自定义变量:全部大写,方便识别,加$实现调用。unset命令删除变量赋值。
位置变量(命令行参数):用作函数中参数的传递
#!/bin/bash
echo $0 #$0表示命令行的上一个命令
echo $# #$#命令行参数个数
echo $1,$2...$9 #表示命令行的1到9个参数,./test.sh 1,2,3..
echo $? #表示前一个命令的退出状态(0成功,1失败,与c相反)
预设变量:包含环境变量
环境变量:
#!/bin/bash
echo $HOME #打印用户主目录
echo $PATH #打印shell的搜索路径(/etc/environment)
3.shell的功能语句
1.shell包括三类
1.说明性语句
注释语句
2.功能性语句
1.read 标准输入读入一行,赋值给后面的参数
#!/bin/bash
read val1 val2 val3
echo $val1 $val2 $val3
2.算数运算指令expr,包括+,-,乘(/*),/,%。算术运算符左右两侧用空格隔开。
#!/bin/bash
expr 23 % 3 /* 4
sum=`expr 12 + 3 \* 5` #使用命令置换符将这一串命令赋给sum,引用命令的运行结果
expr $sum + $sum
sum=`expr $sum + 1` #相当于sum=sum+1
echo $sum
3.test 测试对象有字符串,整数,文件属性
#!/bin/bash
read str
test "$str" = "hello world" #$str为一个字符串可以不加“”,为多个变量是需要加“”
#相等,不等 -eq,-ne
#大于,大于等于 -gt,-ge
#小于,小于等于 -lt,-le
#-d 目录,-f 文件,-s 长度是否为0,-e 文件是否存在,-L 是否为符号链接文件(软链接)
3.shell结构性语句(条件,分支,循环)
1.条件语句:
#!/bin/bash
#条件语句格式
if 表达式
then 命令表1
elif
then 命令表2
else 命令表3
fi
if test -f $1 #或可写成if [ -f $1 ],中括号里为条件
then
echo "$1 is 普通文件"
fi
2.多路分支语句:case…esac和switch…case相似
#!/bin/bash
#语法结构
case 字符串变量 in
模式1) 命令表1;;
模式2|模式3) 命令表2;;
...
模式n) 命令表n;;
esac
read a b
sum=0
case $1 in
1) let sum=a+b;;
2) let sum=a-b;;
3) let sum=a*b;;
*) echo "input error"
esac
echo "sum=$sum"
3.循环语句:
#!/bin/bash
#for语法结构
for 变量名 in 单词表 #单词表可为{1..100}
do
命令表
done
for i in {1..10}
do
let sum=sum+i
done
echo "sum=$sum"
#while语法结构
while 命令或表达式
do
命令表
done
while [ $i -le 10 ]
do
sum=`expr $sum + $i` #let sum=sum+i
i=`expr $i + 1` #let i++
done
echo "sum=$sum"
4.循环控制语句:break,continue
break n 跳出n曾循环
#break n 跳出n曾循环
#continue n
#!/bin/bash
num=1
while [ $num -le $1 ]
do
count=`expr $num % 2`
if [ $count -eq 0 ]
then
num=`expr $num + 1`
continue
else
echo "$num"
num=`expr $num + 1`
fi
done
2.shell数组
#!/bin/bash
#shell数组用()表示,数组元素用空格隔开
#$@表示命令行所有参数
#array(元素1 元素2 元素3 ...)
数组定义:
a=(1 2 3 4 5 6) a=($@)
read val a=($val)
arry=($@)
数组调用
${a[$i]}
${a[@]} #遍历数组
${#a[@]} #计算数组中元素个数
#!/bin/bash
arr=($@)
sort ()
{
local arr=$1
for((i=0;i < ${#arr[@]};++i))
do
for((j=i+1; j < ${#arr[*]};++j))
do
if [ ${arr[i]} -ge ${arr[j]} ]
then
t=${arr[i]}
arr[i]=${arr[j]}
arr[j]=$t
fi
done
done
}
arr=($@)
sort "${arr[@]}"
echo ${arr[@]}
4.shell函数
1.shell函数的定义
函数名()
{
函数体
}
function 函数名()
{
函数体
}
注意:函数定义时不能有任何c语言的形参,函数必须定义在调用之前,函数只能在本shell文件中使用
#!/bin/bash
function add()
{
#expr $1 +$2 函数中的$1,$2不是保存的命令行参数,保存函数传递的实参
sum=`expr 6 + 5`
return $sum
}
2.shell函数的调用
#!/bin/bash
add #函数调用(直接函数名)
#方式1
echo "add return"$? #$?表示add函数返回的值,$?会保存函数返回值
#方式2
变量名=`函数名 参数1 参数2 参数3`
#注意:函数中没有使用return,而是使用了向终端输出的命令
SUM=`add`
echo "SUM=$SUM"
#!/bin/bash
function add()
{
sum=`expr 1 + 2`
return $sum
}
add1()
{
expr 1 + 2
}
add
echo "return value"$?
sum1=`add1`
echo "sum1=$sum1"
3.函数参数传递
#!/bin/bash
function func()
{
expr $1 + $2 #函数中的$1 $2等未知变量只能用来保存函数调用时保存的实参
}
read a b
SUM=`func $a $b` #函数调用时参数传递方式1
echo "SUM=$SUM"
function fun2()
{
RES=`expr $1 + $2`
return $RES
}
fun2 $a $b #参数传递,传递的实参,将会被函数中的$1~$9保存
echo "$a + $b = $?" #函数调用时参数传递方式2
4.局部变量
#!/bin/bash
sum=12
add()
{
echo "sum:$sum"
local sum=`expr $sum + 3` #local声明局部变量
}
add
echo "sum=$sum"
二、c语言的编译工具和调试工具
1.编译工具:将一个源程序编译成一个可执行程序
gcc编译步骤
预处理:将源文件中的头文件,宏定义变量展开替换,生成*.i文件。常用命令:gcc -E *.c 进行预处理;gcc -E *.c -o *.i 将c文件预处理生成*.i文件
分析器(编译处理):检查代码的语法结构,语法错误等。常用命令:gcc -S *.c gcc -S *.i -o *.s
汇编器(汇编处理):将汇编代码翻译成系统的机器码,生成*.o文件。常用命令: gcc -c *.c -o *.o
链接器(链接处理):目标文件,依赖库文件等链接组成一个可执行文件。常用命令:gcc *.o -o test
2.调试工具 :能对执行程序进行源码或者汇编级的调试(查看)
GDB调试:在使用gcc编译时要加上-g选项才能进行gdb调试
gcc -g *.c -o test
gdb test
查看文件:(gdb)l
在第n行设置断点:(gdb)b n
查看断点信息:(gdb)info b
删除断点:(gdb)d 断点号
运行代码:(gdb)r #遇断点暂停
查看变量n的值:(gdb)p n
单步运行:(gdb)n或(gdb)s
恢复程序运行:(gdb)c
退出:(gdb)quit或(gdb)q
查看所有变量的值:i locals
三、C语言指针
1.指针基础
1.指针:c语言中,内存单元的地址(编号)。
2.指针变量:存放地址量的变量。
3.指针的目标:指针所指向的内存区域中的数据。
4.指针的目标变量:指针所指向的内存区域。
5.指针定义: 数据类型 * 指针变量名; 在32位系统中所有指针只占4个字节。
char *c;
char ch;
c=&ch;
6.指针运算:算数运算、关系运算、逻辑运算,指针运算的实质是地址量的计算。
7.空指针:NULL
8、野指针:
没有目标变量的指针,野指针的非法使用会造成程序段错误。
野指针的定义:
char *p;
*p += 2; //错误写法,程序段错误,因为 p 是一个野指针
9、数组
1、概念:一串具有相同数据类型的元素的集合。
2、定义:
数据类型 数组名[元素个数];
eg:
int a[10];
1、数组元素下标从 0开始; 数组最后一个元素下标:元素个数 - 1
2、数组的首元素地址就是该数组的首地址;数组的数组名也就是 数组的首地址
3、数组空间大小 = 元素个数 * sizeof(类型)
10、指针和数组
int a[10] = {1, 2, 3, 4, 5, 6};
int *p;
p = &a[0]; //让指针指向数组 a
int *q = &a[0];
1、当一个指针 p 指向数组 a时,则:
a[i] <==> p[i] <==> *(a + i) <==> *(p + i)
11、指针数组
概念:
本质上是一个数组,数组中元素全为指针。
定义:
数据类型 *数组名[元素个数];
eg:
int * a[5];
若:
char buf[6] = "hello";
char str[6] = "world";
char *a[2]; //定义一个指针数组
a[0] = buf; //将字符数组buf 存储到 指针数组中
a[1] = str; //将字符数组str 存储到 指针数组中
则:
*(a[i]+j) :取得下标为i的这个数组中第j个元素
若:
char a[2][6] = {"hello", "world"};
char *p[2];
p[0] = a[0]; // ==> p[0] = &a[0][0]
p[1] = a[1]; // ==> p[1] = &a[1][0]
则 指针数组中的元素就是二维数组的行:
a[i][j] = *(p[i] + j) = *(*(p + i) + j) = p[i][j]
printf("a[0][0]");
printf("*(p[0]+ 0)");
printf("a[0][1]");
printf("*(p[0]+ 1)");
2.特殊关键字
1.const:常量化,表示被const修饰的变量不可修改
char * const p; //const修身指针变量,表示指针变量不可修改,也就是指针的指向不变, 但是可以通过*来修改指针目标的值
char const *p; //*p不可更改
int const * const p //p,*p都不能修改
2.void型指针:指向一种不确定类型的指针,可通过强制转换
3.malloc:在堆区空间开辟一片内存空间
//函数原型
void *malloc(int nbytes);//在堆区开辟nbytes个字节的空间,返回该空间首地址。头文件 #include <stdlib.h>
4.free:释放堆区空间
//函数原型
void free(void *p);//释放堆区空间
#include <stdlib.h>
#include <stdlio.h>
void main()
{
char *p;
//开辟4字节空间,将首地址赋值给p
p=malloc(sizeof(char *)); //隐式转换
p=(char *)malloc(sizeof(char *));
*p+=23;
free(p);//释放堆区空间,实质是该指针的指向标记清除,断开指针指向,堆区空间还在。
//一般会给p赋空
p=NULL;
}
3.函数
1.sscanf()
实际开发中用作数据的提取。
//函数原型
int sscanf(const char *str,const char *format,...)
//功能:从str字符串中,将数据按照format格式,赋值给后面的变量
char buf[]="light:128hum:27temp:26";
int light,hum,temp;c
sscanf(buf,"light:%dhum:%dtemp:%d");
2.sprintf()
实际开发中用作数据的组装。
char buf[128]={0};
int light=125,hum=29,temp=32;
sprintf(buf,"%d",light); //将int 125赋值到char数组中
sprintf(buf,"%d,%d,%d",light,hum,temp);//以字符的形式存在buf里面
//执行之后buf="125,29,32"
3.、函数、指针函数、函数指针
1、函数:
完成特定功能的代码模块,其程序代码要独立,通常要求有返回值。
2、函数定义的语法结构
数据类型 函数名(<形参列表>)
{
函数体
return;
}
注意:
1、函数的数据类型,就是该函数返回值的类型。
2、形参列表中多个形参时,以逗号","隔开。也可以没有任何形参
3、函数也可以没有返回值
3、函数的调用
函数名(<实参列表>);
变量名 = 函数名(<实参列表>);
注意:
1、函数调用时,实参列表被形参列表决定有无
2、函数调用时,是否保存返回值,取决于调用之后的代码中是否需要返回值
4、函数的声明
一般形式:
数据类型 函数名(<形参列表>);
注意:
1、一般情况函数声明头文件中,也就必须声明在函数被调用之前
2、函数的声明是没有函数体的,以 ; 号表示声明的结束
3、函数声明时的形参列表中,形参变量名可以不写,只保留数据类型
5、函数参数传递方式:
1、值传递(复制传递):
函数调用时,系统将实参的数值,拷贝到函数空间形参中,
则不能通过形参访问、修改实参的数值。
2、地址传递:
函数调用时,系统将实参的地址,拷贝给函数空间的形参中,
则在函数空间中可以通过形参的数值 访问、修改实参的数值。
3、指针函数:
1、概念:
返回值为地址量的函数,本质还是函数
2、定义的一般形式:
数据类型 * 函数名(<形参列表>)
{
函数体
return 地址量;
}
3、大部分字符串处理函数都是指针函数
字符串处理函数头文件:
#include <string.h>
1、字符串拷贝
char *strcpy(char *dest, const char *src);
char *strncpy(char *dest, const char *src, int count);
2、字符串的拼接
char *strcat(char *s1, const char *s2);
3、字符串查找
char *strstr(const char *s1, const char *s2);
4、字符串设置函数
void *memset(void *buf, int c, int n);
功能:
将 buf 地址上的前 n 字节数据设置成 c。
实际开发中常用来做 字符串的清零,保护数据的安全、完整性。
5、字符串大小比较函数
int strcmp(const char *s1, const char * s2);
int strncmp(const char *s1, const char * s2, int n);
4、函数指针
1、概念:
本质上就是一个指针,保存函数地址的指针变量。
函数名就是该函数的入口地址。
2、当一个指针指向函数,那么可以通过该指针,来调用这个函数。
3、定义的一般形式:
数据类型 (*指针变量名)(<参数列表>);
eg:
int (*p)(int, int);
5、函数指针数组
1、概念:
本质上是一个数组,数组中的每一个元素都一个函数指针。
2、一般形式:
数据类型 (* 指针变量名[元素个数])(<形参列表>);
eg:
int (*p[10])(int, int);
6、递归调用
函数内部自己调用自己。
编写程序实现 5!;
递推阶段:
5! = 5 * 4!;
4! = 4 * 3! ;
...
回归阶段:
int func(int n)
{
if (n == 1)
return 1;
return n*func(n-1);
}
7.字节序对齐方式sasdASsa
联合体(union)和结构体(struct)都满足字节序对齐,联合体大小是占内存最大的成员按照字节序对齐之后的大小
//一般情况下在32位系统中含有int,double按4字节序对齐,在64位中duble是8字节对齐
//类型字节数最大的字节序对齐
//若开辟空间用不完,则会把其他空间压入,若不能完全存下,则在单独开辟空间。例如:
struct stu1
{
int age; //4字节序对齐,开辟4byte字节空间
char sex; //开辟4字节空间,剩3byte
char name[10]; //10个连续的1byte空间,用掉sex的3byte,还剩7byte,4字节序对齐,还得开辟8byte空间
//sizeof(strcut stu1)=16byte
};
struct stu2
{
char name[11];//12byte
char sex;//存入name[11],剩下的1byte空间
int age;//4byte
//sizeof(struct stu2)=16byte
};
struct stu3
{
char sex; //4byte
int age; //存不下,单独开辟4byte
char name[10];//12byte
//sizeof(struct stu3)=20byte
};
struct stu4
{
//2字节序对齐
char sex;//2byte
char name[10];//用掉sex的剩余一个,还剩9byte,还得开辟10byte
short age;//剩余1byte空间,存不下,单独开辟2byte
//sizeof(struct stu4)=14byte
};
struct stu4
{
//2字节序对齐
char sex;//2byte
char name[9]//用掉sex的剩余一个,还剩8byte
short age;//开辟2byte
//sizeof(struct stu4)=12byte
};
union str1 //4byte对齐,16byte
{
char name[13];
int sex;
};
//枚举是为了替换多次宏定义,从左往右数字依次增1
//定义之后无法修改
enum week{mon=1,thu,wed,thi,fri,sta,sun};//这样定义默认1~7
enum week{mon=1,thu=3,wed,thi,fri,sta,sun};//这样定义默认1,3~8
关键字
1、extern :引用外部变量,引用外部函数
注意:
1、extern 引用的外部数据,必须定义初始化。
2、extern 修饰的变量,不能进行初始化。
3、一般情况下,extern 用来引用其他文件的变量或者函数。
2、动态内存分配
malloc/free
calloc
3、宏定义: #define
#define Max(a, b) ( (a) > (b) ? (a) : (b) )
宏定义副作用:
Max(++a, ++b); ( (++a) > (++b) ? (++a) : (++b) )
宏定义一个变量,表示一年中有多少秒:
#define second (3652460*60)ul
4、条件编译:
#if Debug
语句1
#else
语句2
#endif
1、如果 Debuf 宏值为0(假值),表示:
语句1被注释(屏蔽),不会被编译器编译;
语句2才会被编译器进行编译。
2、如果 Debug 宏值为1(真值),表示:
语句1倍编译器进行编译,
语句2被屏蔽(注释),不会被编译器编译。
二、make工程管理工具
1、“自动”的工作原理:根据文件时间戳发现新更改的文件进行编译,减少大量的编译时间。
2、Make工程管理工具,根据读入的Makefile文件内容来批量执行编译命令。
3、Make只编译改动的代码文件
4、Makefile变量定义方式:
1、递归展开式:
Var=var
2、简单方式:
Var:=var
3、?= :
dir := /var/lib
FOO ?= lib
(含义,FOO变量如果没有被定义过,则FOO的值为 lib;
如果定义过就保留原来的值)
5、自动变量
1、$< :表示第一个依赖文件
2、$^ :表示所有不重复的完整依赖文件名
3、$@ :目标文件完整名
6、其他目录下的文件进行编译
SRCS = $(wildcard ./src/*.c ./lib/src/*.c ./lib/cjson/*.c)
# wildcard :遍历后面路径下的c 文件,然后保存到 SRCS中
OBJS = $(patsubst %.c, %.o,$(SRCS))
#patsubst :将所有的c文件替换成所有的.o文件,保存到OBJS中
CC = gcc
all: $(APP)
$(APP): $(OBJS)
$(CC) -o $(APP) $^ $(LDFLAGS)