一、Linux基础
1、什么是Linux
内核:能够让计算机硬件进行工作,能够运行的最核心的功能组合。Linux内核:Linus和Linux社区的人员一起研发出来的内核,Linux内核是免费开源的内核程序。在Linux内核基础上,加上各个厂商匹配内核开发出网络管理、桌面系统、快捷功能,形成完整的操作系统。操作系统:ubuntu、debian、redhat、centos stream、arch linux。
-
Linux系统体系结构
Linux内核、shell、文件系统、应用程序
2、Linux内核
内核是Linux系统体系中的最底层,提供系统中核心功能,并允许有序访问硬件资源,管理:输入输出设备、进程执行情况、文件系统操作、内存资源管理。Linux内核支持:多任务、多用户。
3、shell
shell命令解释器,壳,用于保护Linux内核。当程序执行时,把程序的各种操作指令进行解析执行,通过shell命令解释器就让内核执行对应操作。作用:提供一个方式可以让用户和内核(操作系统)进行交互。
4、文件系统
文件系统用于管理和组织计算机存储设备上的大量文件,在Windows系统使用NTFS格式文件系统,在Linux系统中目前一般使用ext4格式文件系统。Linux中文件系统是把文件组织为一棵倒置的树,每个文件夹相当于树枝,文件相当于树叶,Linux中一切皆文件。
(1)文件夹(目录)
文件系统只有一个起始位置:根目录 / ,是整个磁盘起始位置,文件系统就是一个树型的目录结构。
(2)目录结构(存储结构)
位置 | 描述 |
---|---|
/ | 根目录 |
/boot | 存放Linux和系统的启动文件 |
/bin | 存放系统中最常用的可执行程序(命令) |
/dev | 存放系统中的设备文件(设备在Linux系统中被表示为文件),包括鼠标、键盘、磁盘等各种设备 |
/etc | 存放系统的配置文件,如:hostname存放主机名 |
/home | 普通用户的家目录的默认位置 |
/lib | 存放共享库,/lib32:存放32位的共享库,/lib64:存放64位的共享库 |
/media | 存放cd、软盘、usb临时读入的文件 |
/mnt | 挂载文件系统挂载点 |
/opt | 作为可选程序和文件存放目录 |
/proc | 作为进程(当前执行程序)的文件存放目录 |
/root | 系统管理员用户的目录(root用户) |
/sbin | 作为扩展的、更多的二进制程序存放的目录 |
/sys | 系统运行时的文件存放目录 |
/tmp | 临时文件存放目录 |
/usr | 标准库、第三方库存放目录 |
(3)文件分类
Linux中一切皆文件,存在多种文件类型:普通文件、目录文件、设置文件等等,把文件分为了7类。
文件类型 | 符号 | 说明 |
---|---|---|
普通文件 | – | 字符文件、二进制。Linux中最多的一种文件类型, 包括纯文本文件(ASCII);二进制文件(binary);数据格式的文件(data);各种压缩文件.tar、.tar.gz、.tar.bz、.tgz、.zip |
目录文件 | d | 文件夹。就是目录, 能用 cd 命令进入 |
块设备文件 | b | 块设备文件:存储。就是存储数据以供系统存取的接口设备,简单而言就是硬盘。例如一号硬盘的代码是 /dev/hda1等文件 |
字符设备文件 | c | 字符设备文件:鼠标、键盘。即串行端口的接口设备,例如键盘、鼠标等等 |
套接字文件 | s | 网络编程通信文件。这类文件通常用在网络数据连接,可以启动一个程序来监听客户端的要求,客户端就可以通过套接字来进行数据通信 |
管道文件 | p | 进程通信文件。FIFO也是一种特殊的文件类型,它主要的目的是解决多个程序同时存取一个文件所造成的错误。FIFO是first-in-first-out(先进先出)的缩写 |
符号链接文件 | l | 快捷方式。类似Windows下面的快捷方式 |
(4)文件属性
通过“ ls -l ”命令查看文件、目录的详细信息(文件属性)
-rw-rw-r-- 1 ubuntu ubuntu 656 2月 24 17:11 study.c //普通文件
drwxrwxr-x 2 ubuntu ubuntu 4096 3月 3 16:28 one //目录
说明:
文件类型 | 文件权限 | 文件硬链接数 | 文件所有者 | 文件所属组 | 文件大小 | 文件最后修改时间 | 文件名 |
---|---|---|---|---|---|---|---|
第1个字符 | 第2-10共9个字符 | 数字 | 用户名 | 用户名组 | 文件的内容所占空间大小(数字) | 如果修改了文件,最后一次修改的时间 | 文件名字 |
- | rw-rw-r– | 1 | ubuntu | ubuntu | 656 | 2月 24 17:11 | study.c |
d | rwxrwxr-x | 2 | ubuntu | ubuntu | 4096 | 3月 3 16:28 | one |
(5)文件权限
- Linux文件权限分为三组:文件所有者权限、文件所属组权限、其他用户权限
- Linux文件权限类型分为3种:读、写、执行
第一组 | 第二组 | 第三组 |
---|---|---|
用户权限 | 用户组权限 | 其他用户权限 |
读、写、执行 | 读、写、执行 | 读、写、执行 |
3位 | 3位 | 3位 |
- 第1位:读的权限,为1表示有权限,为0表示没有权限
- 第2位:写的权限,为1表示有权限,为0表示没有权限
- 第3位:执行权限,为1表示有权限,为0表示没有权限
在显示权限时:
- 如果第1位为 1,有读权限显示 r,如果第1位为 0,没有读权限显示 –
- 如果第2位为 1,有写权限显示 w,如果第2位为 0,没有写权限显示 –
- 如果第3位为 1,有执行权限显示 x,如果第3位为 0,没有执行权限显示 –
例如:
r--rw---- //r-- rw- --- : 用户可读,组可读写,其他用户没有任何权限
//权限二进制表示:
100 110 000
//权限八进制表示:
0460 //前面加数字0表示八进制
如果文件是目录:
- 文件的硬链接数,表示目录中的目录文件个数
- 文件权限
- r 读:表示能够查看目录下有什么内容;
- w 写:表示能够在目录下执行创建、删除文件;
- x 执行:表示能够访问目录
5、shell命令
使用shell命令操作Linux系统(字符界面),shell命令完成内核与用户之间的交互。shell命令就是shell解释器能够识别的命令,能够让内核执行对应操作。shell命令的输入:在终端提示符,标识命令输入的位置。
(1)路径表示
- 相对路径:
默认从当前的工作路径位置开始,找到对应的文件或目录的位置,例如:…/…/tmp/VMwareDnD/ok.txt - 绝对路径:
对于Linux文件系统,从根目录 / 开始,来表示一个文件的位置,例如:/tmp/VMwareDnD/ok.txt - 特殊路径表示:
~ :家目录,当前用户的用户目录
. :当前目录
.. :当前目录的上一级目录
/ : 根目录
(2)命令格式
command + [option] + [argument]
命令 选项 参数
//命令:shell要解析的指令内容,要执行的内容
//选项:改变命令执行动作的类型(命令功能选择),用:-选项表示,选项可以有多个:-选项1 -选项2 -选项3 -选项4 或 -选项1选项2
//参数:命令要作用的目标
-
man [command]:查看手册中该命令的使用说明
man rm //查看rm命令的详细使用说明,按q退出!
(3)常用命令
-
pwd:在文件系统中,显示当前的工作路径(绝对路径),pwd没有选项和参数
-
cd :改变切换当前的工作路径
cd 参数(新的工作路径)
-
mkdir:创建目录文件
mkdir 参数(要创建的目录路径)
- -p :参数,如果目录路径不存在,则依次创建,直到最终要创建的目录创建完成
-
touch:创建普通文件
touch 文件路径 //如果文件已经存在,则会修改文件的最后修改时间,不会清空该文件!!!
-
cat :查看显示指定文件的内容
cat 文件路径
- -E :参数,在每一行结束时添加一个 $ 符号
-
file :查看文件类型
file 文件路径
-
cp :将目录或文件拷贝到另一个路径中
cp 源文件路径 目标文件路径
- -r :参数,拷贝目录文件
-
mv :将源(目录)文件移动到另一个路径中
mv 源文件(目录) 目标文件路径
-
rm :删除文件或目录
rm 删除的文件路径
- -r :参数,删除目录以及目录中的所有文件
-
diff :比较两个文件,显示出不同的地方
diff 文件1 文件2
-
ls :列出对应文件、目录的信息,默认列出当前目录的信息
ls 路径 //ls:不写路径,默认列出当前目录
- -a :参数,列出目录中的所有文件(包括隐藏文件)
- -l :参数,列出文件、目录的详细信息(文件属性)
-
ln :创建链接文件(默认创建硬链接文件)
-
符号链接文件
也叫做软链接文件,就是对文件创建一个快捷方式,操作符号链接文件就是操作源文件,符号链接文件只是对源文件引述。- -s:参数,对源文件建立符号链接,而非硬链接
ln -s 源文件 目标符号链接文件
-
硬链接文件
在文件系统中,创建一份和源文件完全一致的独立文件,新文件和源文件会一直保持一致,删除一个文件对其他文件不会有影响ln 源文件 目标硬链接文件 //ln不加参数,默认创建硬链接文件
-
-
tar :归档与压缩
归档文件:把多个文件打包成一个文件。压缩文件:把已经打包的文件进行压缩处理-
-A :参数,新增文件到已存在的备份文件
-
-d :参数,记录文件的差别
-
-t :参数,列出备份文件的内容
-
-u :参数,添加改变了和现有的文件到已经存在的压缩文件
-
-c :参数,建立新的备份文件
-
-x :参数,从备份文件中还原
-
-v :参数,显示操作过程;
-
-f :参数,指定备份文件;这个参数是最后一个参数,后面只能接档案名!
-
-z :参数,使用gzip算法进行压缩与解压
-
-j :参数,使用bzip2算法进行压缩与解压
a、创建归档文件: 把多个文件合并成一个文件
tar -cvf 归档文件名.tar 文件1 文件2 文件3 ......
b、释放归档文件:把归档文件中合并打包的文件释放出来
tar -xvf 归档文件名.tar
c、使用gzip算法进行压缩与解压( -z )
-
压缩
tar -czvf 压缩文件名.tar.gz 文件1 文件2 文件3 ......
-
解压
tar -xzvf 归档文件名.tar.gz
d、使用bzip2算法进行压缩与解压( -j )
-
压缩
tar -cjvf 压缩文件名.tar.bz2 文件1 文件2 文件3 ......
-
解压
tar -xjvf 归档文件名.tar.bz2
e、使用zip算法压缩与解压
- 压缩
zip 压缩文件名.zip 文件1 文件2 文件3 ......
-
解压
unzip 压缩文件名.zip
-
6、用户管理
(1)修改用户密码
在工作用户下只能修改当前用户的密码,如果要修改其他用户的密码,必须是管理员权限(身份)。在命令前加上sudo,表示在当前命令执行时临时提升为管理员root权限
passwd 用户名 //只能修改当前用户的密码
sudo passwd 用户名 //提权后可以修改任意用户的密码
(2)切换用户
su 用户名 //切换用户
exit //退出当前用户
(3)添加用户
只有管理员root能添加
sudo adduser 用户名 //提权后以管理员root身份去创建新用户
注意:新添加的用户不具备提升为管理员运行的权限,不能使用sudo。此时需要切换到root用户或者已具备提权的用户去修改配置文件:/etc/sudoers,在文件最后添加:用户名 ALL=(ALL:ALL)ALL
(4)删除用户
sudo userdel -r 用户名
7、用户组管理
(1)创建用户组
sudo groupadd 用户组名
(2)把用户添加到用户组
sudo gpasswd -a 用户名 用户组
(3)把用户从用户组删除
sudo gpasswd-d 用户名 用户组
(4)删除用户组
sudo groupdel 用户组名
8、权限修改
修改文件的操作权限(读、写、执行),执行修改权限,需要文件的所属用户才能执行修
改,root用户权限也是可以更改。
(1)chmod :修改文件权限
sudo chmod 权限 文件
- 权限
- u + [r、w、x] :在用户权限中添加权限
- u - [r、w、x] :在用户权限中删除权限
- g + [r、w、x] :在用户组权限中添加权限
- g - [r、w、x] :在用户组权限中删除权限
- o + [r、w、x] :在其他用户权限中添加权限
- o - [r、w、x] :在其他用户权限中删除权限
注意:
权限是用二进制位来表示,1表示有,0表示无,可以通过chmod直接设置每一位的值。对于目录,执行权限 x 表示可以进入该目录!
//1、使用二进制方式来表示权限值,进行设置
chmod 110110110 xxx.txt //设置xxx.txt文件权限是:用户拥有读写、用户组拥有读写、其他用户拥有读写
//2、使用八进制方式来表示权限值,进行设置(八进制前要加0)
chmod 0666 xxx.txt //设置Xxx.txt文件权限是:用户拥有读写、用户组拥有读写、其他用户拥有读写
- 在创建文件时
- 普通文件权限:0664
- 目录文件权限:0775
(2)chown :改变文件所属者
要改变所属者,必须是root管理员身份运行
chown 用户名 文件
(3)chgrp :改变文件所属组
要改变所属组,必须是root管理员身份运行
chgrp 用户组 文件
9、输出重定向
(1)> :输出到某个位置的内容变为输出到指定的位置中(会删除指定位置的原先内容)
(2)>> :以追加方式输出到指定位置
注意:
-
echo :在命令行终端输出指定内容
echo 字符串
例如:
echo helloworld //输出 helloworld
echo helloworld > 1.txt //输出helloworld到1.txt文件中,1.txt原内容会被清空!
echo helloworld >> 1.txt //在1.txt的末尾添加helloworld,1.txt原内容不会被清空!
10、Linux通配符
使用一些特殊的符号来代替文件名的字符
(1)* :匹配(代替)任意长度的字符串
file_*.txt //能匹配:file_abc.txt, file_ok.txt, file_12345.txt 不能匹配:fi1e_ok123
(2)? :匹配(代替)任意的一个字符
file_?.txt //能匹配:file_1.txt, file_a.txt, file_q.txt 不能匹配:fi1e_12.txt
(3)[ … ] :匹配(代替)特定的(方括号中写出的)一个字符
file_[abc123].txt //能匹配:file_a.txt, file_1.txt, file_c.txt 不能匹配:file_5.txt
(4)[ – ] :匹配(代替)特定的(方括号指定范围)一个字符
file_[a-e].txt //能匹配:file_a.txt, file_b.txt, file_e.txt 不能匹配:file_ f.txt
(5)[ ^… ] :匹配(代替)特定的(除了方括号写出)一个字符
file_[^159a].txt //能匹配:file_2.txt, file_6.txt, file_b.txt 不能匹配:file_5.txt
11、软件包管理
软件两种使用方式:直接执行二进制程序,打开软件使用;安装包进行安装,然后进行使用。软件存在依赖(需要有其他环境的支持),软件包管理:软件以及对应的依赖说明。debian Linux软件包管理机制:deb;red hat Linux软件包管理机制:rpm。ubuntun软件包管理机制:deb
-
软件包管理器:
-
dpkg : 本地软件包管理器(离线安装,不能自动处理依赖)
-
安装本地软件包
dpkg -i 软件包名.deb路径
-
查看本机安装的软件包
dpkg -l
-
卸载已安装的软件
dpkg -r 软件名
-
卸载已安装的软件以及配置文件
dpkg -p 软件名
-
-
apt :在线联网软件包管理器(在线安装,可以自动处理依赖)
-
服务器地址列表文件:/etc/apt/sources.list
-
apt update :获取软件源地址中有哪些软件(获取软件列表)
-
在线下载安装软件
apt install 软件包名 //例如,安装网络管理工具 net-tools sudo apt install net-tools
-
卸载安装的软件
apt remove 软件名
-
-
12、TFTP服务器
类似于网盘,客户端与服务器模式:一个用于上传和下载文件的服务器。
-
安装TFTP
-
安装一个TFTP服务器:提供一个文件夹用于上传和下载
apt install tftpd-hpa
-
安装一个TFTP客户端:从服务端提供的文件夹下载文件,往服务端上传一个到所提供的文
件夹中apt install tftp
-
-
配置TFTP服务器
选择哪个文件夹作为上传、下载文件夹-
编辑配置
sudo vim /etc/default/tftpd-hpa
-
修改配置文件内容如下:
TFTP_USERNAME = "tftp" //服务名 TFTP_DIRECTORY = "/srv/tftp" //选择目录,可以自己改,例如改为: "/home/ubuntu/tftp" TFTP_ADDRESS = "0.0.0.0:69" //允许哪些客户端地址进行上传下载 TFTP_OPTIONS = "-c -l -s" //允许服务端创建文件,即客户端可以上传文件到服务器 -c:允许创建 -l:允许列表查看 -s:允许存储
-
配置服务端的文件夹路径后,需要在对应路径有一个工作目录
mkdir /home/ubuntu/tftp
-
将该工作目录权限设置为0777
chmod 0777 /home/ubuntu/tftp
-
重启TFTP服务
sudo /etc/init.d/tftpd-hpa restart //其他命令 start(开启服务)、stop(停止服务)、status(查看服务器状态)
-
-
-
客户端上传下载
-
连接服务器
tftp 服务端地址 //例如:tftp 192.168.3.3
-
上传
put 文件名 //只能上传当前目录的文件,文件名不能含路径!
-
下载
get 文件名
-
退出
quit
-
13、NFS服务器
-
安装NFS服务器
sudo apt install nfs-kernel-server
-
编辑NFS服务器配置文件
sudo vim /etc/exports
-
添加服务端共享目录
/sv/homes //提供共享的目录,可以自己改,例如改为: /home/ubuntu/nfs hostname1(rw,sync,no_subtree_check) //hostname:表示允许哪些地址来访问(写*表示任意地址); rW:可读写; sync:同步; no_subtree_check:不检查目录结构 hostname2(ro,sync,no_subtree_check) //地址可以有多个!
-
-
修改目录权限
chmod 0777 /home/ubuntu/nfs
-
重启NFS服务
sudo /etc/init.d/nfs-kernel-server restart //其他命令 start(开启服务)、stop(停止服务)、status(查看服务器状态)
-
-
安装NFS客户端
sudo apt install nfs-common
-
使用NFS客户端挂载NFS服务端提供的目录
使用mount挂载的方式,将NFS服务器的共享目录挂载到本地的一个目录,挂载成功后可以直接使用。客户端创建一个挂载点(目录),通常放在:/mnt/nfs-
挂载
mount 192.168.3.38:/home/ubuntu/nfs /mnt/nfs //服务端ip地址:192.168.3.38 //服务端共享目录路径:/home/ubuntu/nfs //挂载到客户端本地的位置:/mnt/nfs
-
解除挂载
umount /mnt/nfs //将挂载到客户端本地的位置进行解除
-
-
14、shell脚本
shell脚本是一种解释性的编程语言,可以将多个shell命令有序存放在文件中,由shell解释器
读取命令执行。多用于做命令控制,批量处理等操作。
(1)shell脚本
-
文件通常以 .sh 作为后缀名
-
shell脚本注释,是以 # 作为标识
-
给shell脚本添加可执行权限,并指定shell解释器来执行文件
chmod u+x xxxx.sh //1、添加可执行权限 bash xxxx.sh //2、用bash解释器来执行shell脚本
(2)shell脚本注意事项
- 每一行就是一个shell命令
- 编写规范和shell命令规范一致
- 严格使用标点符号,包括空格!
(3)shell脚本编程
-
a、shell变量
-
变量不需要指定类型,弱变量类型,一切变量皆认为是字符串
-
定义
变量名=值 //不能有空格
-
变量的使用
-
$变量名:本质是字符串的替换
" " //双引号,如果字符串内容有特殊字符,就解析特殊字符 ' ' //单引号,如果字符串内容有特殊字符,都认为是普通字符 ` ` //反引号,执行命令得到的结果
-
位置变量
在执行shell脚本时,给定的参数或选项将通过位置变量进行传递(不需要定义)$0:表示执行的shell脚本名称 $1:表示第一个参数 $2:表示第二个参数 ... $9 //注意:如果传入的参数超过10个:${10}、${11}、...
-
预定义变量
- $# :shell脚本执行时的参数个数
- $@ :所有位置参数值(不包括$0)
- $* :所有位置参数值(不包括$0)
- $? :上一条命令执行时的结束状态值
-
-
-
b、shell输入输出
-
输出
echo "输出内容"'输出内容'
-
输入
read 变量名1 变量名2 ......
-
-
c、算数运算
expr 算数表达式
例如:
a1=10 a2=15 c=`expr $a1 + $a2 - 5` echo $c
-
d、shell条件表达式
-
字符串判断
s1 = s2 //测试两个字符串的内容是否完全一致 s1 != s2 //测试两个字符串的内容是否不一致 -z 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
-
test命令
用于做对应的条件表达式的测试,得到结果,成立为0,失败为1,test运算结果不能用变量来存储,可以用 $? 来获取test的结果!test 表达式
-
文件测试
-d name //测试name是否为目录 -e name //测试name文件是否存在 -f name //测试name文件是否为普通文件 -L name //测试name文件是否为符号链接 -r name //测试name文件是否存在且可读 -w name //测试name文件是否存在且可写 -x name //测试name文件是否存在且可执行
例如:
//例1 if [ -d "/home/ubuntu/Desktop/test" ] then echo "当前是目录" fi //例2 $?存放上一次运算 test -d "/home/ubuntu/Desktop/test" 的结果,注意test运算结果不能用变量来存储!!! test -d "/home/ubuntu/Desktop/test" if [ $? -eq 0 ] then echo "当前是目录" fi
-
-
-
e、shell中的 if 结构
-
单分支
if [ 条件表达式 ] then 命令块 fi
-
双分支
if [ 条件表达式 ] then 成立命令块 else 不成立命令块 fi
-
多分支
-
if … elif … else 结构
if [ 条件表达式1 ] then 成立1命令块 elif [ 条件表达式2 ] then 成立2命令块 elif [ 条件表达式3 ] then 成立3命令块 else 不满足命令 fi
-
case 结构
case 变量 in 字符串1) 命令1 ;; 字符串2) 命令2 ;; 字符串3) 命令3 ;; *) //*:匹配(代替)任意长度的字符串,此处类似于default,其余条件放*中 命令n ;; esac
-
-
-
f、shell循环
-
while循环
while [ 条件表达式 ] do 满足条件执行 done
-
for循环
变量名能够从参数表中取值一次,就循环一次for 变量名 in 参数表 do 命令 done
-
-
g、函数
同C语言函数类似,是为了完成某个功能的命令块。对于函数如果有参数是不用添加的,是通过位置参数来标识,只需要在调用的时候写上值(在函数定义时不用写)-
函数定义
函数名() { 命令 }
-
函数调用
函数名 值1 值2 ......
-
函数返回值
在函数中使用echo输出内容。在调用函数时,如果没有变量来进行存储赋值,就是打印;如果有变量进行赋值,则不会打印,此时会把echo要打印的内容存储到变量中。
-
二、Linux C高级
1、调试
(1)printf打印
使用printf打印信息,来确定是否执行,或值是否正确。
(2)gdb调试
在执行时,可以查看程序执行过程中的任意信息,且能够按照我们需要的方式进行执行:可以一步一步的执行,跟踪程序的执行过程。例如:可以让程序在没有执行完的情况下,停留在某条
语句位置,查看变量的值,查看程序到底执行了哪些代码,可以监控到程序的每个执行细节。Windows:windbg;Macos:lldb;Linux:gdb。gdb可以调试:C、C++、go、opencl、object-c。如果需要调试程序,在编译时,需要支持调试。
gcc -g 文件名 //-g:编译程序时加上调试信息,例如:gcc -g test.c
-
a、打开调试功能
命令行输入gdb打开调试功能,调试可运行的可执行程序。gdb(gdb) file 可执行程序文件名 //装入想要调试的可执行文件,例如 (gdb) file a.out
-
b、退出调试
(gdb) q
-
c、gdb命令
命令 作用 r run 执行被调试的程序,会执行到第一个断点处(如果没有断点则执行到程序结束) b **break **设置断点在源代码中指定某一行设置断点:b xxxx c continue 当程序在某一个断点暂停,使用这个命令就继续执行,直到下一个断点或程序结束 n next 程序执行一行代码,执行当前行的代码 s step 执行一步代码,通常是执行一行,但是如果这一行是函数,就表示进入函数执行 p print 打印变量的值, p xxx:表示打印xxx这个变量的值 d delete 删除断点:delete xxx(断点号) info 查看信息,比如查看断点、局部变量 :info b 、info locals start 执行到main函数的第一句 list list n1 n2 :列出指定区域(n1到n2之间)的代码 注意:
gdb tui:直接将屏幕分成两个部分,上面显示源代码,下面显示调试信息//方法一 gdbtui -q 需要调试的程序名 //或用gdb -tui 需要调试的程序名 //方法二 直接使用gdb调试代码,先运行(start),然后使用切换键 Ctrl + X + A 调出 gdb tui
2、指针与数组
程序的执行都在CPU中执行,在磁盘上的程序 需要把程序的指令拿到cpu中执行,效率不高,在计算机 中存在一个存储设备,用于存储当前正在执行的程序:内存
在程序执行时,所有内容都存储在内存中,每一个字节存储单元都分配了一个编号,把这个内存的编号叫做地址(内存地址)
说明:在程序中,定义一个变量(int a=10),将数据10存储到变量a中,a变量就是所占用4个字节空间的数据,占用对应的内存空间4个字节(每个字节都有地址),那么数据存储到某个地方,就涉及到地址。依靠变量的地址,可以访问变量中的数据。存储数据,需要存储的内存地址。定义一个变量要占用内存空间,内存空间由地址来区别,所以变量名也只是表示了不同的空间。程序中通过一个变量名来定义变量,在内存中使用内存空间来存储数据,变量就是表示这个空间的数据,代表存储在内存空间中某个地址的数据内容。
(1)指针
内存地址也是一个数据,可以用一个变量来存储内存地址。这种存储地址的变量就叫做指针变量(指针)。指针就是存储一个数据内容的内存地址的一种数据类型(指针类型),即指针的内容就是另一个变量的地址。注意:指针存储地址,地址没有类型,只有地址中存储的数据有类型。
- 指针的两层含义
- 指针首先是一个变量,就拥有变量的所有属性:值和类型。它的类型是指针,值就是另一个变量的地址。指针变量也需要内存空间,存放其它变量的地址。
- 指针的指向:存储哪个地址,就表示指向地址对应的空间
(2)指针变量定义
在定义指针时,存储变量的地址,指针存储地址后,怎么知道存储的地址对应的空间到底是什么类型数据如:p=0x10地址,怎么知道0x10地址对应的空间中是什么数据。所以定义指针变量时,需要确定指针存储的地址对应空间的数据类型。
-
定义指针变量
指向数据类型 * 指针变量名
- 指向数据类型:是指这个指针变量指向的数据类型,即指针变量存储地址,指针到底存储的是哪种数据类型数据的地址
- * :在定义指针变量时,表示这是一个指针变量
- 指针变量名 :指针变量的名字
-
变量地址
- & :取地址运算符
- &变量名 :得到变量的地址
-
间接取值符
- * :取地址对应空间的值
- * 指针变量 :取指针变量存储的地址,对应空间的值,会按照定义时指针变量指向的类型来获取!
int a = 10;
float * p;
p = &a;
printf("%f\n",*p); //按照float类型去取值
(3)指针的运算
指针变量是可以存储数据的,但是存储的数据是地址,对于指针的运算只能有部分运算,对于指针的运算也是对地址的运算。
-
px表示指针变量
-
加法( + )
px + n :指针px向着地址增大的方向,移动n个指向数据类型大小的地址 -
减法( - )
px - n :指针px向着地址减小的方向,移动n个指向数据类型大小的地址-
特殊的减法
px1 - px2 :指针减指针,要除以指针指向的类型,指两个地址间隔多少个数据int *px1,*px2; px1 = 0x1154; px2 = 0x114C; px1 - px2 = (0x1154-0x114C)/sizeof(int)= 8/4 = 2
-
-
自增( ++ )
- px++ :先使用px指针变量,然后 px = px + 1;先用px指针变量值(地址),然后px指针向地址增大方向移动一个数据类型大小存储到px指针变量中
- ++px :先 px = px + 1,然后使用px指针变量;先px指针向地址增大方向移动一个数据类型大小存储到指针变量中,然后使用px指针变量值(地址)
-
自减( - - )
-
px- - :先使用px指针变量,然后 px = px - 1;先用px指针变量值(地址),然后px指针向地址减小方向移动一个数据类型大小存储到px指针变量中
-
- -px :先 px = px - 1,然后使用px指针变量;先px指针向地址减小方向移动一个数据类型大小存储到指针变量中,然后使用px指针变量值(地址)
-
-
(4)指针变量的大小
对于指针类型,由于指针在定义时说明的是存储哪种变量的地址,存储的数据类型和数据的地址不是同一个东西。不管是任意类型的指针变量,指针变量的大小都是一样的,是受计算机的内存限制:32位系统,占4字节;64位系统,占8字节。在同一系统中:sizeof(int *) == sizeof(double *) == sizeof(char *) == 4 或 8 。
int a;
double b;
int * p;
double * q;
p = &a;
q = &b;
printf("%ld\n",sizeof(p)); //求p这个变量对应的类型的大小,64位系统,为8字节
printf("%ld\n",sizeof(q)); //求q这个变量对应的类型的大小,64位系统,为8字节
printf("%ld\n",sizeof(int *)); //求int *这个指针变量对应的类型的大小,64位系统,为8字节
printf("%ld\n",sizeof(double *)); //求double *这个指针变量对应的类型的大小,64位系统,为8字节
(5)普通指针与一维数组
-
一维数组:在内存中数组的元素是一段连续的空间,在这段空间中每个数据元素占用对应大小,元素与元素相邻。
访问数组时,由于数组是连续的,如果指针变量存储了数组元素的地址,指针+1/-1得到当前数组元素后一个/前一个的地址(a[0]和a[1]数据元素地址是连续的,相隔就是一个数据类型大小)。
a、指针变量可以访问数组
只要指针存储数组第零个元素的地址,就可以访问整个数组
int * p = &a[0];
p+1 == &a[1];
p+2 == &a[2];
*(p+n) == a[n];
b、数组可以进行指针访问(数组当作指针)
对于数组而言,数组名有一定特殊含义,表示数组的首地址(数组中第零个元素的地址)
- &a[0] :数组首地址 ,**&a[0] == a **;
- a+n :移动n个数组元素大小的地址,可以移动到数组的每个元素的地址
- *(a+n) :取对应地址中的数据,*(a+n) == a[n];
例如:
int a[5];
*(a+2) == a[2];
c、指针变量可以当做数组访问(指针当作数组)
-
指针变量存储地址,可以存储数组首地址
int a[5]; int * p = a; //或 int p = &a[0];
-
数组名是数组首地址,a[i]访问
a == p; a[i] == p[i];
d、指针和数组在本质上还是存在区别
数组名还可以表示整个数组(有整个数组空间存储数据);指针表示存储地址(指针大小为4/8,只是存储地址而已)。
- 数组名是一个常量,表示数组首地址,不可赋值!!!
- 指针是一个变量,可以存储数组首地址,也可以存储其他地址
(7)普通指针与字符数组
对于字符数组,如果是按照字符串进行操作(整体操作,‘\0’ 结束),字符数组按照字符串的操作方式,只需要字符数组的首地址就可以操作一个字符串。指针:存储地址,存储字符数组的首地址,这个指针就可以当作数组名操作(指针可以代表数组名,即数组首地址)。
char buf[10] = "hello";
char * p = buf; //p就代表字符数组首地址
scanf("%s",p); //输入字符串到buf数组中
//for循环的特色用法
for(int i=0; buf[i] != '\0';i++); //直接找字符数组中'\0'的下标位置
-
常量字符串
“hello” :常量字符串。只要在程序中定义常量字符串,则常量字符串也会存储在内存中。定义字符串时,常量字符串就表示常量字符串的首地址,只能通过指针来存诸常量字符串的首地址。char * p = "常量字符串"; //p就是存储了这个常量字符串的首地址
(8)多级指针与指针数组
-
a、多级指针
-
一级指针变量存储普通变量的地址
-
二级指针变量存储一级指针变量的地址
-
三级指针变量存储二级指针变量的地址
int a 10; int * p = &a; //p存储&a int * * q = &p; //q存储&p int ** * z = &q; //z存储&q printf("%ld\n",sizeof(p)) //8 printf("%ld\n",sizeof(q)) //8 printf("%ld\n",sizeof(z)) //8 // *z == q // **z == *q == p // ***z == **q == *p == a
-
b、指针数组
是一个数组,数组中的每个元素是指针类型,可以说成是”指针的数组”,“指针”修饰这个数组。二级指针可以和指针数组相互替换使用。指针数组还是数组,数组名表示数组首地址,即第0个元素的地址。
-
定义
指针类型 数组名[ 元素个数 ];
指针类型:指向数据类型 *
例如://存储5个整型的地址,在64位系统中,每个元素的大小都为8字节 int * a[5]; //a数组有5个元素,每个元素都是指针,指针存储int类型的地址 char *arr[4] = {"hello", "world", "shannxi", "xian"}; //arr就是一个指针数组,它有4个元素,每个元素是一个char *类型的指针,这些指针存放着其对应字符串的首地址。
(9)数组指针和二维数组
a、二维数组
数组的每一个元素也是一个数组,就把这种数组叫做二维数组。二维数组中每个数组元素是一个一维数组,是一维数组的集合;数组元素是一维数组,是普通数据的集合。
一维数组元素类型 二维数组名[二维数组元素个数][一维数组元素个数]
b、数组指针
是一个指针,存储地址,用于存储整个数组的地址,指针类型为数组地址类型,指向整个数组。
-
定义
元素类型 (*指针变量)[元素个数];
例如:
int (*p)[5]; //p是一个指针,存放int[5]这种数组的地址 //具体使用案例 int a[3][5]; for(int i=0;i<3;i++) { for(int j=0;j<5;j++) { a[i][j]=1; } } // *(*(a+i)+j); //可用于访问数组中元素 int (*p)[5] = a ; //数组指针,a == &a[0],表示a[0]这个一维数组的地址 for(int i=0;i<3;i++) { for(int j=0;j<5;j++) { printf("%d ",*(*(p+i)+j)); } printf("\n"); }
说明:当a+1时:移动二维数组元素的大小;当a[0]+1时:移动一维数组的元素大小。
3、指针在函数的使用
(1)指针在函数中的定义使用
(2)指针作为函数的参数使用
函数的形式参数为指针(地址),在调用时传递地址,可以得到对应空间的地址,可以通过地址访问对应内存空间。
-
定义
返回类型 函数名( 指向类型1 *指针变量名1, 指向类型2 *指针变量名2, ... ) { 函数体; return 返回值; }
-
调用
函数名( 地址1, 地址2, ... );
注意:
如果函数的参数是数组类型,在调用时实参就需要数组,但是对于数组而言,实参的添加只能是数组名,形式参数的指针就自动退化为对应的指针类型。例如:
//函数中形参的指针会自动退化 //退化前 退化后 int a[10] --> int *a int *a[10] --> int **a int a[3][5] --> int (*a)[5]
(3)指针类型作为函数的返回值
函数的返回值是指针:也叫做指针函数,函数的结果为一个数据的地址。
指向类型 *函数名()
{
函数体;
}
(4)函数指针
在执行程序时,函数在使用时也是存储在内存中(函数就有存储的地址),所以在调用函数时也是找到函数的地址然后执行函数的内容。函数是存在地址的,函数名就表示函数的地址。函数指针:用于存储函数的地址的指针变量。由于函数的返回值,参数列表的类型都可能各不相同,一个函数都由这几个部分影响,对于函数而言,不同返回值,不同参数列表代表不同的函数类型。
-
定义
返回类型 (*函数指针名)(参数列表);
-
调用
函数指针名(实参列表); //调用函数指针对应的函数
例如:
//函数add,求和 int add(int a,int b) { return a + b; } //函数指针定义 int (*p)(int a,int b); //p是函数指针名 p = add; //或者直接写: int (*p)(int a,int b) = add; printf("%d\n",p(10,20));
4、其他指针
(1)空指针
在内存中,地址 0 是不可用地址,在定义变量或分配空间时也不可能申请到 0 这个地址(系统规定0x0这个地址不允许任何程序进行访问),通常就使用 0 这个地址来作为定义指针之后的初始值,把 0 这个地址叫做空指针:NULL == 0x0
例如:
int *p = NULL;
(2)野指针
指针存储的地址不明确(指向的空间不清楚),这个地址对应的空间是否能够在程序中操作不清楚。
例如:
int *z;
*z = 10; //错误,不确定操作哪个空间
(3)万能指针( void * )
int *,float *,都只能存储对应类型的地址。void:空类型,不存在这个类型的大小。指针大小是固定的4字节(32位系统)或8字节(64位系统)。void *:空类型指针,也叫做万能指针,一种指针类型,可以存放任意类型的地址,也可以把指针值赋值给任意其他类型的指针变量,注意:不能用 * 取对应地址的值。
int *p;
int a;
void *q = &a;
float b;
q = &b;
p = q;
//不能用*取对应地址的值,因为不知道所存数据所占字节的大小,故不能取出!!!
*q; //错误
*p; //正确,按int类型(4个字节)去取数据
5、define和typedef
(1)typedef
指进行类型替换,用一串字符来表示代替已有的类型。
-
语法
typedef 被替换的类型 新命名; //把被替换的类型,用新命名来表示
例如:
typedef int * int_p; int_p p1,p2; //p1,p2都是指针 //函数指针类型替换 char * ok(int a,int *p) { return NULL; } typedef char * (* func_p )(int a,int *p); //用func_p代表这个函数指针 func_p m; //定义函数指针m m = ok;
(2)define
内容的替换(原样替换),是什么内容就原样替换出来,也叫做宏定义。
#define 定义的名字 被替换的内容
-
#define可以带参数
使用#define进行替换时,定义的名字还可以携带一些参数,在被替换的内容中进行使用。#define 名字(参数) 被替换的内容(使用参数)
注意:
带的参数一定要加括号(),否则极其容易因为运算符优先级的问题导致运算出错!!!
例如:
#define N 10 //表示用N来替换10,只要之后使用N就是使用10
//注意
#define p_int int *
p_int a,b; //等价于 int * a,b; //其中a为指针,b为整型
//#define带参数
#define ADD(A,B) (A)+(B) //注意参数加括号
ADD(1,2); //结果为3
#define MAX(a,b) a > b ? a : b
Max(2,6); //结果为6
//参数不加括号,错误演示!
#define MUL(C,D) C * D //此时参数未加括号
MUL(1+2,3+4); //此时结果不是3*7=21,而是 1+2 * 3+4 = 11
6、存储类型关键字
C语言内存分区示意图,从栈区到代码区,是从高地址到低地址。栈向下增长,堆向上增长。。
内存位置 | 描述 | 权限 |
---|---|---|
栈区 | 栈区(stack):向下增长(地址由高到低生长)、先进后出,由编译器自动分配释放,存放:局部变量、const修饰的局部变量、形参、返回值 | 可读可写 |
堆区 | 堆区(heap):向上增长(地址由低到高生长)、进出随意,由程序员分配内存和释放,通过调用函数:malloc()和free() | 可读可写 |
全局(静态)区 | 分为未初始化和已初始化全局/静态区,如:全局变量、static修饰的静态变量 | 可读可写 |
常量区 | 用于初始化变量的常量,const修饰的全局变量、如字符串“ABCD”、数字123等 | 只读 |
代码区 | 存放程序的代码 | 只读 |
-
存储类型关键字
定义的变量的位置-
auto
在局部变量中使用,表示自动存储管理(自动分配,自动释放),表示在栈区存储局部变量,如果局部变量没有添加存储类型,默认添加auto。(auto) 数据类型 变量名;
-
static
静态数据类型,表示把数据存储在数据段中的静态区(也叫全局区)。static修饰局部变量:表示局部变量不再是默认的存储类型(auto),而是存储到静态区。变量的生命周期不再是语句块结束而结束,生命周期延长到整个程序结束,作用域不变。注意:
- 如果遇到多次创建同一个静态变量,不会重复定义初始化,使用之前创建的静态变量!
- static修饰全局变量:只能在本文件内使用!
- static修饰的变量在定义时若不赋初值,默认值为0
-
register
寄存器数据类型,把数据存储到cpu中的存储单元,在执行程序时,如果能够存储到cpu就进行存储,若cpu已经不能分配,则当作普通存储类型auto存储。 -
extern
提供一个全局变量的引用,在其他文件中定义一个变量,现在在当前文件要使用其他文件定义的变量,需要把当前文件的变量名指向之前定义过的全局变量存储位置。
注意:-
定义全局变量时,即使不加extern关键字,编译器也会默认添加extern
-
extern修饰的全局变量不允许赋值,只能读!
//在文件1.c中,定义全局变量 a = 10 int a = 10; //在文件2中,引用 a extern int a; //正确 extern int a = 20; //错误,不允许赋值!!!
-
-
const
修饰变量,表示变量不可修改。int a = 10; const int b = 20; b = 30; //错误,定义变量使用const修饰,不能修改 const int * p; //表示*p不能修改,但是p这个指针可以修改 *p = 30; //错误,p是指针可以修改,但是*p不能修改 p = &a //正确 int * const q = &a; //表示指针变量不能修改,但是*q可以修改 q = &b; //错误,不能修改指针q *q = 20; //正确,指针指向的空间值可以修改 int const * const z = &a; //指针z和*z均不能修改 z = &b; //错误,不能修改为其他地址 *z = 20; //错误,z存储地址后,不能修改这个地址对应空间的值
-
7、自定义类型(构造类型)
有些数据的表示不能够用单一的数据类型就能表示出来,C语言中就赋予类型的扩展型,可以按照c语言允许的方式去自己添加类型,这种叫做构造类型。
(1)结构体类型(struct)
把基本数据类型复合在一起构成新类型,结构体类型就是由多个部分构成,共同组合表示一个完成的信息内容。
-
声明结构体
struct 结构体名 { 数据类型1 成员名1; 数据类型2 成员名2; 数据类型3 成员名3; ...... };
-
定义结构体变量
struct 结构体名 变量名;
-
结构体变量的使用
结构体变量是一个整体,包含了多个成员,需要单独对构体变量的成员进行操作。-
. :成员运算符,用于访问结构体变量中的某个成员
结构体变量名.成员名 //访问结构体变量中某个成员
-
-
结构体变量初始化
struct 结构体名 变量名 = { 值 }; //全部初始化,所有成员都要赋值 struct 结构体名 变量名 = { .成员名1=值1, .成员名2=值2, .成员名3=值3, ... }; //部分初始化,可以只对部分成员赋值
-
使用结构体指针访问结构体变量
struct 结构体名 *指针名; 指针名->成员名 //通过结构体指针访问结构体变量中某个成员
-
- 结构体变量的首地址,必须是结构体变量中的“最大基本数据类型成员所占字节数”的整数倍。(对齐)
- 结构体变量中的每个成员相对于结构体首地址的偏移量,都是该成员基本数据类型所占字节的整数倍。(对齐)
- 结构体变量的总大小,为结构体变量中“最大基本数据类型成员所占字节数”的整数倍(补齐)
例如:
struct Test1 { int b; double c; long d; }Test1; printf("size = %d\n", sizeof(Test1)); //size = 12
(2)联合体(共用体)类型(union)
和结构体类似,也是多个成员复合的类型(多个成员组成),多个成员共用同一份内存空间,每个成员没有单独的空间。空间大小是最大成员的空间能够整除共用体中最大的数据类型。在使用共用体时,同一时间只能使用一个成员,当前成员使用完,才能去使用其他成员。
-
声明联合体
union 联合体名 { 数据类型1 成员名1; 数据类型2 成员名2; 数据类型3 成员名3; };
(3)枚举类型(enum)
在使用之前就已经把类型能够取值的范围一一列举出来,在定义类型变量时变量就只能从中取值,这种类型就叫做枚举类型。每个枚举元素可以用一个标识符来表示,也可以为它们指定一个整数值,如果没有指定,那么默认从 0 开始递增。
-
声明枚举类型
enum 枚举名 { 成员1, 成员2, 成员3, ..... };
注意:
定义枚举变量,就只能在成员范围内取值,每个成员都是一个整数值。当没有指定值的枚举元素,其值为前一元素加 1。enum DAY { MON, TUE, WED, THU, FRI=100, SAT, SUN }; enum DAY day; //MON的值为0,TUE的值为1,WED的值为2,THU的值为3,FRI的值为100,SAT的值为101,SUN的值为102
8、字符串的相关函数
字符串的拷贝:strcpy、strncpy,求字符串的长度:strlen,比较两个字符串是否相等:strcmp、strncmp
#include <string.h>
//拷贝字符串
char *strcpy(char *dest,const char *src); // 把src所指向的字符串复制到dest
char *strncpy(char *dest,const char *src,size_t n); //复制src字符串的前n个字符至dest
//求字符串的长度
size_t strlen(const char *s);
//比较两个字符串是否相等,返回值: 0表示相等,非0表示不相等
int strcmp(const char *s1,const char *s2);
int strncmp(const char *s1,const char *s2,size_t n); //比较两个字符串前n个字符是否相等,返回值: 0表示相等,非0表示不相等
-
sizeof和strlen的区别
sizeof strlen sizeof运算符是用于计算一个变量或数据类型所占用的字节数。对于一个字符串,sizeof返回的是整个字符串所占用的字节数,包括末尾的空字符’\0’。 strlen函数是用于计算字符串中实际字符的数量,不包括字符串末尾的空字符’\0’,返回的是一个size_t类型的整数。strlen函数只能用于计算字符串中实际字符的数量,而不能用于计算其他类型的数组长度。如果要计算整型数组的长度,可以使用sizeof运算符
9、动态内存
在C语言内存分配中,存在一段内存空间,由我们程序员自己来申请内存空间进行使用,申请多大,什么时候申请,什么时候释放都是通过我们写的程序决定,叫做动态内存(堆区)
-
申请内存(malloc)
#include <stdlib.h> void * malloc(size_t size);
-
size_t size :参数,申请的空间大小(字节)
-
void * :返回值,万能指针,申请的空间首地址,申请失败则返回 NULL
例如:
//在堆空间申请空间使用 int *p = malloc( sizeof(int) * 10 ); //申请40个字节 for(int i 0;i<10;i++) { *(p+i) = 1; //p[i] = 1; } //结构体使用 struct stu * q = malloc( sizeof((struct stu) ); strcpy(q->name,"xiaoming"); printf("%s",q->name);
-
-
释放内存(free)
#include <stdlib.h> //释放已经申请的空间 void free(void *ptr);
- void *ptr :参数,万能指针,表示释放的空间首地址
10、多文件编程
编写程序时,通常一个文件来编写程序,会造成文件中代码非常庞大不利于阅读,通常程序会使用多个文件来编写、编译,即多文件编程。
-
编写头文件,进行声明。头文件存放各种声明内容,头文件一般命名为:xxx.h
//头文件中内容 #ifndef __TEST_H #define __TEST_H //各种函数声明 #endif
-
编译时,多个文件都进行编译合并成一个可执行程序,在多个文件中相互调用,在调用的文件使用声明
#include "头文件"
-
gcc编译流程
-
预处理
处理宏定义,引入头文件等,处理#开头的内容gcc -E xxx.c //生成 xxx.i 文件
-
编译
把C程序翻泽成汇编代码gcc -S xxx.c //生成 xxx.s 文件
-
汇编
将汇编代码翻译为二进制机器码gcc -C xxx.s //生成 xxx.o 文件
-
链接
将多个二进制文件链接在一起合并成一个可执行程序gcc xxx1.o xxx2.o xxx3.o //生成 a.out 文件
-
-
make工程管理器
它是一个用于管理编译工程辅助作用的工具软件,make工具是根据文件中的描述来进行管理:makefile(MakeFile)文件,用 vim makefile 打开-
makefile文件编写格式
目标 : 依赖 //依赖可以没有,依赖也可以是另一个目标,先会执行另一个目标 <table>命令/或动作 //table键,表示空格
例如:
说明:
all:目标;1.c 2.c 3.c:依赖 -
文件执行
make all //执行makefile文件中的目标all make clean //执行makefile文件中的目标clean
-
变量
-
用户自定义变量
变量名=值
-
使用变量
$(变量名)
例如:
-
自动变量
- $* :不包含扩展名的目标文件名
- $@ :包含扩展名的目标文件名(推荐使用)
- $+ :所有的依赖文件,从左至右,有可能重复
- $^ :所有的依赖文件,从左至右,不重复
- $< :第一个依赖文件
-
隐藏规则
%.o : %.c //%:根据依赖文件 1.o 2.o 3.o来进行替换 gcc -c $< -o $@
例如:
-
-