嵌入式开发——Linux C学习
该文为学习笔记,仅作学习参考,如有错误,望指正!
一. 基础命令
GNU -> GNU is not UNIX;
GPL -> General Public License;
内核:内核是 Linux 系统的最底层,提供了系统的核心功能并允许进程以一种有序的方式访问硬件。用于控制进程、输入、输出设备、文件系统操作、管理内存。
Linux 内核支持多用户、多任务模式运行。
多用户:同时有多个用户访问系统;
多任务:某个时刻有多个程序运行;
Shell 是一个命令行解释器,它使得用户能够与操作系统进行交互;
vim 是 vi 的升级版,如果想要自己配置 vim 的话,首先要安装 vim(
sudo apt-get install vim-gtk
),另外需要更改 vim 的配置文件.vimrc
文件(sudo vim /etc/vim/vimrc),vim ~/.vimrc
如果没有就创建一个名为 ~/.vimrc 的文件,然后在将网上提供的配置文件源码复制进去即可,配置字体配色。Debian Linux 首先提出“软件包”的管理机制——Deb 软件包**,**将应用程序的二进制文件、配置文档、man/info 帮助页面等文件合并打包在一个文件中,用户使用软件包管理器直接操作软件包,完成获取、安装、卸载、查询等操作。
Name_Version-Reversion_Architecture.deb:软件包名称+软件版本+修订版+体系架构
dpkg:(本地已有软件安装)不可以在线安装软件,不能检查软件包的依赖关系,为了解决软件包依赖性问题和获取问题,就出现了APT工具。
apt:不仅仅可以完成 dpkg 的功能,而且还可以从网络中下载软件,它会自动检查软件包依赖关系,会将某一软件包依赖的所有软件包都下载并安装软件源,配置文件只是告知 Ubuntu 系统可以访问的镜像站点地址。但那些镜像站点都拥有什么软件资源并不清楚。若是每安装一个软件包,就在服务器上寻找一遍,效率是很低的。因而,就有必要为这些软件资源列个清单(为镜像站点地址建立索引文件),以便本地主机查询。这就是APT软件包管理器的工作原理。
软件源配置文件就是
/etc/apt/sources.list
刷新软件源:
- 修改了配置文件——
/etc/apt/sources.list
,将适合自己的软件源替换此文件中的软件源; - 使用
apt-get update
命令刷新软件源,建立最新的索引文件,更新了软件包列表;
- 修改了配置文件——
dpkg -i
:安装一个在本地文件系统上存在的Debian软件包。dpkg -r
:移除一个已经安装的软件包dpkg -P
:移除已安装软件包及配置文件dpkg -L
:列出安装的软件包清单dpkg -s
:显出软件包的安装状态apt-get:用于管理软件包,包括安装、卸载、升级等操作;
sudo apt-get install xchat
,在/var/cache/apt/archives
会有已经下载的所有安装包(deb包)内核驱动程序目录:
/lib/modules/$(uname -r)/kernel
env:环境变量;
set:自定义变量;
export:自定义变量转换为环境变量;
locale:系统支持的语系
x2dos:Linux 与 DOS 之间的格式转换工具;ex:
unix2dos -k oldfile newfile;
iconv:语系编码转换;
ex:
iconv -f 原编码 -t 目标编码 oldfile -o newfile
;繁体中文转换为简体中文:
iconv -f utf8 -t big4 vi.utf8 |iconv -f big5 -t gb2312 |iconv -f gb2312 -t utf8 -o vi.gb.utf8
KDE:桌面环境
shell:命令行模式传输速度比较快,不容易掉线和信息外流。
二. 基本知识
apt-get install 软件包
:在 /var/cache/apt/archives 路径先下载再安装 deb 包。apt-get source 软件包
:下载源码包,只下载不安装。apt-cache policy xchat
:查询软件包安装状态。apt-get remove xchat
:卸载软件包。–purge 与 remove 子命令一起使用,完全卸载软件包。
clean 删除缓存区中所有已下载的包文件。
Shell 是一个命令行解释器,将用户命令解析为操作系统所能理解的指令,实现用户与操作系统的交互。
username@hostname:direction$:用户名+主机名+目录名
通常一条命令包含三个要素:命令名称、选项、参数。三要素之间使用空格分隔,不能连写到一起。命令名称是必须的,选项和参数都可能是可选项。
- 一条命令的三要素之间用空格隔开;
- 若将多个命令在一行书写,用分号(;)将各命令隔开;
- 如果一条命令不能在一行写完,在行尾使用反斜杠(\)标明该条命令未结束。
补齐命令与文件名: TAB 自动补齐。
通配符:
- 星号
*
匹配任意长度的字符串 - 问号
?
匹配一个长度的字符 - 方括号
[…]
匹配其中指定的一个字符 - 方括号
[ - ]
匹配指定的一个字符范围 - 方括号
[^…]
除了其中指定的字符,均可匹配
- 星号
输入/输出重定向是改变Shell命令或程序默认的标准输入/输出目标,重新定向到新的目标。
- Linux 中默认的标准输入定义为键盘,标准输出定义为终端窗口。
- > file 将file文件重定向为输出源,新建模式
- >> file 将file文件重定向为输出源,追加模式
- < file 将file文件重定向为输入源 wc < file1,将file1中的内容作为输入传给wc命令
- 2> 或 &> 将由命令产生的错误信息输入到…:标准输入 0 、标准输出 1、标准错误 2
命令command2的输出将作为命令command1的参数。需要注意,命令置换的单引号为ESC键下方的 ` 键;
grep -Rn "bool" ./
在当前目录下递归查找字符串 “bool”,列出所在文件及行号;head -n file
显示某文件的前 n 行;tail -n file
显示某文件的后 n 行;文件系统:用于组织和管理计算机存储设备上的大量文件,并提供用户交互接口;
/dev/sda1
设备节点Ubuntu 设置共享文件目录
cd /media/VMware\ Tools/ cp VMwareTools-9.6.0-1294478.tar.gz ~/Desktop tar xvf VMwareTools-9.6.0-1294478.tar.gz cd vmware-tools-distrib/ sudo ./vmware-install.pl
/dev/sda1
含义? a 代表第一块sata硬盘, 1 第一个分区/dev/sdb3
含义? b 代表第二块sata硬盘, 3 第三个分区挂载U盘:
sudo mount -t vfat /dev/sdb1 ~/usb
将第二块硬盘挂载到指定目录家目录下的usb目录,-t指定文件系统类型。sudo umount ~/usb
:解除挂载Linux 文件系统就是一个树形的分层组织结构。将根(/)作为整个文件系统的唯一起点,其他所有目录都从该点出发。
file:查看文件类型
参数 英文 说明 b block 块设备 c character 字符设备 d directory 目录 - 普通文件 l link 链接文件 s socket 套接字 p pipe 管道 硬链接是利用Linux中为每个文件分配的物理编号——inode建立链接。因此,硬链接不能跨越文件系统。
- 软链接(符号链接)是利用文件的路径名建立链接。通常建立软链接使用绝对路径而不是相对路径,以最大限度增加可移植性。
- ln [ -s ] target link_name
- 软连接,原文件删除,软连接就没有意义
- 硬链接,两个文件内容一样,并且更改某一个,另一个也会随之变化;但是删除任何一个,另一个不受影响,可做备份文件。
-rwxrwxr-x 1 linux linux 7158 8月 26 2014 test01
文件类型,- 是普通文件;文件访问权限,rwx rwx r-x;链接数;文件所有者;文件所有组;文件大小,以字符为单位;上次修改文件或目录的时间;文件名。
touch 命令用于创建一个新文件。
touch filename
:其中filename是文件名。如果这个文件是已有文件,就改变这个文件的最后修改日期;如果文件不存在,就创建新的空文件。echo "Hello" >> file
echo $PATH
:输出系统环境变量;归档文件是将一组文件或目录保存在一个文件中。
压缩文件也是将一组文件或目录保存一个文件中,并按照某种存储格式保存在磁盘上,所占磁盘空间比其中所有文件总和要少。
tar [ -t | -x | -u | -c | -v | -f | -j | -z ] tar file filelist tar -cjvf day.tar.bz2 *.c:创建了一个bzip2的压缩归档文件。 tar -czvf day.tar.gz *.c 创建了一个gzip的压缩归档文件。 tar -xjvf day.tar.bz2 tar -xzvf day.tar.gz
ps -aux
:列出所有进程信息 ;Kill -9 PID
:根据进程号杀死指定进程;用户管理:Linux用户分组管理,一个用户属于某个组,但是可以属于多个组,某个组可以包含多个用户;
/etc/passwd
用户登陆时,系统查询这个文件,确定用户的UID并验证用户口令、登陆名、经过加密的口令、UID、默认的GID、个人信息、主目录、登陆shell;/etc/group
文件 包含了UNIX组的名称和每个组中成员列表。每一行代表一个组,包括4个字段:组名、加密的口令、GID号成员列表,彼此号隔开;
adduser newuser
:创建普通用户newuser,uid,gid都是从1000后开始的;deluser --remove-home user1
删除用户user1的同时删除用户的工作目录del group groupname
;su 命令用于临时改变用户身份,具有其他用户的权限。
passwd root
修改密码。su - root
切换用户为 root。chmod : 改变文件或目录的访问权限。
chown: 改变文件或目录的属主。
- rwx rw- r-- 1 linux linux 40 7月 8 23:54 test.txt
第一项是由10个字符组成的字符串,例如 “drwxr-xr-x”,说明了该文件/目录的文件类型和文件访问权限。
第一个字符表示文件类型。
从左起第2个字符到第10个字符表示文件访问权限。并且以3个字符为一组,分为3组。
组中的每个位置对应一个指定的权限,其顺序为:读、写、执行。
3组字符又分别代表文件所有者权限、文件从属组权限以及其他用户权限。
rwx:代表文件所有者具有可读、可写、可执权限行
rw-:代表文件所有者所在的组具有的权限:可读、可写,- 权限缺失。
r–:除了文件所有者及所在组的其他用户具有的权限:可读,- 权限缺失,没有可写和可执行的权限。
chmod u+x test.txt 给当前用户增加可执行权限 chmod u-x test.txt 给当前用户去除可执行权限 chmod a+x test.txt 给所有用户增加可执行权限 chmod g+x test.txt 给同组的其他用户增加可执行权限 chmod o+x test.txt 给其他用户(文件所有者及文件同组用户之外的其他用户)增加可执行权限
权限 二进制 十进制 r… 0100 4 .w. 0010 2 …x 0001 1 chown username 文件:更改文件的所有者为 username;
三. 配置nfs、tftp和网络
man
man man
Executable programs or shell commands
System calls (functions provided by the kernel)系统调用
Library calls (functions within program libraries)库函数
Special files (usually found in /dev)
File formats and conventions eg /etc/passwd
Games
Miscellaneous (including macro packages and conventions)
e.g:man(7), groff(7)
System administration commands (usually only for root)
Kernel routines [Non standard]
Write语句?
Man语句中1、2、3很重要
ifconfig:查看网络配置信息,eth0为网卡,lo为本地回环地址
Linux世界中,无论是配置静态IP还是动态IP,计算机系统将IP信息保存放在
/etc/network/interfaces
。sudo vi /etc/network/interfaces // 配置网络 auto eth0 iface eth0 inet static address 192.168.1.209 netmask 255.255.255.0 gateway 192.168.1.1 sudo vim /etc/resolv.conf // 配置文件,设置 DNS 服务 nameserver 192.168.1.1 sudo /etc/init.d/networking restart // 重启网络
临时指定ip地址:sudo ifconfig eth0 192.168.1.222。
tftp 服务器,完成数据的上传下载。
sudo apt-get install tftpd-hpa
:安装成功之后,在 /etc/default/ 目录下会自动生成一个配置文件 tftpd-hpa, 如果安装不成功,此文件是不存在的,所以切记不要自己去写一个。配置文件:# /etc/default/tftpd-hpa TFTP_USERNAME="tftp" TFTP_DIRECTORY="/tftpboot" //tftp服务器的工作目录 TFTP_ADDRESS="0.0.0.0:69" TFTP_OPTIONS="-l -c --secure" // l:列出文件,c:创建文件
因为配置文件中指明工作目录是 /tftpboot, 所以我们创建此目录,并且改权限为:
sudo chmod 777 tftpboot/
。重启 tftp 服务:sudo /etc/init.d/tftpd-hpa restart
tftp ip地址 get&put命令无效?
get file
:下载文件,从服务器的工作目录/tftpboot中来下载。put file
:上传文件,把当前目录下的file文件上传到tftp服务器的工作目录/tftpboot。nfs:网络文件系统,完成数据共享。
sudo dpkg -s nfs-kernel-server
查看是否已经安装nfs,如果没有安装,可以使用以下命令安装:sudo apt-get install nfs-kernel-server
,nfs 服务的配置文件:在/etc下vi exports
,添加配置文件/source/rootfs *(rw,sync,no_subtree_check,no_root_squash)
。/source/rootfs 网络文件系统共享目录,需在根目录下新建。*
:和当前主机在同一网段的所有主机,这些主机都可以访问此网络文件系统; rw:权限;sync :同步;no_subtree_check:不做目录树检查;no_root_squash:登录用户具有root权限;重启服务:
sudo /etc/init.d/nfs-kernel-server restart
;挂载网络文件系统:
sudo mount -t nfs 192.168.1.222:/source/rootfs ./test
;卸载:
sudo umount /home/linux/test/
;Shell脚本语言是解释型语言。Shell脚本的本质:Shell命令的有序集合。
程序基本过程分为三步:
编辑shell 文件:包含任意多行操作系统命令或shell命令的文本文件;一般我们是以".sh"作为shell文件的后缀名。
赋予shell文件执行权限:用chmod命令修改权限。
执行shell文件:直接在命令行上调用shell程序;不管test.sh是否具有可执行权限,我们是可以通过"bash test.sh"来指明使用bash来解析执行test.sh文件的。
Shell允许用户建立变量存储数据,但不支持数据类型(整型、字符、浮点型),将任何赋给变量的值都解释为一串字符;shell中的变量不需要声明,直接可以使用。
变量直接可以使用,count=1 注意:"="前后没有空格。
变量的调用:在变量前加$。
使用unset命令删除变量的赋值。
$0 与键入的命令行一样,包含脚本文件名,脚本文件名 + 路径(相对路径/绝对路径)
./test.sh
这时$0就是指 ./test.sh/home/linux/test.sh
这时$0就是指 /home/linux/test.sh$1,$2,……$9
分别包含第一个到第九个命令行参数。$#
包含命令行参数的个数$@ 包含所有命令行参数
:“$1,$2,……$9”$?
包含前一个命令的退出状态,上一条命令执行成功,返回 0 ; 不成功,返回非 0$* 包含所有命令行参数
:“$1,$2,……$9”$$
包含正在执行进程的ID号PATH :shell搜索路径
说明性语句:以
#号
开始到该行结束,不被解释执行#! /bin/bash
:告诉操作系统使用何种类型的shell来解析当前的shell脚本功能性语句: 任意的shell命令、用户程序或其它shell程序。
read
read从标准输入读入一行, 并赋值给后面的变量,其语法为:read var(后不需分号)。
read var1 var2 var3:把读入行中的第一个单词(word)赋给var1, 第二个单词赋给var2,…把其余所有的 单词赋给最后一个变量
算术运算命令
expr
主要用于进行简单的整数运算,包括加(+)、减(-)、乘(*)、整除(/)和求模(%)(即求余)等操作。反撇号()将expr运算括起来,实际是引用命令的运行结果,最终去做其他操作test语句可测试三种对象:字符串 整数 文件属性
-eq; -gt; -ge; -lt; -le;
测试文件-n;
四. shell 脚本命令,正则表达式
type
:查看系统内置变量declare -i s=100+100
:定义数据类型(-a:数组;-i:整型;-x:参数变为环境变量;-r:readonly)var[index]=content
var[0]="s" var[1]="k" var[2]="l" echo ${var[0]}, ${var[1]}, ${var[2]}
ulimit
:用于控制shell程序的资源。ulimit -f 10240
alias,unalias
:命令别名设置source
:读入环境配置文件find /home -name .bashrc > list_right 2> list_error
echo "error message" 1>2
:创建2并保存error message;echo "error message" 1>&2
:终端打印;echo "error message" 2> /dev/null 1>&2
(错误信息丢进垃圾箱);2>&1
的意思就是将标准错误重定向到标准输出cat > file 、cat > file < file1、cat > file << “eof”
\> /dev/null
:丢进垃圾箱cut
截取内容(echo ${PATH}|cut -d ':' -f 1;cat catfile |cut -c 2-
)last
:显示登陆者信息。grep
:-v 反向选择,-a 将二进制文件以文本文件的方式查找数据;-c 计算查找到字符次数;-i 忽略大小写;-n 输出行号(grep -in 'the' file.txt
:查找文件内容)分类
sort
:-f 忽略大小写;-b 忽略前面的空格;-M 以月份名字来排序;-n 使用纯数字排序;-r 反向排序;-u 相同的数据中仅出现一次(同uniq);-t 分隔符;-k 排序的区间uniq
:-i 忽略大小写;-c 进行计数(重复文件数目)统计
wc
:-l 仅列出行;-w 列出多少个字;-m 多少字符(默认算\n)双定向
tee
:-a 追加删除替换
tr
:tr -d SET1
删除信息中SET1字符(cat catfile |tr -d ':'
);-s 替换重复字符(cat catfile |tr [a-z] [A-Z]
);tr -d '^M'==tr [^M] [\r]==dos2unix
tab
转换空格col
:-x 将 tab 转换为对等空格(cat man_db.conf |col -x |cat -A |more
)cat -A
:tab 以 ^l 显示类粘贴
join
:-i 忽略大小写;-t(join -t 'a' file1 file2
:以a为分隔符对比file1和file2,相同则file2按行拼接在file1后)(用前需进行排序)粘贴
paste
:-d 将两个文件按行粘贴在一起,以 tab 分隔;-file 部分用 - 代替,则该部分内容来自标准输入tab 转换空格
expend
:expend -t n
将tab转换为空格(可自定义空格位数,默认8位)unexpend
:与expend相反文件分割
split
:-b 按文件大小分割文件(split -b 1k spit.txt spit
),-l 以行数分割文件(ls -la /|split -l 10 - lsroot
)xargs
:xargs [-0epn] command
;-0 转换特殊字符为一般字符;-e EOF;-p 询问使用者意思;-n 次数-
:使用前一命令的 stdoutdmesg
:打印内核产生的信息,包括硬件检测流程shell脚本的编写习惯:(bash file.sh、./file.sh、source file.sh)
- 脚本功能
- 版本信息
- 作者和联系方式
- 版本声明
- 历史记录
- 脚本内较特殊的命令
- 脚本运行时的环境变量预先声明和设置
shift n:偏移n个参数
test 语句可测试三种对象:(常与判断符号[]一起使用)
字符串 整数 文件 属性
命令 说明 -d name 测试name 是否为一个目录 -e name 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 更旧 if 判断
if 表达式 then 命令表 fi
如果表达式为真, 则执行命令表中的命令; 否则退出 if 语句, 即执行 fi 后面的语句。
逻辑或
if [ $score -lt 0 ] || [ $score -gt 100 ] thenecho "输入的分数非法!" fi
逻辑或
if [ $score -lt 0 -o $score -gt 100 ] thenecho "输入的分数非法!" fi
逻辑与
if [ $score -ge 0 -a $score -le 100 ] thenecho "输入的分数合法!" fi
逻辑与
if [ $score -ge 0 ] && [ $score -le 100 ] thenecho "输入的分数合法!" fi
多路分支语句 case 用于多重条件测试, 语法结构清晰自然.
其语法为:
case 字符串变量 in模式1)命令表1;;模式2 | 模式3)命令表2;;……模式n)命令表n;; esac
for 循环
for 变量名 in 单词表 do命令表 done 变量依次取单词表中的各个单词, 每取一次单词, 就执行一次循环体中的命令. 循环次数由单词表中的单词数确定.
while 语法结构
while 命令或表达式 do命令表 done
在shell程序中, 常常把完成固定功能、且多次使用的一组命令(语句)封装在一个函数里,每当要使用该功能时只需调用该函数名即可。
函数在调用前必须先定义,即在顺序上函数说明必须放在调用程序的前面。
调用程序可传递参数给函数,函数可用return语句把运行结果返回给调用程序。
变量前面不加local,这个变量就是全局变量,不管是在什么位置;
变量前加local,这个就是局部变量,只在相应作用域有效。
gdb
在gcc编译选项中一定要加入‘-g’。
只有在代码处于“运行”或“暂停”状态时才能查看变量值。
设置断点后程序在指定行之前停止
shell脚本的调试:
sh [-nvx] file.sh;
-n 检查语法;-x 输出脚本内容再执行;-v 是用到的脚本输出到终端;正则表达式
grep [-A] [-B] [--color=auto] '字符查找' filename
:cat spit.txt |grep -n -A3 -B2 --color=auto 'f' file.txt
;grep -n 'the' file.txt
:查找file.txt文件中存在the的行;grep -vn 'the' file.txt
:反选,查找file.txt文件中不存在the的行grep -in 't[ae]st' file.txt
:[]控制该位置内容grep -n '[^[:lower:]]oo' vbird.txt == grep -n '[^a-z]oo' vbird.txt
grep -n '^the' vbird.txt
:^制表符(行首)grep -n '\.$' vbird.txt
:行尾grep -n '^$' vbird.txt
:输出空行grep -n 'g..d' vbird.txt
:'.'代表一位字符grep -n 'go*g' vbird.txt
:'*'重复前一个字符0到无数次(重复0次即为空)grep -n 'o\{3,5\}' vbird.txt
:限定搜索范围
增删改查 sed
- -n安静模式;-e直接在命令行模式进行sed操作;-f操作结果写入文件;-r使用扩展型正则表达式语法;-i直接修改读取的文件内容;
- [n1,[,n2]] function:a新增;c替换;d删除;i插入;p打印;s替换;
扩展正则表达式
egrep -v '^$|^#' filename
;- RE字符:+重复一个或一个以上;?重复一个或0个;|或;()找出群组字符集;()+多个重复群组的判别;
- 数据处理工具awk:$1,$2,$3;
- 比较diff、cmp;
- patch:-pN目录层数;-R还原;diff -Naur passwd passwd.new >passwd.patch;
- 文件打印设置:pr filename;
五. 指针的代码案例
指针大小:
sizeof(指针)
计算指针p的类型大小,64位系统大小为8,32位系统中大小4,在32位系统上,不管指针p指向的是整型数据,还是字符型数据,short型数据,long型数据等,指针p本身所占的内存字节数均为4。#include <stdio.h> int main(){// 变量 a 的地址就是 int *,4个字节int a = 10;char b = 10;// int * 是一个类型int *p = &a; // p 指向的是数字 a 的首地址char *q = (char *)p; // q 指向强转为 char 类型指针 p 的地址// int 数据类型说明指针变量指向的那片空间是 int,占有 4 个字节p + 1; // 指针 p 地址加 1,向后移动 sizeof(p的数据类型) 个字符单位printf("%p\n", p); // 输出指针 p 的地址printf("%p\n", (char *)p+1); // 强转为 char 类型指针,地址运算:+ n * sizeof(强转的数据类型)printf("%p\n", q);printf("%p\n", q+1);printf("%d\n", sizeof(p)); // 指针的长度为 4printf("%d\n", sizeof(q));return 0; }
[root@localhost ~]# gcc tt.c [root@localhost ~]# ./a.out 0x7ffc0de01764 0x7ffc0de01765 0x7ffc0de01764 0x7ffc0de01765 8 8
主机大小端(即字节序)
大端:低地址存高字节;
小端:高地址存高字节(左边是高位,右边是低位))
Ubuntu 一般为小端操作,即低地址存低字节
#include <stdio.h> int main(){unsigned int a = 0x12345678;int *p = &a;char *q = (char *)p; // 同级指针进行操作printf("%#x\n", *q); // %#x 表示十六进制输出,%#o 表示八进制输出printf("%#x\n", *(q+1));printf("%#x\n", *(q+2));printf("%#x\n", *(q+3));return 0; }
[root@localhost ~]# ./a.out 0x78 0x56 0x34 0x12 # 十六进制数的一位相当于二进制的4位;打印一个字节就得取十六进制数的两位 # ------>小端存储 # 一个 char *为 8bit ,一个 int 为 4bit ,两个 int 存满一个 char *,所以 12 34 56 78 两两分别存储
指针移动、数组首地址、数组指针的使用
#include <stdio.h> int main(){int a[6] = {3,2,5,6,1,9};// int *p = &a[0]; // 定义了一个 int *型指针 p 指向数组首地址 a[0] (a 也可表示首地址)int *p = a; // 一维数组名代表的就是数组首元素的地址// C99 语法,编译时加上 std=c99// 注意:i 作用域只限于 for 循环for(int i = 0; i < 6; i++){// printf("a[%d] = %d\n", i, *(a+i)); // *(a+i) 表示指针 a(首地址) 向后移动 i 个单位后取内容// p++ 是可以的,因为 p 是指针变量// a++ 是错误的,因为 a 是地址变量printf("a[%d] = %d\n", i, *p++); // *p++ 表示指针,p 取内容后,向后移动一个单位}// printf("i = %d\n", i);int (*qq)[6] = &a; // (*qq)[6] 表示指针数组(指向数组的指针,[6]表示该数组的列数)printf("%p\n", a);printf("%p\n", a+1); // 首地址:a++ 不可以,a+1 可以printf("%p\n", &a+1); // & 相当于把列指针提升到行指针,&a+1 位移动一行printf("%p\n", qq+1); // qq+1 表示数组指针向后移动一个单位return 0; }
[root@localhost ~]# gcc tt.c [root@localhost ~]# ./a.out a[0] = 3 a[1] = 2 a[2] = 5 a[3] = 6 a[4] = 1 a[5] = 9 0x7ffd17bbdab0 0x7ffd17bbdab4 0x7ffd17bbdac8 0x7ffd17bbdac8
指针的使用
#include <stdio.h> int main(){// char str1[] = {'H','e','l','l','o'};char str1[6] = "Hello"; // 字符串赋值给字符数组,最后实际操作的是字符数组里的内容,即 str[6] 为变量char *s = "Hello"; // 字符指针 s 中保存的是字符串常量的首地址,为常量不可变,字符指针里面并没有保存字符串的内容,它只是保存了首地址// str1[2] = 'W'; // str1[2] 为变量可以赋值成功// s[2] = 'w'; // 通过字符指针来间接更改了字符串常量的内容,这是非法的,常量不可修改int i = 0;for(; i < 5; i++){// putchar(*(str1+i));putchar(*(s+i));putchar(10);}// printf("%s\n", str1);printf("%s\n", s); // 输出字符串printf("%p\n", "Hello"); // 输出 Hello 的首地址printf("%p\n", "Hello"+1); // Hello 的首地址加一个字节printf("%p\n", s+1); // 字符指针 s 增加一个字节printf("%c\n", *(s+1)); // 字符指针 s 增加一个字节再取内容printf("%c\n", *("Hello"+1)); // Hello 首地址加 1 取内容printf("%c\n", *("Hello")+1); // Hello 首地址取内容后加 1return 0; }
[root@localhost ~]# ./a.out H e l l o Hello 0x402004 0x402005 0x402005 e e I
示例1:
#include <stdio.h> int main(){char *s[] = {"Hello","World","abcd"}; // 字符指针数组// s[0] 是一个字符指针数组,保存的是字符串 Hello 的首地址;// s[1] 保存的是 World 的首地址// s[3] 保存的是 abcd 的首地址// char *s = "Hello";printf("%c\n", *(s[0]+1)); // 输出 World 的首地址内容int i = 0;char **p = s; // 二级指针 **p 指向字符指针数组 s 的地址for(i = 0; i < 3; i++){// printf("%s\n", *(s+i)); // 加 i 实际是移动 i 个字符串后输出字符串// printf("%s\n", *(p+1)); // 加 i 实际是移动 i 个字符串后输出字符串// printf("%s\n", *p++);printf("%c\n", *(*(p++))); // *(p++) 为 p 指向的字符指针数组首地址 s 的首地址(即取 s 的首个字符串的首地址),*(*(p++)) 为再 s 取地址内的内容后,指针向后移动指向下一个字符串首地址}return 0; }
[root@localhost ~]# ./a.out e H W a
示例2:
#include <stdio.h> int main(){int a[2][3] = {1,2,3,7,8,9};int i, j;for(i = 0; i < 2; i++){for(j = 0; j < 3; j++){printf("a[%d][%d] = %d\n", i, j, a[i][j]);}}printf("======================================\n");for(i = 0; i < 2; i++){for(j = 0; j < 3; j++){printf("a[%d][%d] = %d\n", i, j, *(a[0]+i*3+j)); // //a[0] & a[0][0] 都表示二维数组首元素的地址}}printf("======================================\n");for(i = 0; i < 2; i++){for(j = 0; j < 3; j++){printf("a[%d][%d] = %d\n", i, j, *(*a+i*3+j));}}printf("======================================\n");for(i = 0; i < 2; i++){for(j = 0; j < 3; j++){printf("a[%d][%d] = %d\n", i, j, *(*(a+i)+j));}}printf("======================================\n");for(i = 0; i < 6; i++){printf("a[%d] = %d\n", i, *(&a[0][0])+i); // 可定义 *p = &a[0][0],把 &a[0][0] 换成 *p}printf("======================================\n");for(i = 0; i < 6; i++){printf("a[%d] = %d\n", i, *(a[0]+i)); // 可定义 *pp = a[0],把 *a[0] 换成 *pp}printf("======================================\n");for(i = 0; i < 6; i++){printf("a[%d] = %d\n", i, *((*a)+i));}printf("**************************************\n");// 也可以用指针的方式存int (*q)[3]=a;for(i = 0; i < 2; i++){for(j = 0; j < 3; j++){printf("a[%d][%d] = %d\n",i,j,*(*(q+i)+j));}}return 0; }
[root@localhost ~]# ./a.out a[0][0] = 1 a[0][1] = 2 a[0][2] = 3 a[1][0] = 7 a[1][1] = 8 a[1][2] = 9 ====================================== a[0][0] = 1 a[0][1] = 2 a[0][2] = 3 a[1][0] = 7 a[1][1] = 8 a[1][2] = 9 ====================================== a[0][0] = 1 a[0][1] = 2 a[0][2] = 3 a[1][0] = 7 a[1][1] = 8 a[1][2] = 9 ====================================== a[0][0] = 1 a[0][1] = 2 a[0][2] = 3 a[1][0] = 7 a[1][1] = 8 a[1][2] = 9 ====================================== a[0] = 1 a[1] = 2 a[2] = 3 a[3] = 4 a[4] = 5 a[5] = 6 ====================================== a[0] = 1 a[1] = 2 a[2] = 3 a[3] = 7 a[4] = 8 a[5] = 9 ====================================== a[0] = 1 a[1] = 2 a[2] = 3 a[3] = 7 a[4] = 8 a[5] = 9 ************************************** a[0][0] = 1 a[0][1] = 2 a[0][2] = 3 a[1][0] = 7 a[1][1] = 8 a[1][2] = 9
示例3:
#include <stdio.h> int main(){int a = 10;int *p = &a;int **pp = &p;// int ***pp = &pp;printf("%p\t%p\n", p, &a);printf("%d\t%d\n", *p, *(&a));printf("%p\t%p\n", pp, &p);printf("%p\t%p\n", *pp, *(&p));printf("%d\t%d\n", **pp, **(&p));// printf("%p\t%p\n", ppp, &pp);/*int a[] = {3,4};int *s = a;*/char *str[] = {"Hello","World"}; // 字符数组每个元素的类型都是 char *char **q = str;/*printf("%s\n", *q);printf("%p\n", *q);printf("%c\n", **q);*/int i = 0;for(i = 0; i < 2; i++){// puts(*q++); // q++ 是移动一个字符串putchar(*(*q++)); // *q 得到字符数组的第一个元素,只不过这个元素是字符指针类型// puts(*q++)putchar(10);}return 0; }
[root@localhost ~]# ./a.out 0x7ffda491d4a4 0x7ffda491d4a4 10 10 0x7ffda491d498 0x7ffda491d498 0x7ffda491d4a4 0x7ffda491d4a4 10 10 H W
示例4:
#include <stdio.h> int fun(); // 函数声明 int add(int, int); void swap(int *, int *);int fun(){ // 函数名实际是函数的入口函数// int a = 90;// printf("123456\n");// printf("1234\n");return 2+3; }int add(int a, int b){return a+b; }void swap(int *a, int *b){/*int tmp = a;a = b;b = tmp;*/int tmp = *a;*a = *b;*b = tmp; }int main(){/*int num = 0;num = fun();add(2, 3);printf("%d\n", num);printf("%p\n", fun);*/int a = 2, b = 3;swap(&a, &b);printf("a = %d\tb = %d\n", a, b);return 0; }
[root@localhost ~]# ./a.out a = 3 b = 2
示例5:
#include <stdio.h> void fun(int *, int); // 找到二维数组的最大值int findMax(int (*p)[3], int n, int m){int i, j;int max = p[0][0];for(i = 0; i < n; i++){for(j = 0; j < m; j++){if(max < *(*(p+i)+j)){max = *(*(p+i)+j);}}}return max; }void show(int (*p)[3], int n){ // 打印二维数组int i = 0;for(; i < n; i++){printf("%d\n", *((*p)+i));} }void fun(int *q, int n){int i;for(i = 0; i < n; i++){printf("%d\n", *q++);} }int main(int argc, const char *argv[]){int a[] = {4,5,2,1};int b[2][3] = {1,5,3,4,2,7};printf("%d\n", findMax(b, 2, 3));show(b, 6);// fun(a, sizeof(a)/sizeof(b));return 0; }
[root@localhost ~]# ./a.out 7 1 5 3 4 2 7
六. 结构体 & Makefile
函数指针变量说明的一般形式如下: <数据类型> (*<函数指针名称>)(<参数说明列表>);
函数指针数组是一个包含若干个函数指针变量的数组。
定义形式如下:
<数据类型> ( * <函数指针数组名称> [<大小>] ) ( <参数说明列表> );
#define MAX(a,b) ((a)>(b)?(a):(b))
,这就是一个简单的函数宏,我们同样可以传递参数,实现功能。在实际的处理对象中,有许多信息是由多个不同类型的数据组合在一起进行描述,而且这些不同类型的数据是互相联系组成了一个有机的整体。此时,就要用到一种新的构造类型数据——结构体(structure),简称结构。 结构体是用户自定义的新数据类型,在结构体中可以包含若干个不同数据类型和不同意义的数据项(当然也可以相同),从而使这些数据项组合起来反映某一个信息。
定义一个结构体类型的一般形式为:
struct 结构体名 {数据类型 成员名1;数据类型 成员名2;........................数据类型 成员名n; };
注意:
- 在大括号中的内容也称为“成员列表”或“域表”;
- 其中,每个成员名的命名规则与变量名相同;
- 数据类型可以是基本变量类型和数组类型,或者是一个结构体类型;
- 用分号“;”作为结束符;
- 整个结构的定义也用分号作为结束符;
构造出的结构体类型,它属于C语言的一种数据类型,与整型、实型相当。因此,定义它时不分配空间,只有用它定义变量时才分配空间。
结构体类型变量的定义方法
先定义结构体类型再定义变量名,这是C语言中定义结构体类型变量最常见的方式
struct 结构体名 {成员列表; };
"struct 结构体名"才是一个完整的结构体类型,所以在定义变量时,一定写全,不可省略任何一个。
struct 结构体名 变量名;
结构体变量的初始化
与其他类型变量一样,也可以给结构体的每个成员赋初值,这称为结构体的初始化
一种是在定义结构体变量时进行初始化;
语法格式如下:struct 结构体名 变量名={初始数据表};
另一种是在定义结构体类型时进行结构体变量的初始化。
struct 结构体名 {成员列表; }变量名={初始数据表};
具有相同结构体类型的结构体变量也可以组成数组,称它们为结构体数组;
结构体指针
可以设定一个指针变量用来指向一个结构体变量。
此时该指针变量的值是结构体变量的起始地址,该指针称为结构体指针。
结构体指针与前面介绍的各种指针变量在特性和方法上是相同的。
与前述相同,在程序中结构体指针也是通过访问目标运算
*
访问它的对象。结构体指针在程序中的一般定义形式为:struct 结构体名 *结构指针名;
其中的结构体名必须是已经定义过的结构体类型。
在C语言中,不同数据类型的数据可以使用共同的存储区域,这种数据构造类型称为共用体,又称联合体。共用体在定义、说明和使用形式上与结构体相似。
malloc / free
void * malloc(size_t num)
void free(void *p)
malloc函数本身并不识别要申请的内存是什么类型,它只关心内存的总字节数。
malloc申请到的是一块连续的内存,有时可能会比所申请的空间大。其有时会申请不到内存,返回NULL。
malloc返回值的类型是void *,所以在调用malloc时要显式地进行类型转换,将void * 转换成所需要的指针类型。
如果free的参数是NULL的话,没有任何效果。
释放一块内存中的一部分是不被允许的。
在 C 语言中,允许使用关键字typedef定义新的数据类型,其语法如下:typedef <已有数据类型> <新数据类型>;
Make 工程管理器也就是个“自动编译管理器”,这里的“自动”是指它能够根据文件 、时间戳 ,自动发现更新过的文件而减少编译的工作量,同时,它通过读入 Makefile 文件文件的内容来执行大量的编译工作。
Makefile是Make读入的唯一配置文件。
由make工具创建的目标体(target),通常是目标文件或可执行文件 ;要创建的目标体所依赖的文件(dependency_file);创建每个目标体时需要运行的命令(command);
注意:命令行前面必须是一个”TAB键”,否则编译错误为:*** missing separator. Stop。
自动变量
$*
不包含扩展名的目标文件名称;$+
所有的依赖文件,以空格分开,并以出现的先后为序,可能包含重复的依赖文件;$<
第一个依赖文件的名称;$?
所有时间戳比目标文件晚的的依赖文件,并以空格分开;$@
目标文件的完整名称;$^
所有不重复的目标依赖文件,以空格分开;$%
如果目标是归档成员,则该变量表示目标的归档成员名称;
嵌入式开发——Linux C学习相关推荐
- 嵌入式开发——Linux操作系统
学习内容及目标:Linux介绍,使用Linux操作(命令),Linux开发工具(会用,够用即可) 1.安装Linux开发环境 vm虚拟机(通过软件的方法模拟pc) 为什么不选择双系统?(因为嵌入式开发 ...
- 嵌入式开发有必要学习python吗?
嵌入式与Python之间在概念上存在着明显的差异,嵌入式是一个开发领域,而python是一种编程语言. 对于新手来说,如果有时间,python是应该去学的.一方面是因为python语言本身相对简单,即 ...
- 没必要参加嵌入式开发培训班 (学习嵌入式的资料)
http://bbs.csdn.net/topics/390803818 ; 你想知道为什么的话,就请接着向下看.我相信你会收获很多的.一定要耐心看完哦,你会收获很多的,相信我!尤其是最后面我提供的资 ...
- 嵌入式开发linux工具,嵌入式Linux开发入门之MfgTool工具的使用
介绍嵌入式linux开发的环境搭建: 一.PC端环境搭建 2.Ubuntu装好后,在终端安装minicom工具,安装命令:sudo apt-get install minicom 注:该工具在后面烧录 ...
- 【嵌入式开发】gcc 学习笔记(一) - 编译C程序 及 编译过程
一. C程序编译过程 编译过程简介 : C语言的源文件 编译成 可执行文件需要四个步骤, 预处理 (Preprocessing) 扩展宏, 编译 (compilation) 得到汇编语言, 汇编 (a ...
- 基于c语言的linux嵌入式开发入门
前言 本文主要包含,c语言基本结构与语法.make及makefile的使用.main函数参数与返回值的说明.标准输入.输出.错误流的介绍以及linux管道的应用. 语言数据类型 联合体也有翻译为共用体 ...
- 循序渐进学习嵌入式开发技术
嵌入式时代已经来临,你还在等什么? ---循序渐进学习嵌入式开发技术最近经常有用人单位给我打来电话,问我这有没有嵌入式Linux方面的开发人员,他们说他们单位急需要懂得在嵌入式linux环境下的 ...
- 嵌入式开发需要学习哪些东西
刚刚读到这篇文章,对于刚入门嵌入式来说挺有用的,献给向我一样正在迷茫,苦于没有头绪,没人指引的学习朋友们. 主要讨论下嵌入式技术学习方法,主要是针对嵌入式软件. 嵌入式技术是一门边缘科学(又称交叉科学 ...
- 嵌入式入门学习笔记6:[转]嵌入式开发需要学习哪些东西
本文转自迅为开发板iTOP-4412开发板实战手册:http://www.topeetboard.com 刚刚读到这篇文章,对于刚入门嵌入式来说挺有用的,献给向我一样正在迷茫,苦于没有头绪,没人指引的 ...
最新文章
- 【HashMap 嵌套 ArrayList】
- Android数据库高手秘籍
- 目标检测第2步:如何在Windows 10下安装Anaconda?
- Android 系统(22)--Android P 行为变更
- POST 一张 图像的调试来认识 http post
- xv6 Traps, interrupts, and drivers
- Navicat for MySQL 视图创建使用方法以及如何查看数据表创建语句
- 老年手机英文改中文_老年手机设置成英文怎么办
- 如何找到能商用的背景纯音乐
- css 设置行内元素顶部对齐
- 如何对CAD图纸上的图形进行单独保存起来
- python import illegal instruction
- 电脑提示ISDone.dll错误怎么办?
- 使用深度学习Web项目的手写Marathi印地语字母书写和检测
- listen的backlog值分析
- 跨境电商的三大平台Amazon、eBay、速卖通,你怎么选择?
- mvc中简单从controll传递数据到前台页面(视图)
- 链表、二叉树、图的建立与初始化C源代码
- 曾经沧海难为水,除却巫山不是云~ 的意思是为思念亡妻
- 新160个CrackMe分析-第2组:11-20(上)