紫色是我没记住的,黄色是标注,需要知道的,红色是重点,粉红色是需要注意的点,黄色高亮是记错但相对没那么重要的点
目录在旁边,点红框就能看见目录了
ctrl alt t:打开一个新终端
ctrl shift n:打开一个相同路径的新终端
ctrl shift t:左右分屏的方式打开一个新终端(路径相同)
放大:ctrl shift +
缩小:ctrl -
复制:ctrl shift c
粘贴:ctrl shift v
ctrl 空格 切换输入法
shift 切换中英文
linux 用户名
@ 分隔符
ubuntu 主机名
: 分隔符
~ 当前所在路径地址,~表示用户的home目录
$ 普通用户,#管理员用户(root)
管理员用户有系统中所有的权限
普通用户想以管理员身份执行某些操作时 可以在命令前加 sudo
需要使用root密码 是 1
注意:并不是所有的主机命令行提示符都长这样
等后面学了环境变量后,可以通过修改环境变量 PS1 来修改命令行提示符
让命令行提示符有颜色 可以执行下面的指令:
export PS1="${debian_chroot:+($debian_chroot)}\[\033[01;35;1m\]\u\[\033[00;00;1m\]@\[\033[01;35;1m\]\h\[\033[00;31;1m\]:\[\033[00;34;1m\]\w\[\033[01;32;1m\]\$ \[\033[0;37;1m\]"
根目录,家目录的关系
家目录在根目录下,一个用户有一个家目录,普通用户只能操作自己的家目录下的内容
linux下一切皆文件
一个终端只需输一次密码
绝对路径:相对于根目录的路径
相对路径 :相对于当前所在目录的路径
假设在linux里想执行清除student下的文件1操作,
会报错,显示当前路径没有文件1
linux中所有和文件相关的命令都是即支持绝对路径,也支持相对路径的。
格式:命令 选项(对参数的操作限制) 参数(文件目录之类的)
注意:1.格式要素之间都要有空格,空格个数无所谓
2.格式要素不一定全都有
3.执行,回车
ls 列出当前路径下的所有文件
ls 文件名 列出指定的单个文件
ls 目录名 列出目录下的所有文件
ls -a 列出当前路径下的所有文件,包括隐藏文件,隐藏文件以.开头
ls -l 详细的列出当前文件下的所有文件
drwxr-xr-x 3 linux linux 4096 7月 15 2021 code
d
文件类型 bsp-lcd
b:binary块设备文件
s : socket套接字文件
p:pipe管道文件
-:普通文件
l:link链接文件
c:char字符设备文件
d : direct目录文件
rwxr-xr-x 权限,每三个一组,分别代表用户权限,组权限,其他人权限
3 硬链接个数
linux 用户名
linux 组名
4096 大小,单位字节
7月 15 2021 时间戳
code 文件名
ls -h 以M,KB的方式显示文件
ls -lh 显示文件的详细信息并且文件大小单位是M,KB
man ls
cd enter 跳转到到当前路径下
cd . 跳转到当前路径
操作结果一样,记得加空格,不然报错
cd / 根目录下
cd ~ 家目录:/home/linux(这里家目录下有/,是因为这是绝对路径的表示方式)
cd.. 跳转到上一级路径
cd - 上一次路径
/表示分隔,跳转到非根目录下的时候不需要添加
比如,在home下跳转到linux不需要加/
现实当前所在位置(绝对路径)
查看当前用户
mkdir d1 新建目录文件
mkdir ~/d1 在加目录下新建目录文件d1
mkdir -p d1/d2/d3 嵌套新建目录文件
mkdir d1/d2/d3 是错误的,会报错
mkdir d1 d2 d3 建立三个独立目录文件(和上面区分)
touch filename 文件不存在,新建普通文件
文件存在,更新时间戳
rmdir d1 删除目录文件d1(只能删除空目录)
rm filename 删除指定文件
rm -r 删除文件(不管是目录文件还是普通文件都可以删)
直接rm dir会报错
rm -f 忽略提示信息,直接删除(在根用户下会有提示信息,一般也不在根目录下操作,感觉没啥必要)
rm -rf 在普通用户路径下,-r和-rf没什么区别
cp file1 file2 如果文件file2不存在,表示将文件file1复制一份儿取名为 file2
如果文件file2存在,表示将文件file1复制一份儿取名为 file2,会覆盖原来的 file2
cp file1 dir1 将文件file1复制一份,放到目录dir1里面
如果没有目录dir1,此命令依旧可以执行,并且会有dir1,此时dir1是文件不是目录,就相当于执行上一步操作,将file1复制一份取名为dir1,dir1是普通文件
文件类型不同,也不能重名
cp -r dir1 dir2 如果目录dir2不存在,表示将目录dir1复制一份儿取名为 dir2
如果目录dir2存在,表示将目录dir1复制一份放到 dir2 里面
mv file1 file2 file2存在,表示将文件file1重命名为 file2,会覆盖原来的 file2(其实就是file1成为file2,原来的file2就没了)
file2不存在,file1改名为file2
mv file1 dir1 dir1存在,file1移动到dir1里
dir1不存在,估计应该跟cp一样,我没试,可以自己试试
mv dir1 dir2 dir2存在,dir1移动到dir2里
dir2不存在,dir1改名为dir2
(不论dir1 ,dir2是否非空,都可以移动)
在程序里,一切以现象为准,有想法就去试,不要老想,现象是最准确的
练习
1.在用户的家目录下创建 目录文件 dir1 和 普通文件 file1
2.在家目录下给dir1目录嵌套创建 dir1/dir2/dir3/dir4/dir5
3.在家目录下直接一步进入到 dir4 里面
4.在dir4目录中将家目录下的file1 移动到上一级的dir3中
mv ~/file1 ~/dir1/dir2/dir3
mv ~/file1 ..
出错的原因是file1建到dir4里了
5.在dir4目录下创建一个目录文件 test
6.将test 复制到上一级的dir3中
7.在dir4中直接查看dir3中有哪些文件
8.在dir4中直接回到家目录 删除刚才创建的目录 dir1
cat filename 查看文件内容
cat -n filenamen 带行号显示文件内容
清屏
su 切换用户 需要密码
返回上一次用户,当已经是初始用户时,退出终端
补齐命令
补全的规则:
给定开头的一些关键字符,如果给定的信息足以确定唯一的文件,
则按一键tab键,就能补全了。
如果给定的信息,不足以确定唯一的文件,按一下tab键,能补到尽可能多的位置
然后再按tab键会提示,当前情况下有哪些满足的内容,根据提示的信息,
再给定一些关键信息,按tab键就能补全了
vi和vim是一样的
vi filename 打开vi编辑器
打开vi编辑器,默认的就是在命令行模式
一般是用来复制粘贴代码的
其他模式回到命令行模式 按 esc 键
常用操作
复制 yy:复制一行
nyy:复制n行
粘贴 p:光标后粘贴
P:光标前粘贴
剪切 dd:剪切一行
ndd: 剪切n行
定位 gg:定位到第一行
ngg:定位到第n行(在任意位置输入ngg都是到第n行,n不在当前行叠加)
G:定位到最后一行
撤销 u
取消撤销(重做) ctrl+r
查找指定单词 /word n,查找下一处,N查找上一处
到指定位置以后就可以插入想写的数据了
在命令行模式下,输入下面的按键,进入插入模式:
插入的相关指令
i:光标前插入
a:光标后插入
I:光标所在行行首插入
A:光标所在行行尾插入
o:光标的下一行插入
O:光标的上一行插入
常用操作
第行操作记得加“:”
取消查找后的高亮 :noh
也可以查找一个不存在单词
分屏操作多个文件 :vsp 切换窗口:ctrl ww(直接鼠标切换最简单)
保存退出 :w 保存
:wq 保存并退出 x一样
:waq 保存所有文件并退出(一般在vsp后用,保存打开的所有文件)
:q! 不保存强制退出
:qa! 所有文件不保存强制退出
行号 :set nu/number 显示行号
:nonu nonumber 不显示行号
批量替换(指定行替换,询问) :%s/word1/word2/g word1替换为word2
:m,ns/word1/word2/g 替换指定行的所有单词 ,此行无%
:%s/word1/word2/gc 会询问是否替换
linux下的C言编译器
直接编译指定.c文件,编译完成后会生成.out文件,默认执行文件的名字为a.out,a.out是执行文件
./a.out是运行可执行文件,执行这部以后才会出运行结果
自定义执行文件名字
编译流程:预处理->编译-> 汇编->链接
gcc -E test.c -o test.i 预处理:展开头文件,替换宏定义,删除注释
gcc -S test.i -o test.s 编译:查错,无错则生成汇编文件
gcc -c test.s -o test.o 汇编:将汇编文件生成对应二进制文件
gcc test.o -o test.out 链接:多个目标文件链接 链接库文件,生成对应可执行文件(可能会有多个.c文件还有对库的调用,链接就是干这个的)
计算机能处理的数据分为两大类:数值型数据 非数值型数据
非数值型数据就用acsii码表示
查看ASCII码表 man ascii
常见ASCII码表示
A:65
a:97
0:48
\0:0
\n:10
2bit可以存四个数 (00 01 10 11)
注:\ddd表示八进制
'\101'是十进制65
内存分配的最小单位:字节
基本类型
整型
浮点型
枚举类型
构造类型
数组
结构体
共用体
指针类型
空类型 void
整型又分为 有符号的(signed) 和 无符号的(unsigned)
所以以上类型都有signed和unsigned
signed:最高位为符号位,其余位照常按无符号的算 0负 1正
举例(都是原码) -1:1000 0001 -56:0011 1000
unsigned:无符号位
范围:
char -128~127
short -2^15~2^15-1
int -2^31~2^31-1
long long -2^63~2^63-1
按照原反补的形式求即可
原反补转换
注意:计算机存取数据时的规则
规则:存储时看数据(正负),取出时看类型(有无符号)
看类型的意思是, 计算机存储的是补码,取出时也以补码为准,取出时先确定好补码,再看类型,如果是无符号的那么原反补都一样,如果是有符号,在对当前补码的进行反码原码的转换
举例:
1.unsigned char a=-56;
printf("%d\n",a);输出结果:200
原因:
-56
存储时:
原:0011 1000
反:1100 0111
补:1100 1000
取出时:因为是无符号,所以
补:1100 1000
反:1100 1000
原:1100 1000
2.signed char a=235;
printf("%d\n",a);输出结果:-21
原因:
235
存储时: 正数原反补一样
原:1110 1011
反:1110 1011
补:1110 1011
取出时:有符号,所以需要减一转换为反码,再取反转换为原码
补:1110 1011
反:1110 1010
原:0001 0101
float:精确到小数点第8位
double:精确到小数点第16位
给变量赋值的就是常量,等号后面的,可以是数,也可以是具体的字符
数包括整型常量,浮点型,指数
前导符 输出时占位符 例如
整型常量
十进制 无 %d %u 1314
二进制 0b 无 0b10101
八进制 0 %o 0765
十六进制 0x %x 0x67EF
字符常量包括,单个字符和字符串
字符常量 %c 'M'
字符串常量 %s "hello"
还有单独的标识常量
标识常量 ----宏定义 #define
注意输出类型
long: %ld
long long: %lld
unsigned int %u
unsigned long %lu
unsigned long long %llu
C里小数是凑出来的,有的数只能无限接近,并不能是正好的
#include
int main()
{
float q=3.1415926;
printf("q1=%f\n",q);
printf("q2=%f\n",q);
printf("q3=%f\n",q);
return 0;
}
%f默认输出6位,%.nf输出n位小数,由q2可以看出C中小数是无限接近,并不能完全等于
#include
int main()
{
//小数也可以按指数形式输出
float c = 1234.56;
printf("c = %e\n", c);// 1.23456e3
float d = 2.3456e2;
printf("d = %f\n", d);// 245.560000
return 0;
}
整型和字符型在C里本质都是二进制数,所以可以互相转换,利用ASCII码
#include
int main()
{
//整型可以以字符型输出,只要在printf的时候改变%c为%d即可
char v1 = 66;
printf("v1 = [%d] [%c]\n", v1, v1); // 66 B
7 字符型常量可以输出整型类型,只要在printf的时候改变%c为%d即可
char v2 = '8';
printf("v2 = [%d] [%c]\n", v2, v2); // 56 8
char v3 = 10;
//char v3 = '\n';
printf("v3 = [%d] [%c]\n", v3, v3); // 10 回车
//思考
//1. 如何将字符 '8' 转换成 整数 8
char v4 = '8';
//v4 = v4 - 48;
v4 = v4 - '0'; //字符参与运算时 本质就是其对应的ascii参与运算
printf("v4 = %d\n", v4);//8
//2.如何将 大写字母 转换成 小写
char v5 = 'M';
//v5 = v5 + 32;
v5 = v5 + ('a'-'A');//和上面的写法本质是一样的
printf("v5 = %c\n", v5);// m
return 0;
}
每个字符串结尾都有隐藏的转义字符'\0',
'\0'占一个字节的内存,但不计入字符串长度
宏定义只是替换,写什么替换什么,注意运算顺序
注意事项:
1.宏定义是在预处理阶段完成替换的;
2.宏定义只是一个简单的替换,无脑替换;
例:
如果要按顺序计算的话,记得自己加括号
#include
#define X 10
#define Y 20
#define SUM1 X+Y
#define SUM2 (X+Y)
int main(int argc, const char *argv[])
{
int ret1 = SUM1 * 3;
// int ret1 = X+Y * 3;
// int ret1 = 10+20 * 3;
printf("ret1 = %d\n", ret1); //70
int ret2 = SUM2 * 3;
// int ret2 = (X+Y) * 3;
// int ret2 = (10+20) * 3;
printf("ret2 = %d\n", ret2); //90
return 0;
}
存储类型:const static volatile extern register auto
默认是auto
强制类型转换本身是不安全的,使用的时候要谨慎。
小的类型转大的类型 一般没问题
大的类型转小的类型 就可能会出现数据的截断和丢失。
格式:
(新的类型)变量;
int a=7;
int b=2;
float c=(float)a/(float)b;
printf("%d",c);
编译器自动进行的类型转换
#include
int main(int argc, const char *argv[])
{
float m = 3.567;
int n = m; //这种操作相当于对浮点型的取整操作 小数位舍弃
printf("n = %d\n", n); //3
return 0;
}
注:有符号和无符号类型的数值进行运算时,会按照无符号类型运算
这个代码是没有办法接收输出的
因为,接收a+b的值时无法固定接收类型,
如果以unsigned接收,那么无法确定到底是运算的时候改变了还是最后接受的时候改变了类型;
如果什么都不加,直接用int接收,结果会是-10,但其实上面的判断语句已经告诉我们编译器在运行时把数据按照unsigned执行了,用int接收看不出这个效果了。
int b=++a;
int b=a++;
前置递增:先自增,在赋值
后置递增:先赋值,在自增
逻辑与连接的多个表达式,如果遇到某个表达式为假了,后面的就都不执行了
逻辑或连接的多个表达式,如果遇到某个表达式为真了,后面的就都不执行
#include
int main(int argc, const char *argv[])
{
int x = 10;
int y = 20;
int z = 30;
if(x>0 && y<0 && ++z){//因为y<0为假 足以判断整个表达式为假 所以 ++z 不执行
printf("yes\n");
}else{
printf("no\n");
}
printf("x = %d y = %d z = %d\n", x, y, z);// 10 20 30
printf("-------------------------------------\n");
x = 10;
y = 20;
z = 30;
if(x<0 || y>0 || ++z){//因为y>0为真 足以判断整个表达式为真 所以 ++z 不执行
printf("yes\n");
}else{
printf("no\n");
}
printf("x = %d y = %d z = %d\n", x, y, z);// 10 20 30
return 0;
}
^:相同为0,相反为1
1与任何数都是其本身
0与任何数都为0
1或任何数都是1
0或任何数其本身
1异或任何数是其本身取反
0异或任何数是其本身
置一:|=(1< 清零:&=~(1< 对单片机理解有帮助,C51里的数码管都是共阴极接法,即共地,这样GPIO口输出高电平灯就亮,低电平灯就灭。 =将右边的值赋给左边,以右边的为准 和关系运算==区分 思考:如果讲判断if(a==b)写成if(a=b)会怎样,是否会报错? 不会报错,如果写成=,那么会将b的值赋给a,只要b不是零,if语句的结果就是真(负数也是真,非即为位真) 计算变量或类型的大小 用法sizeof(变量或类型) 当括号里为变量时,则计算变量所占内存空间大小 当括号里为数据类型时,则计算此类型所占内存 ----了解即可 所谓的逗号运算符,就是用逗号连接的多个表达式, 格式: (表达式1, 表达式2, ..., 表达式n); 执行逻辑: 就是从左到右依次运算,每个表达式都会运算, 最后一个表达式的结果,就是整个逗号运算符表达式的结果。 例: 单算移关与,异或逻条赋。 会用就行,不会可以搜 putchar/getchar:单个字符,getchar是返回值赋值char a=getchar(); puts/gets:字符串,字符串可以带空格,直接gets(s1); printf/scanf: 说下scanf scsnf("%c",c); 当获取的为数字时,scanf("%d%d%d",&num1,&num2,&num3);回车和空格都可以用来分隔数字,当数据达到输入的个数时,回车就代表结束了 获取字符时,scanf("%c%c%c",&a,&b,&c);是可以接收空格,回车这种空白字符的如果需要避免垃圾字符,可以用空格吃掉scanf("%c %c %c",&a,&b,&c); scanf("%s",s); 获取字符串时,不能获取空格,回车空格这种空白字符意味着结束,不再接受后面的字符 1.switch后面的()中可以是变量,也可以是表达式 一般情况下,都是整型或者字符类型,不能是浮点型 。 2.break的作用的是执行完某个分支的代码后,就立即结束整个switch..case语句 如果没有break,程序会继续执行下面case的代码块(不再判断,直接执行) 直到遇到break或者整个swtich..case语句结束----case击穿 3.default分支的作用,相当于if..else语句中的else分支, 如果前面的case都不满足,则执行default分支 如果不关心其他分支,整个default分支都可以不写 4. case后只能有一个常量,常量多了会报错 6.1goto goto本身是用来实现代码跳转的,注意只能在同一个函数中跳转。 实际中很少使用goto实现循环,代码太多跳来跳去会有问题,了解即可 10.1.1使用格式 代码块1; goto NEXT; 代码块2; NEXT: //标签 是一个标识符 要符合命名规范 代码块3; 10.1.2执行逻辑: 先执行代码块1,然后遇到goto语句,直接跳转到标签的位置继续向下执行代码块3, 而代码块2就被跳过了,不会被执行。 10.1.2 实例 思考:下面的代码会输出什么。 注:负数也为真,只有零为假 do..while 和 while 的区别 while:先判断 后执行 do..while:先执行 后判断 不管do..while的表达式为真还是为假,代码块都至少执行一次。 搞清表达式含义,别老写反了 表达式2:判断真假 表达式3: 改变变量的值,从而控制循环结束 for(表达式1; 表达式2; 表达式3){ 代码块; } 执行逻辑:先执行表达式1,然后执行表达式2,如果表达式2为真, 则执行代码块,然后执行表达式3, 然后再执行表达式2,如果还为真,则继续执行代码块和表达式3 直到表达式2为假,循环立即结束 注意搞清执行逻辑,然后才能判断对次数 break:结束本层循环 continue:结束本层的本次循环 return:结束整个程序 练习: 增加标志位,标志位清零 3.输出[1,1000]范围内所有的完数,及完数的个数 完数:一个数所有的真因子(即除了自身以外的约数)的和恰好等于它本身。 如:6 --> 1 + 2 + 3 == 6 初始化该初始化的值,否则会影响下次执行 4.输出[2, 100]范围内所有的质数 处理方式1:使用标志位解决 当判断不了的时候,可以增加标志位来解决问题 解决方式2:根据循环结束的条件判断 也可以加break,当满足条件的时候退出本次循环 类型相同,数组中每个元素在内存上都是连续的。不管几维数组,都是连续的 //数组越界访问的问题 编译器不会检查 //需要程序员自己检查 //数组越界导致的错误是不可预知的 // 可能不报错 // 可能段错误 // 可能修改了不该修改的数据 二维数组的元素在内存上也是连续的。 二维数组的本质也是一维数组, 多了一个行号,只是多了一种按行操作的方式而已 二维数组的列数很重要,因为他决定了按行操作时的跨度。 练习: 1.定义一个有10个元素的一维数组,类型为int 在终端输入十个数 来给这个数组赋值 找出数组中的最大值,及最大值的下标,并输出。 赋初值时以数组元素为基准,可以解决比较中的问题 这种方法可以少定义一个变量 注意数组的越界访问问题 作业: 杨辉三角 字符数组&字符串数组 字符: char s1[5] = {'h','e','l','l','o'}; 输出需要以循环的方式输出,像数字一样一位一位遍历 for(i = 0; i < 5; i++){ printf("%c", s1[i]); } putchar(10); 字符串:char s2[6] = {"abcde"}; char s3[6] = "world";//需要手动预留\0的位置 char s4[] = "beijing"; //系统自动留了\0的位置 输出利用%s直接以字符串形式输出 printf("s3 = [%s]\n", s3); 区分: 注意:如果不是字符串 就不能按字符串的方式操作 如:不能使用 printf的 %s 输出 %s 将后面的数据按字符串的方式输出 直到遇到 '\0' 才结束 如果没有遇到 就一直往后找 区分: 0 \0 '0' 0 和 '\0' 是一样的 ascii码都是 0 '0'的ascii码是 48 举例辨析 指针就是地址, 32位系统中 指针的大小都是4字节的 64位系统中 指针的大小都是8字节的 注意区分*在不同位置的不同含义 在定义指针变量的时候,*只是做一个标识符的作用,告诉后面的变量是一个指针类型的变量,赋值的时候*才表示对指针变量指向的数值进行操作。 指针的定义方式有两种,一种是定义的时候不赋值,一种是定义的时候直接赋值,也就是初始化的方式 第一种: int a=0; int *p; p=&a; 第二种: int a =0; int *p=&a;//定义一个指针变量p,p里存的是a的地址 *p=10;//给指针变量指向的地址赋值为10 区分上面两行 前面我们已经知道,变量的内存大小根据类型不同而不同,int类型占四个字节,char类型一个字节,对于多字节的变量比如int,short,long,指针变量在定义的时候 int *p=&a;存的就是变量的首地址,对应上图就是0x20 输出地址用%p类型 指针类型决定了其能操作的大小是多少 定义指针的时候可以是int *p; 可以是 char *p;指针类型是多少,就可以操作做多少内存,int类型的指针可以操作四字节的地址,char类型的指针可以操作一个字节的地址,所以指针类型一般和所操作的变量类型相同。 比如,如图int *p=&a;此时p就可以操作0x20-0x23的地址,如果是 char *p=&a;就只能操作0x20这块地址。 注:1.不能使用普通变量保存地址 普通变量可以从保存地址,但是不能对所保存的地址做赋值操作 long long value = &a; printf("value = %#llx\n", value);//只保存可以 *value = 1314;//但是普通变量不允许取 * 操作 2.指针只能保存已经分配的地址 对没有分配的地址进行取 * 操作 错误是不可预知的 int *p2 = 0x11223344; printf("p2 = %p\n", p2); *p2 = 100; 3. 常量是没有地址可言的 int *p = &10; 4. 一行可以定义多个指针,但是要注意写法 int *p3, p4;//这种写法 p3 是指针 p4就是一个int类型的变量 p3 = &a; p4 = &a; //这样写 p3 和 p4 才都是指针 int *p3, *p4; p3 = &a; p4 = &a; 5.定义指针如果不初始化,里面都是随机值,也就是指针变量指向随机地址 种指针叫做 野指针 , 野指针对程序是有害的 错误不可预知 int *p5; *p5 = 1234;//错误是不可预知的 6.为了避免野指针带来的问题,可以指向空指针 指向 NULL 的指针叫做 空指针 NULL 本质 (void *)0 应用程序对编号为0的地址取 *操作 一定是段错误 段错误也必不可预知的错误好!!! int *p6 = NULL; *p6 = 1234; //段错误 指针的运算本质就是指针保存的地址量作为运算量来参与运算。 既然是地址的运算,能进行的运算就是有限的了。 相同类型的指针变量之间做运算才有意义。 指针能作用的运算: 算数运算: + - ++ -- 关系运算:> < >= <= == != 赋值运算: = 小端储存:数据低位放到地址低位 大端储存:数据低位放到地址高位 验证: 按照这个逻辑,那a=10的时候数据是怎么存储的呢?是每一个字节都是10吗? 程序验证: 可以看到只有最低字节存放的是0x0A,其余都是0,那么是怎么放的呢,十六进制两位是一个字节0x0000000A 如果将p2改为0xC2A0结果就如下,当输出是十六进制的时候最高位如果是1,前面就会自动补1,所以c前面的f可以全部忽略 %s是将num以字符串形式输出,直到遇到\0才会停。注意取出的顺序,字符串不符合这个规则,字符串就按顺序放置,第一个字符在高位。 学习了数组,现在我们知道数组名就是数组的首地址 那么定义一个指针变量,就可以直接让它等于数组名,前面已经说过,指针的类型最好与其指向的数据类型相同,所以指针的操作空间和数组名是一样的。 一个字节是8bit 新的等价关系s[i]<==>*(s+i)<==>*(p+i)<==>p[i] 注:1.二维数组的数组名操作的是一行指针 2.二维数组的数组名已经超过了普通指针可以指向的范围,所以不能使用普通指针指向二维数组 3.保存二位数组的首地址用数组指针 p[0]<==>*(p+0)<==>*p int *p=s;可以遍历数组 本质是一个指针,指向一个二维数组,也叫行指针。 数组指针多用于将二维数组作为函数的参数传递时。 格式:数据类型 :(*指针变量名)[列宽] int (*p)[n] 之所以不能对一维数组名取地址的原因:----了解即可,记住不要这样用就行了 本质是一个数组,数组中每个元素都是一个指针。 格式:数据类型 *指针变量名[列宽] 指针数组里的存放的指针是不是也不可以改变 二级指针是用来保存一级指针的地址的。 二级指针多用于将一级指针的地址作为函数的参数传递时。 本质指针,指针指向一个函数 函数指针一般作为回调函数使用 常用定义类型如下 int (*p)() int (*p)(int ,int) int (*p)(int) 实际使用的时候是p(a,b);其实就是一个函数,本质和myadd(a,b) mysub(a,b)没有区别 int *p(int ,int); int *p(); 本质函数,p是函数名,如果要接指针函数的值,需要在main里额外定义一个指针变量来接函数的结果。和函数指针区分,函数指针的p是指针变量 注:1.指针函数的返回值不能是局部变量的地址,但可以是被static修饰的局部变量的地址,或者是 全局变量的地址,以及参数传递过来的地址(如 strcpy) 2.被static修饰的变量不能用变量赋值 本质数组,数组元素是一个函数指针int (*p[])(int,int) int (*p1)(int,int); int (*p2)(int,int); int (*p3)(int,int); int (*p4)(int,int); int a=10,b=10; int c=100,d=200; int (*q[4])()={NULL}; q[1]=p1(a,b); q[2]=p2(a,b); q[3]=p3(c,d); q[4]=p4(c,d) 不用这样定义很多个函数指针 p(a,b);就是一个函数,本质和myadd(a,b) mysub(a,b)没有区别 int (*(*p))() 全局变量和局部变量同名 函数返回值类型要和函数名前面的类型一致 形参是一级指针,也不一定是地址传递,也可能是一级指针的值传递 实参初始化形参,是把实参的值传给形参,加了取址符&存的形参存的才是实参的地址 只需传字符串首地址即可,以strcpy为例 遍历数组并打印为例, 整型数组传参时:既要传数组的首地址,还要传数组的长度 因为整型数组是没有 '\0' 作为结束标志的 用行指针做参数,也就是数组指针 数组指针必须定义列宽,所以可以不用列数这个参数 int main(int argc ,const char *argv[]){ } argc:终端输入的字符的个数 *argv[]:一个名为argv的指针数组,里面放的就是从终端输入的字符串的地址 不能返回变量的地址,函数接收以后被回收了, 1.const 修饰变量:不能通过变量名修改变量的值 修饰指针: const int *p:指向可以变,值不能变 int const *p:指向可以变,值不能变 int * const p:指向不能变,值可以变 const int * const p :都不能变 2.static: 延长变量生命周期至整个程序结束 限定作用域,static修饰的变量活函数只能在当前文件中访问 static修饰的全局变量,别的源文件不可用,只能本文件可用 static修饰的局部变量,生命周期是整个程序结束,函数结束以后,内部被static修饰的变量并不会消失,每次进入函数时,其值也不会被重置,而是会保留上一次运行的结果,虽然生 命周期延长了,但本质还是局部变量,函数外部不可调用。 3.extern: 外部声明,当所使用的变量在别的源文件中,在另一个文件想要调用,在变量前加extern 就可以实现外部变量的调用 需要注意的是,被static修饰的变量,即使加了extern也不可调用 register: 寄存器,被执行效率高 CPU取数据优先级:寄存器-->cache->内存 但寄存器个数有限,一般不用寄存器存取变量 寄存器不在内存中,所以寄存器是没有地址的,地址是内存的编号,所以register修饰的指针变量不能对其进行取地址操作 volatile: 防止编译器优化,要求编译器每次取值都在内存上取 在keil会见到,有时候编译器优化以后,会影响结果 auto: 变量不加任何存储类型,默认就是auto类型 由于栈区的内存空间都是由操作系统负责分配和回收的, 使用的过程中,不够灵活,空间的大小,和生命周期都不够灵活。 所以有些场景下,需要我们灵活操作内存的时候,就需要自己手动分配和回收了。 自己手动分配的内存是在堆区的。 功能:在堆区分配一个指定大小的内存空间 头文件:#include 参数: 内存大小 malloc(sizeof(数据类型)); 函数原型:void *malloc(size_t size); 返回值: 成功:分配的空间的首地址 失败:NULL int *p=malloc(sizeof(int)); 既然分配的是地址,需要指针变量来接,此处有个隐式强转,malloc的类型是void *,将void * 转换为int *类型,一般为了方便阅读,都写成显式强转,需要什么类型写成什么类型, 不显示强转 也不会报错,因为指针的强转是安全的 int *p=(int *)malloc(sizeof(int)); 功能:回收malloc分配的空间 参数:分配空间首地址 头文件: #include 返回值:无 回收的意思是回收这块地址的使用权 堆区自己分配的空间,不会主动被操作系统回收,而是需要我们自己在不再需要使用的时候 手动的调用 free 函数来进行回收,所谓的回收,是回收使用权, 相当于告诉操作系统,这块空间我不再使用了,你可以把他分给别人了 如果自己的程序中只分配空间,而不释放,会造成内存泄漏的问题 内存泄漏就是指,只分配不回收,导致的系统内存资源越来越少的问题 内存泄漏只发生在长时间运行的服务器程序上,因为进程结束后操作系统会回收进程的所有资源 回收完资源后,记得给栈区指针赋值为NULL,防止野指针 练习: 封装一个函数 hqyj_malloc,函数的功能是:在堆区分配一个长度5的int类型的数组空间 且将数组元素赋值成 10 20 30 40 50 main函数中可以通过调用hqyj_malloc来获取分配的首地址,且遍历输出数组元素 最后二级指针为什么会出错: NULL的本质是(void *)0,应用程序对编号为零的地址取*操作,一定是段错误 typedef给类型起别名 typedef unsigned int uint32_t 为什么要给类型起别名? 1.有些类型名字比较长,写起来不方便,如 unsigned long long int 这种情况就可以使用typedef起个别名 typedef unsigned long long int hqyj; 后面使用hqyj定义变量和使用 unsigned long long int定义变量是一样的 2.我们后面会学到枚举、结构体、共用体,这些类型名字都有两个单词组成 配合着typedef使用起来会更方便。 C语言中类型名 把定义变量的语句中的变量名去掉,剩下的就是类型名: 在定义变量的语句前加上typedef 原来的变量名 就变成了新的类型名: typedef和宏定义的区别,宏定义是无脑替换 枚举是一个基本类型,一旦订好了就是常量,是数据的有限罗列。 枚举是用来防止“魔鬼数字”的 1.enum和类型名构成一个类型,定义变量时 :enum 类型名 变量名 2.枚举变量如果不初始化,其值是0,依次递增 3.如果初始化,前面继续从零开始递增不受影响,初始化后面的依次递增 4.枚举类型的大小由最大的成员的大小决定,一般是4字节 5.枚举成员一旦定义好以后就都是常量 6.当局部变量名字和枚举成员冲突时,采用局部优先原则 构造类型,定义不需要() 结构体里面既可以是相同的数据类型的集合 也可以是不同的数据类型的集合 一般情况下,结构体多用于管理一组不同数据类型的数据 注意:结构体的使用方式和枚举类似,又略有不同 1.结构体成员之间用 分号 分隔 2.结构体的成员都是变量 3.结构体的成员在内存上都是连续的(涉及到内存对齐的问题) 4.结构体变量之间可以直接相互赋值 结构体写完也有分号!!! 声明时内部成员不能初始化,结构体是数据类型,没有分配内存,赋值逻辑相悖 变量 指针 例: 结构体数组的定义方式: struct 结构体类型名 数组名[下标]; 给内存空间清0 memset(首地址,0, 长度); 练习:使用结构体实现学生管理系统 ./a.out ---------------------------------------------------------------- | 1.添加 2.查找 3.修改 4.删除 | | 5.学员信息排序 6.输出所有学员信息 7.退出 | ---------------------------------------------------------------- 请输入你的选择: 代码实现: stu.h stu.c main.c memset(首地址,0,长度sizeof(结构体类型名/变量名)) 结构体大小也由成员决定 头文件: #include 函数原型:void *memset(void *s, int c, size_t n); 功能:从指针s指向的地址开始 向后填充 n 个字节的 c表示字符 C++中,结构体里面允许定义函数 C语言中,结构体里面是不允许定义函数的,但是可以有函数指针 64位系统:按最大的成员对齐 long double:32位系统 12字节 64位系统 16字节 32位系统: 如果成员都小于4字节,按照最大成员对齐 如果有成员大于4字节,都按4字节对齐 要特别注意char和short连续存储的问题!!! 存储数据一般都从偶数地址开始,没有从单数开始的 结构体中包含结构体时的对齐规则: 结构体位域是压缩结构体的一种手段。 定义共用体的格式以及共同体访问成员的方式,和结构体的用法一模一样 只不过是将struct改成union即可 共用体中所有的成员共用一块内存空间 共用体的所有成员首地址都是一样的 共用体的大小取决于成员中最大的那个 定义共用体的格式 union Test{ 数据类型1 成员1; 数据类型2 成员2; ... 数据类型n 成员n; }; 例: 请使用共用体,判断你使用的主机是大端存储还是小端存储?3.4赋值运算符
3.5sizeof
3.5逗号运算符
#include
3.5运算顺序顺口溜
四、常用输入输出函数
五.分支语句
5.1switch case
5.1.1注意事项
六、循环语句
#include
6.2while
int x = 2;
while(x--);
printf("%d\n", x); //-1
6.3for
#include
6.4辅助控制关键字
七、数组
7.1数组越界访问
7.2二维数组
#include
7.3字符数组&字符串数组
//这种会出现越界访问,出现的问题不可预知
char s5[5] = {'h','e','l','l','o'};
printf("s5 = [%s]\n", s5);
//如果想以字符串输出需要给\0留出位置
char s5[6] = {'h','e','l','l','o'};
printf("s5 = [%s]\n", s5);//hello
char s6[32] = "hello beijing0hqyj";
printf("s6 = [%s]\n", s6);//hello beijing0hqyj
s6[5] = '0';
printf("s6 = [%s]\n", s6);//hello0beijing0hqyj
s6[5] = '\0';篇
printf("s6 = [%s]\n", s6);//hello
//虽然输出的时候只输出前五个字符,但是实际的内容还是存在的
//可以用循环以%c的形式打印出来
s6[5] = 0;
printf("s6 = [%s]\n", s6);//hello
八、指针
8.1定义
8.2指针和变量的关系
8.3指针类型的作用
8.4指针类型的运算
#include
8.5大小端存储
int a=0x56677941;
int *p=&a;
char *q=(char*)p;
printf("%d",*q);
printf("%d",*(q+1));
printf("%d",*(q+2));
printf("%d",*(q+3));
int a=0x56677941;
int b=8098;
int *p=&a;
int p2=&b;
*p=0x12345678;
*p2=0x0A;
char *q=(char*)p;
char *q2=(char*)p2;
printf("%d",*q);
printf("%d",*(q+1));
printf("%d",*(q+2));
printf("%d",*(q+3));
printf("%d",*q2);
printf("%d",*(q2+1));
printf("%d",*(q2+2));
printf("%d",*(q2+3));
8.6指针和一维数组
int s[5]={1,2,3,4,5};
//那么定义一个指针变量,就可以直接写成
int *p=s;//既然p=s,说明数组名就是一个指针变量,指针变量可以进行加减,那么数组名也可以
printf("%d",s);
printf("%d",s+1);//相差一个int,数组名的操作空间 和 数组的类型是一致的
//那么对数组名进行取*呢
printf("*(s+0) = %d s[0] = %d\n", *(s+0), s[0]);// 1 1
printf("*(s+1) = %d s[1] = %d\n", *(s+1), s[1]);// 2 2
//可以看出是s[i]<==>*(s+i)这两者是等价的
//那么遍历数组的新方法
int i = 0;
for(i = 0; i < 5; i++){
//printf("%d ", s[i]);
//printf("%d ", *(s+i));
//printf("%d ", p[i]);
printf("%d ", *(p+i));
}
printf("\n");
//也就是数组名的下标就是对指针进行取*操作,就是在取指针指向的值
//那么就可以利用指针来操作数组
//也可以利用指针遍历
for(i = 0; i < 5; i++){
printf("%d ", *p++);
//printf("%d ", *s++);//错误的 s不能++
}
printf("\n");
789
//定义指针的方式
int *P=s;
int *p=&s[0];
//int *p=&s 错误写发,相当于给指针升维
//虽然定义的时候可以写成p=s,但是还是要区分p和s
//新的等价关系s[i]<==>*(s+i)<==>*(p+i)<==>p[i]
//p是变量,其指向的值可以改变,可以给p=0x20;赋其他地址的值可以++
//s是数组名,是常量,是字符常量,不可以++,虽然数组名不能赋值,但是数组名就是首地址,
//对数组名取*,相当于操作里面的数*s='H'是可以的
8.7指针和二维数组
#include
8.7数组指针
#include
#include
8.8指针数组
int main(int argc, const char *argv[])
{
//处理多个字符串 可以将其存储在二维数组里
char name1[4][64] = {
"zhangsan",
"lisi",
"fulajimier.fulajimiluoweiqi.pujing";
"zhaoliu"};
printf("%s\n", name1[0]);//zhangsan
printf("%s\n", name1[1]);//lisi
printf("%s\n", name1[2]);//fulajimier.fulajimiluoweiqi.pujing
printf("%s\n", name1[3]);//zhaoliu
//但是这种用法 不好 会造成空间上的严重浪费 因为需要以最长的字符串为准
printf("----------------------------------------\n");
//char *name[4]=NULL;初始化别写错了
*name[0]="zhangsan";
*name[1]="lisi";
*name[2]="fulajimier.fulajimiluoweiqi.pujing";
*name[2]= "zhaoliu";
//
8.9指针和字符串
int main(int argc, const char *argv[])
{
//可以将字符串保存在字符数组中
//s1是数组 在栈区 "hello world" 是字符串常量 在字符串常量区
//这个操作相当于用字符串常量区的 "hello world"
//给栈区的数组初始化
char s1[32] = "hello world";
//后面对s1的操作 操作的都是栈区的数组
//栈区的内容是允许修改的
*s1 = 'H';
printf("s1 = [%s]\n", s1);//Hello world
char s2[32] = "hello world";
//栈区定义多个数组,即使保存一样的数据,数组的首地址也不一样
//在栈区不同位置存放多个一样的hello world
printf("s1 = %p, s2 = %p\n", s1, s2);//不一样
//也可以使用指针直接指向字符串常量
//这种写法 指针变量p在 栈区 保存的地址是字符串常量区 "hello world"的地址
char *p1 = "hello world";
printf("p1 = %s\n", p1);//读操作允许
//字符串常量区的内容是不允许修改的
//*p1 = 'H';//段错误 不允许修改
//不管定义多少个指针,只要指向同一个字符串常量 那么保存的地址就是一样的
char *p2 = "hello world";
printf("p1 = %p, p2 = %p\n", p1, p2);//一样的
return 0;
}
8.10二级指针
int a = 10; //变量
int *p = &a; //一级指针
int **q = &p; //二级指针
#include
8.11函数指针
#include
#include
8.12指针函数
#include
8.13函数指针数组
#include
8.14函数指针数组指针
#include
九、函数
9.1一维数组的传参方式
9.1.1字符串传参
#include
9.1.2整型数组的传参方式
#include
9.2二维数组的传参方式
#include
9.3main函数的参数
#include
函数的返回值
十、存储类型
#include
#include
十一、动态内存的分配和回收
malloc
#include
free
#include
十二、typdef
int a
int a[]
int *a
int **a
int *a[]
int (*a)[]
int (*a)();
int *a();
int (*a[])()
int (*(*a))()
int
int []
int *
int **
int *[]
int (*)[]
int (*)();
int *();
int (*[])()
int (*(*))()
typedef int a;
typedef int *a;
typedef int **a;
typedef int a[5];
typedef int a[3][4];
typedef int *a[5];
typedef int (*a)[5];
typedef int (*a)(int);
typedef int (*a[5])(int);
#include
十三、枚举
1.概念
2.定义枚举的格式
enum 类型名 {
成员1,//用逗号分隔
成员2,
...
成员n //最后一个成员不需要逗号
};
#include
3. 定义枚举变量的格式
enum color{
red,
yellow,
purple=12,
green
}c1,c2;
c1=red;
c2=green;
enum color{
red,
yellow,
purple=12,
green
};
enum color c1=red;
enum Color c2;
c2 = red;
4. 枚举和typedef结合
typedef enum color{
red,
yellow,
purple=12,
green
}ful;
enum color c1=red;
ful c2;
typedef enum {
red,
yellow,
purple=12,
green
}ful;
ful c1=red;
ful c2;
十四、结构体
1. 概念
2. 定义结构体的格式
struct 类型名{
数据类型1 member1;
数据类型2 member2;
数据类型3 member3;//成员之间用分号分隔,且最后一个成员也需要分号
};//写完也需要分号
3. 定义结构体变量
struct student{
char name[32];
int age;
int id;
int score;
}
struct student stu1;
strcpy(stu1.name,"zhiang");
stu1.id=1;
stu1.age=23;
stu1.score=100;
struct student{
char name[32];
int age;
int id;
int score;
}
struct student *s;
strcpy(s->name,"zhiang");
s->id=1;
s->age=23;
s->score=100;
4. 结构体访问成员
#include
#include "list.h"
//创建顺序表
Sql * createlist(){
Sql *p=(Sql *)malloc(sizeof(Sql));
//memset(L,0,sizeof(Sql));
memset(p,0,sizeof(p));
return p;
}
void printmenu(){
printf("----------------------------------\n");
printf("| 1.添加 2.随机插 3.查找 4.删除 |\n");
printf("| 5.排序 6.输出 7.修改 8.去重 9.退出|\n");
printf("----------------------------------\n");
printf("请输入你的选择:");
printf("\n");
printf("\n");
}
//尾插
//第二个参数是啥呢?
//传的数据是L->sql数组里的值,那不应该是stu类型的结构体数组吗,所以是stu类型
void tailinsert(Sql *L, stu data){//栈区接收堆区的地址,
//L->index=0;//index在哪初始化,在这么,感觉有点问题
//memset初始化了index
//检查参数合法性
//检查数据合法性
L->sql[L->index]=data;//可以直接这样写欸,不用在L->sql[index].name啥的了
L->index++;
}
void tailinsert2(Sql *L){
stu s;
//while(1){
//注意清空,防止前面的垃圾数据干扰,清空的是学生信息结构体数组
memset(&s,0,sizeof(stu));
if (L->index
#ifndef _LIST_H_
#define _LIST_H_
#include
#include
5. 结构体变量的赋值和初始化的方式
方式1:
struct Test{
int a;
char b[32];
char c;
};
struct Test t1;
t1.a = 100;
strcpy(t1.b, "hello");
t1.c = 50;
方式2:
struct Test{
int a;
char b[32];
char c;
};
struct Test t1 = {10, "xiaoming", 20};
方式3:
struct Test{
int a;
char b[32];
char c;
}t1; //定义类型的同时定义变量
t1.a = 100;
strcpy(t1.b, "hello");
t1.c = 50;
方式4:
struct Test{
int a;
char b[32];
char c;
}t1 = {10, "xiaoming", 20}; //定义类型的同时定义变量 并初始化
方式5:
struct Test{
int a;
char b[32];
char c;
};
struct Test t1 = {.a = 100, .b = "hello"}; //部分初始化
struct Test t1 = {.a = 100, .c = 30}; //部分初始化
方式6:
struct Test{
int a;
char b[32];
char c;
};
struct Test t1;
t1 = (struct Test){10, "xiaoming", 20};
6. 结构体数组的初始化和赋值
方式1:
struct Test{
int a;
char b[32];
char c;
};
struct Test s[2];
s[0].a = 10;
strcpy(s[0].b, "hello");
s[0].c = 20;
s[1].a = 30;
strcpy(s[1].b, "beijing");
s[1].c = 40;
方式2:
struct Test{
int a;
char b[32];
char c;
};
struct Test s[2] = {
{10, "zhangsan", 20},
{30, "lisi", 40}
};
方式3:
struct Test{
int a;
char b[32];
char c;
};
struct Test s[3] = {
[0] = {10, "zhangsan", 20},
[2] = {30, "lisi", 40}
};
方式4:
struct Test{
int a;
char b[32];
char c;
};
struct Test s[3] = {
[0] = {.a = 10, .b = "zhangsan"},
[2] = {.a = 30, .c = 40}
};
7.结构体清零
struct Test{
int a;
char b[32];
char c;
};
struct Test t1;
memset(&t1,0,sizeof(t1));
memset(&t1,0,sizeof(struct Test));
struct Test *p1 = (struct Test *)malloc(sizeof(struct Test));
memset(p1, 0, sizeof(struct Test));
//注意前面不要写成下面的写法
//下面的写法有两处错误
// 1. &p1 表示的指针自身占用的内存空间的首地址
// 2. sizeof(p1) 就是一个指针大小 是固定值
memset(&p1, 0, sizeof(p1));
8.C语言结构体不能定义函数
#include
9.结构体对齐
struct test1{
char a;
char b;
char c;
}//3
struct test2{
short a;
char b;
}//4
struct test3{
char a;
char b;
int c;
}//8
struct test4{
char a;
int b;
char c;
}//12
struct test5{
char a;
long long b;
}//12
struct test6{
char a;
char b;
short c;
int d;
}//8
struct test7{
char a;
short b;
char c;
int d;
}//12
struct A{
int a;
char b;
};
struct B{
struct A m;
char n;
int k;
};//16 //因为结构体A中是按照4字节对齐的,所以结构体A定义的变量都是8字节的
//当将结构体A嵌套在结构体B中的时候,由于为了满足A自身的对齐 多给A分了三个字节
//这三个字节 B中的成员是不能占用的
struct C{
short a;
short b;
short c;
};
struct D{
struct C m;
char n;
int k;
};//12 //因为结构体C中是按照2字节对齐的, 所以结构体C定义的变量都是6字节的
//当将结构体C嵌套在结构体D中的时候,并没有为了满足C自身的对齐而额外给C分配空间
//而是因为结构体D中是4字节对齐的, 相当于将C放在D中的时候,为了满足D的对齐
//D给C额外分了两个字节,这两个字节D中的成员是可以占用的
10.结构体位域
#include
十五、共用体(联合体)--union
#include
union test17
{
char a;
int b;
}t1;
memset(&t1,0,sizeof(t1));
t1.b=0x12345678;
if(t1.a==0x78){
printf("小端存储\n");
}
if(t1.a==0x12){
printf("大端存储\n");
}