C查漏补缺

紫色是我没记住的,黄色是标注,需要知道的,红色是重点,粉红色是需要注意的点,黄色高亮是记错但相对没那么重要的点

目录在旁边,点红框就能看见目录了

C查漏补缺_第1张图片

壹、Ubuntu

1.快捷键

1.1快捷键方式打开终端

ctrl alt t:打开一个新终端

C查漏补缺_第2张图片

ctrl shift n:打开一个相同路径的新终端

C查漏补缺_第3张图片

ctrl shift t:左右分屏的方式打开一个新终端(路径相同)

C查漏补缺_第4张图片
1.2终端字体调整

放大:ctrl shift +

缩小:ctrl -

1.3复制和粘贴

复制:ctrl shift c

粘贴:ctrl shift v

1.4中文输入,先换输入法,在切换中英文

ctrl 空格 切换输入法

shift 切换中英文

2.命令行提示符

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\]"

3.linux文件系统结构

根目录,家目录的关系

家目录在根目录下,一个用户有一个家目录,普通用户只能操作自己的家目录下的内容

C查漏补缺_第5张图片

linux下一切皆文件

一个终端只需输一次密码

绝对路径:相对于根目录的路径

相对路径 :相对于当前所在目录的路径

假设在linux里想执行清除student下的文件1操作,

会报错,显示当前路径没有文件1

linux中所有和文件相关的命令都是即支持绝对路径,也支持相对路径的。

4.常用命令

格式:命令 选项(对参数的操作限制) 参数(文件目录之类的)

注意:1.格式要素之间都要有空格,空格个数无所谓

2.格式要素不一定全都有

3.执行,回车

4.1 ls

ls 列出当前路径下的所有文件

ls 文件名 列出指定的单个文件

ls 目录名 列出目录下的所有文件

ls -a 列出当前路径下的所有文件,包括隐藏文件,隐藏文件以.开头

ls -l 详细的列出当前文件下的所有文件

C查漏补缺_第6张图片

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

4.2cd命令

cd enter 跳转到到当前路径下

cd . 跳转到当前路径

操作结果一样,记得加空格,不然报错

C查漏补缺_第7张图片

cd / 根目录下

cd ~ 家目录:/home/linux(这里家目录下有/,是因为这是绝对路径的表示方式)

cd.. 跳转到上一级路径

cd - 上一次路径

/表示分隔,跳转到非根目录下的时候不需要添加

比如,在home下跳转到linux不需要加/

C查漏补缺_第8张图片

4.3pwd

现实当前所在位置(绝对路径)

4.4whoami

查看当前用户

4.5mkdir

mkdir d1 新建目录文件

mkdir ~/d1 在加目录下新建目录文件d1

mkdir -p d1/d2/d3 嵌套新建目录文件

mkdir d1/d2/d3 是错误的,会报错

mkdir d1 d2 d3 建立三个独立目录文件(和上面区分)

4.6touch

touch filename 文件不存在,新建普通文件

文件存在,更新时间戳

4.7rmdir

rmdir d1 删除目录文件d1(只能删除空目录)

4.8rm

rm filename 删除指定文件

rm -r 删除文件(不管是目录文件还是普通文件都可以删)

直接rm dir会报错

C查漏补缺_第9张图片

rm -f 忽略提示信息,直接删除(在根用户下会有提示信息,一般也不在根目录下操作,感觉没啥必要)

rm -rf 在普通用户路径下,-r和-rf没什么区别

4.9cp

cp file1 file2 如果文件file2不存在,表示将文件file1复制一份儿取名为 file2

如果文件file2存在,表示将文件file1复制一份儿取名为 file2,会覆盖原来的 file2

cp file1 dir1 将文件file1复制一份,放到目录dir1里面

如果没有目录dir1,此命令依旧可以执行,并且会有dir1,此时dir1是文件不是目录,就相当于执行上一步操作,将file1复制一份取名为dir1,dir1是普通文件

C查漏补缺_第10张图片
C查漏补缺_第11张图片

文件类型不同,也不能重名

C查漏补缺_第12张图片

cp -r dir1 dir2 如果目录dir2不存在,表示将目录dir1复制一份儿取名为 dir2

如果目录dir2存在,表示将目录dir1复制一份放到 dir2 里面

4.10mv

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是否非空,都可以移动)

在程序里,一切以现象为准,有想法就去试,不要老想,现象是最准确的

练习

C查漏补缺_第13张图片

1.在用户的家目录下创建 目录文件 dir1 和 普通文件 file1

C查漏补缺_第14张图片

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

C查漏补缺_第15张图片

4.11cat

cat filename 查看文件内容

cat -n filenamen 带行号显示文件内容

4.12clear

清屏

4.13exit

su 切换用户 需要密码

返回上一用户,当已经是初始用户时,退出终端

4.14tab

补齐命令

补全的规则:

给定开头的一些关键字符,如果给定的信息足以确定唯一的文件,

则按一键tab键,就能补全了。

如果给定的信息,不足以确定唯一的文件,按一下tab键,能补到尽可能多的位置

然后再按tab键会提示,当前情况下有哪些满足的内容,根据提示的信息,

再给定一些关键信息,按tab键就能补全了

5.vi编辑器

vi和vim是一样的

vi filename 打开vi编辑器

5.1vi编辑器的三种模式

5.1.1命令行模式

打开vi编辑器,默认的就是在命令行模式

一般是用来复制粘贴代码的

其他模式回到命令行模式 按 esc 键

常用操作

复制 yy:复制一行

nyy:复制n行

粘贴 p:光标后粘贴

P:光标前粘贴

剪切 dd:剪切一行

ndd: 剪切n行

定位 gg:定位到第一行

ngg:定位到第n行(在任意位置输入ngg都是到第n行,n不在当前行叠加)

G:定位到最后一行

撤销 u

取消撤销(重做) ctrl+r

查找指定单词 /word n,查找下一处,N查找上一处

到指定位置以后就可以插入想写的数据了

5.1.2插入模式

在命令行模式下,输入下面的按键,进入插入模式:

插入的相关指令

i:光标前插入

a:光标后插入

I:光标所在行行首插入

A:光标所在行行尾插入

o:光标的下一行插入

O:光标的上一行插入

5.1.3底行模式

常用操作

第行操作记得加“:”

取消查找后的高亮 :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 会询问是否替换

贰、C

一.gcc编译器

linux下的C言编译器

1.1编译方式

1.1.1gcc test.c

直接编译指定.c文件,编译完成后会生成.out文件,默认执行文件的名字为a.out,a.out是执行文件

./a.out是运行可执行文件,执行这部以后才会出运行结果

1.1.2gcc test.c -o xxx.out

自定义执行文件名字

1.1.3按照编译流程编译

编译流程:预处理->编译-> 汇编->链接

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

1、数据类型

内存分配的最小单位:字节

2、数据类型的分类

基本类型

整型

浮点型

枚举类型

构造类型

数组

结构体

共用体

指针类型

空类型 void

2.1整型
2.2.1char short int long long long都属于整型,整型里面的数据进行强制类型转换时可能会出错
C查漏补缺_第16张图片
2.2.2unsigned和signed

整型又分为 有符号的(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

2.2浮点型

float:精确到小数点第8位

double:精确到小数点第16位

2.3常量

给变量赋值的就是常量,等号后面的,可以是数,也可以是具体的字符

数包括整型常量,浮点型,指数

前导符 输出时占位符 例如

整型常量

十进制 无 %d %u 1314

二进制 0b 无 0b10101

八进制 0 %o 0765

十六进制 0x %x 0x67EF

字符常量包括,单个字符和字符串

字符常量 %c 'M'

字符串常量 %s "hello"

还有单独的标识常量

标识常量 ----宏定义 #define

2.3.1整型常量

注意输出类型

long: %ld

long long: %lld

unsigned int %u

unsigned long %lu

unsigned long long %llu

2.3.2浮点型常量

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;
}
C查漏补缺_第17张图片
C查漏补缺_第18张图片

%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;
}
2.3.3字符型常量

整型和字符型在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;
}
2.3.4字符串常量

每个字符串结尾都有隐藏的转义字符'\0',

'\0'占一个字节的内存,但不计入字符串长度

2.3.5表示常量(就是宏定义)

宏定义只是替换,写什么替换什么,注意运算顺序

注意事项:

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;
}

3.变量

存储类型:const static volatile extern register auto

默认是auto

4.强制类型转换

强制类型转换本身是不安全的,使用的时候要谨慎。

小的类型转大的类型 一般没问题

大的类型转小的类型 就可能会出现数据的截断和丢失。

4.1显式强转

格式:

(新的类型)变量;

int a=7;
int b=2;
float c=(float)a/(float)b;
printf("%d",c);
4.2隐式强转

编译器自动进行的类型转换

#include 

int main(int argc, const char *argv[])
{
    float m = 3.567;
    int n = m; //这种操作相当于对浮点型的取整操作 小数位舍弃
    printf("n = %d\n", n); //3

    return 0;
}

注:有符号和无符号类型的数值进行运算时,会按照无符号类型运算

C查漏补缺_第19张图片
C查漏补缺_第20张图片

这个代码是没有办法接收输出的

因为,接收a+b的值时无法固定接收类型,

如果以unsigned接收,那么无法确定到底是运算的时候改变了还是最后接受的时候改变了类型;

如果什么都不加,直接用int接收,结果会是-10,但其实上面的判断语句已经告诉我们编译器在运行时把数据按照unsigned执行了,用int接收看不出这个效果了。

C查漏补缺_第21张图片
C查漏补缺_第22张图片

三、运算符

3.1自增运算符

int b=++a;

int b=a++;

前置递增:先自增,在赋值

后置递增:先赋值,在自增

3.2逻辑运算短路

逻辑与连接的多个表达式,如果遇到某个表达式为假了,后面的就都不执行了

逻辑或连接的多个表达式,如果遇到某个表达式为真了,后面的就都不执行

#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;
}

3.3位运算符(& | ~)

^:相同为0,相反为1

1与任何数都是其本身

0与任何数都为0

1或任何数都是1

0或任何数其本身

1异或任何数是其本身取反

0异或任何数是其本身

置一:|=(1<

清零:&=~(1<

对单片机理解有帮助,C51里的数码管都是共阴极接法,即共地,这样GPIO口输出高电平灯就亮,低电平灯就灭。

C查漏补缺_第23张图片

3.4赋值运算符

=将右边的值赋给左边,以右边的为准

和关系运算==区分

思考:如果讲判断if(a==b)写成if(a=b)会怎样,是否会报错?

不会报错,如果写成=,那么会将b的值赋给a,只要b不是零,if语句的结果就是真(负数也是真,非即为位真)

3.5sizeof

计算变量或类型的大小

用法sizeof(变量或类型)

当括号里为变量时,则计算变量所占内存空间大小

当括号里为数据类型时,则计算此类型所占内存

3.5逗号运算符

----了解即可

所谓的逗号运算符,就是用逗号连接的多个表达式,

格式:

(表达式1, 表达式2, ..., 表达式n);

执行逻辑:

就是从左到右依次运算,每个表达式都会运算,

最后一个表达式的结果,就是整个逗号运算符表达式的结果。

例:

#include 

int main(int argc, const char *argv[])
{
    int a = 10;
    int b = 20;
    int c = 30;
    int ret = 0;

    ret = (++a, ++b, ++c);
    printf("ret = %d, a = %d, b = %d, c = %d\n", ret, a, b, c);//31 11 21 31

    //要注意 如果是逗号运算符 就必须用()把所有表达式扩起来
    //否则就不是逗号运算符了
    a = 10;
    b = 20;
    c = 30;
    ret = ++a, ++b, ++c;//这种写法 就是从左到右依次运算
                        //ret 中 保存的就是 ++a 的结果
    printf("ret = %d, a = %d, b = %d, c = %d\n", ret, a, b, c);//11 11 21 31

    return 0;
}

3.5运算顺序顺口溜

单算移关与,异或逻条赋。

C查漏补缺_第24张图片

四、常用输入输出函数

会用就行,不会可以搜

putchar/getchar:单个字符,getchar是返回值赋值char a=getchar();

puts/gets:字符串,字符串可以带空格,直接gets(s1);

printf/scanf:

说下scanf

scsnf("%c",c);

当获取的为数字时,scanf("%d%d%d",&num1,&num2,&num3);回车和空格都可以用来分隔数字,当数据达到输入的个数时,回车就代表结束了

C查漏补缺_第25张图片

获取字符时,scanf("%c%c%c",&a,&b,&c);是可以接收空格,回车这种空白字符的如果需要避免垃圾字符,可以用空格吃掉scanf("%c %c %c",&a,&b,&c);

scanf("%s",s);

获取字符串时,不能获取空格,回车空格这种空白字符意味着结束,不再接受后面的字符

五.分支语句

5.1switch case

5.1.1注意事项

1.switch后面的()中可以是变量,也可以是表达式

一般情况下,都是整型或者字符类型,不能是浮点型

2.break的作用的是执行完某个分支的代码后,就立即结束整个switch..case语句

如果没有break,程序会继续执行下面case的代码块(不再判断,直接执行)

直到遇到break或者整个swtich..case语句结束----case击穿

3.default分支的作用,相当于if..else语句中的else分支,

如果前面的case都不满足,则执行default分支

如果不关心其他分支,整个default分支都可以不写

4. case后只能有一个常量,常量多了会报错

C查漏补缺_第26张图片

六、循环语句

6.1goto

goto本身是用来实现代码跳转的,注意只能在同一个函数中跳转。

实际中很少使用goto实现循环,代码太多跳来跳去会有问题,了解即可

10.1.1使用格式

代码块1;

goto NEXT;

代码块2;

NEXT: //标签 是一个标识符 要符合命名规范

代码块3;

10.1.2执行逻辑:

先执行代码块1,然后遇到goto语句,直接跳转到标签的位置继续向下执行代码块3,

而代码块2就被跳过了,不会被执行。

10.1.2 实例

#include 
int main(){
    int sum = 0;
    int i = 1; //用作控制循环次数的变量 称之为 循环变量
                //循环变量使用  i  j  k 
LOOP:
    sum = sum + i;
    i++;
    if( i <= 100 ){
        goto LOOP;
    }
    printf("sum = %d\n", sum); // 5050
    return 0;
}

6.2while

思考:下面的代码会输出什么。

注:负数也为真,只有零为假

int x = 2;
while(x--);
printf("%d\n", x);  //-1   

do..while 和 while 的区别

while:先判断 后执行

do..while:先执行 后判断

不管do..while的表达式为真还是为假,代码块都至少执行一次。

6.3for

搞清表达式含义,别老写反了

表达式2:判断真假

表达式3: 改变变量的值,从而控制循环结束

for(表达式1; 表达式2; 表达式3){

代码块;

}

执行逻辑:先执行表达式1,然后执行表达式2,如果表达式2为真,

则执行代码块,然后执行表达式3,

然后再执行表达式2,如果还为真,则继续执行代码块和表达式3

直到表达式2为假,循环立即结束

注意搞清执行逻辑,然后才能判断对次数

#include 
int main(){
#if 1
    int i = 0;
    for(i = 0; i < 5; i++){
        printf("hello world %d\n", i);
    }
    printf("i = %d\n", i);//此处可以访问i i的值是5
#endif

#if 0
    for(int i = 0; i < 5; i++){
        printf("hello world %d\n", i);
    }
    printf("i = %d\n", i);//此处不可以访问i
                    //因为i是在表达式1中定义的 只能在循环内部使用
#endif
    
    return 0;
}

6.4辅助控制关键字

break:结束本层循环

continue:结束本层的本次循环

return:结束整个程序

练习:

增加标志位,标志位清零

3.输出[1,1000]范围内所有的完数,及完数的个数

完数:一个数所有的真因子(即除了自身以外的约数)的和恰好等于它本身。

如:6 --> 1 + 2 + 3 == 6

初始化该初始化的值,否则会影响下次执行

C查漏补缺_第27张图片

4.输出[2, 100]范围内所有的质数

处理方式1:使用标志位解决

当判断不了的时候,可以增加标志位来解决问题

C查漏补缺_第28张图片

解决方式2:根据循环结束的条件判断

也可以加break,当满足条件的时候退出本次循环

七、数组

类型相同,数组中每个元素在内存上都是连续的。不管几维数组,都是连续的

7.1数组越界访问

//数组越界访问的问题 编译器不会检查

//需要程序员自己检查

//数组越界导致的错误是不可预知的

// 可能不报错

// 可能段错误

// 可能修改了不该修改的数据

7.2二维数组

二维数组的元素在内存上也是连续的。

二维数组的本质也是一维数组,

多了一个行号,只是多了一种按行操作的方式而已

二维数组的列数很重要,因为他决定了按行操作时的跨度。

C查漏补缺_第29张图片

练习:

1.定义一个有10个元素的一维数组,类型为int

在终端输入十个数 来给这个数组赋值

找出数组中的最大值,及最大值的下标,并输出。

赋初值时以数组元素为基准,可以解决比较中的问题

C查漏补缺_第30张图片

这种方法可以少定义一个变量

C查漏补缺_第31张图片

注意数组的越界访问问题

C查漏补缺_第32张图片

作业:

杨辉三角

C查漏补缺_第33张图片
#include 

int main(int argc, const char *argv[])
{
    short a[10][10]={{1},{1},{1},{1},{1},{1},{1},{1},{1},{1}};
    /*short a[10][10]={0};
        for(int j=0;j<10;j++){
        a[j][0]=1;
        }
    for(int i=0;i<10;i++){
        for(int j=0;j<10;j++){
        printf("%d",a[i][j]);
        }
        printf("\n");
    }*/
    for(int i=1;i<10;i++){
        //s[i][0]=1;也可以直接在循环里直接给数组赋值
        //但相应的for循环里的列数就从1开始了
        //for(int j=1;j<10;j++)
        for(int j=0;j<10;j++){
            a[i][j]=a[i-1][j-1]+a[i-1][j];
           // printf("%4d",a[i][j]);
           //如果在这个地方打印会输出1 0 0 0 0 0 0 0 0 0
           //                        1 1 0 0 0 0 0 0 0 0
           //                        1 2 1 0 0 0 0 0 0 0这种形状的数据
        }
        //printf("\n");
    }    
    for(int i=0;i<10;i++){
        for(int j=0;j<=i;j++){
            //为了避免在多余的位置输出零,将输出的数据限制在行号以内,
            //第一行就输出一个,第二行就输出两个
            printf("%4d",a[i][j]);    
        }
        printf("\n");
    }

    return 0;
}

7.3字符数组&字符串数组

字符数组&字符串数组

字符: 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' 才结束 如果没有遇到 就一直往后找

//这种会出现越界访问,出现的问题不可预知
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

区分: 0 \0 '0'

0 和 '\0' 是一样的 ascii码都是 0

'0'的ascii码是 48

举例辨析

    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定义

指针就是地址,

32位系统中 指针的大小都是4字节的

64位系统中 指针的大小都是8字节的

注意区分*在不同位置的不同含义

定义指针变量的时候,*只是做一个标识符的作用,告诉后面的变量是一个指针类型的变量,赋值的时候*才表示对指针变量指向的数值进行操作。

指针的定义方式有两种,一种是定义的时候不赋值,一种是定义的时候直接赋值,也就是初始化的方式

第一种:

int a=0;

int *p;

p=&a;

第二种:

int a =0;

int *p=&a;//定义一个指针变量p,p里存的是a的地址

*p=10;//给指针变量指向的地址赋值为10

区分上面两行

8.2指针和变量的关系

C查漏补缺_第34张图片

前面我们已经知道,变量的内存大小根据类型不同而不同,int类型占四个字节,char类型一个字节,对于多字节的变量比如int,short,long,指针变量在定义的时候

int *p=&a;存的就是变量的首地址,对应上图就是0x20

输出地址用%p类型

8.3指针类型的作用

指针类型决定了其能操作的大小是多少

定义指针的时候可以是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; //段错误

8.4指针类型的运算

指针的运算本质就是指针保存的地址量作为运算量来参与运算。

既然是地址的运算,能进行的运算就是有限的了。

相同类型的指针变量之间做运算才有意义。

指针能作用的运算:

算数运算: + - ++ --

关系运算:> < >= <= == !=

赋值运算: =

#include 

int main(int argc, const char *argv[])
{
    //        0x20 0x24 0x28 0x2c 0x30
    int s[5] = {10, 20, 30, 40, 50};
    //一个指针加上一个整数n 表示加上n个指针的数据类型的大小
    int *p1 = &s[0];
//指针加法
  //指针加法:相差的是n个数据类型的大小
    int *p2 = p1+4; //p2 = p1+4*sizeof(int)
    printf("*p1 = %d\n", *p1);//10
    printf("*p2 = %d\n", *p2);//50
    printf("p1 = %p  p2 = %p\n", p1, p2);//相差16 == 4*sizeof(int)

//指针的强转是安全的
  //因为相同操作系统下指针变量的大小是相同的
  //比如下面的代码,
    char *p3 = (char *)&s[0];
    char *p4 = p3+4;
    printf("p3 = %p  p4 = %p\n", p3, p4);//相差4 == 4*sizeof(char)


//指针减法
  //指针减法的结果是相差的指针数据类型的个数
    int *p5 = &s[0];
    int *p6 = &s[3];
    int ret = p6-p5;
    printf("ret = %d\n", ret);//3  表示相差3个sizeof(int)

    short *p7 = (short *)&s[0];
    short *p8 = (short *)&s[3];
    ret = p8-p7;
    printf("ret = %d\n", ret);//6  表示相差6个sizeof(short)
//不管是int类型还是short类型差的内存大小都是12,但是数据类型不同,导致减法的结果不同


//自增自减运算 要注意下面的用法  以自增为例 自减同理
//需要注意运算顺序
    //          0x20 0x24 0x28 0x2c 0x30
    //int s[5] = {10, 20, 30, 40, 50};
    int *p9 = &s[0];
    int v1 = *++p9;
    printf("v1 = %d  p9 = %p  &s[1] = %p\n", v1, p9, &s[1]); // 20  后两个地址一样

    int *q1 = &s[0];
    int v2 = *q1++;
    printf("v2 = %d  q1 = %p  &s[1] = %p\n", v2, q1, &s[1]); // 10 后两个地址一样

    int *q2 = &s[0];
    int v3 = (*q2)++; // 等价于  int v3 = s[0]++;
    printf("v3 = %d  s[0] = %d\n", v3, s[0]); // 10 11

//指针的关系运算
    int *q3 = &s[0];
    int *q4 = &s[1];
    if(q4 > q3){
        printf("yes\n");
    }else{
        printf("no\n");
    }

    //指针的赋值运算 指针变量本质也是变量 可以被重新赋值
    int *q5 = &s[0];
    q5 = &s[2];
    int *q6 = &s[3];
    q5 = q6;

    return 0;
}

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));
C查漏补缺_第35张图片
C查漏补缺_第36张图片

按照这个逻辑,那a=10的时候数据是怎么存储的呢?是每一个字节都是10吗?

程序验证:

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));
C查漏补缺_第37张图片

可以看到只有最低字节存放的是0x0A,其余都是0,那么是怎么放的呢,十六进制两位是一个字节0x0000000A

C查漏补缺_第38张图片

如果将p2改为0xC2A0结果就如下,当输出是十六进制的时候最高位如果是1,前面就会自动补1,所以c前面的f可以全部忽略

C查漏补缺_第39张图片

%s是将num以字符串形式输出,直到遇到\0才会停。注意取出的顺序,字符串不符合这个规则,字符串就按顺序放置,第一个字符在高位。

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'是可以的
C查漏补缺_第40张图片
C查漏补缺_第41张图片

一个字节是8bit

新的等价关系s[i]<==>*(s+i)<==>*(p+i)<==>p[i]

8.7指针和二维数组

#include 

int main(int argc, const char *argv[])
{
    int s[3][5] = {1,2,3,4,99
                    5,6,7,8,77
                    9,10,11,12,22};
//二维数组数组名也是首地址
//根据一维数组
printf("s = %p\n", s);
//研究二维数组数组名的操作空间
printf("s+1 = %p\n", s+1);//相差20 --> 5*sizeof(int)
//发现是差了一行元素,二维数组名的操作空间是一行指针,叫行指针

//对数组名取*,相当于取数组名的首地址,这时操作空间就是一个元素了
//将操作空间是一行元素的指针 降维 成操作空间是一个元素的指针 列指针
printf("*s = %p\n", *s);
printf("*s+1 = %p\n", *s+1);//相差4 -->1*sizeof(int),相邻数组元素的差值


//对列指针再取*操作,才是操作内容
printf("**s = %d\n", **s);//1
printf("s[0][0] = %d\n", *(*(s+0)+0));//1
printf("s[0][2] = %d\n", *(*s+2));//3
printf("s[0][1] = %d\n", *(*s+2));//3
printf("s[2][1] = %d\n", *(*(s+2)+1));//10

//等价关系:s[i][j]<==>*((*(s+i)+j)
//二维数组的遍历
    int i = 0;
    int j = 0;
    for(i = 0; i < 3; i++){
        for(j = 0; j < 4; j++){
            //printf("%d  ", s[i][j]);
            //printf("%d  ", *(s[i]+j));//这种写法不常用
            printf("%d  ", *(*(s+i)+j));
        }
        printf("\n");
    }
    return 0;
}

注:1.二维数组的数组名操作的是一行指针

2.二维数组的数组名已经超过了普通指针可以指向的范围,所以不能使用普通指针指向二维数组

3.保存二位数组的首地址用数组指针

p[0]<==>*(p+0)<==>*p

int *p=s;可以遍历数组

8.7数组指针

本质是一个指针,指向一个二维数组,也叫行指针

数组指针多用于将二维数组作为函数的参数传递时。

格式:数据类型 :(*指针变量名)[列宽] int (*p)[n]

#include 

int main(int argc, const char *argv[])
{
    int s[3][5] = {1,2,3,4,99
                    5,6,7,8,77
                    9,10,11,12,22};
int (*p)[5]=s;
//int (*p)[5]=NULL;//不知道赋给谁,就赋给空指针
//p=s;



//数组指针指向二维数组后 操作就和二维数组数组名的操作是一样的了
//等价关系
//s[i][j]<==>*(*(s+i)+j)<==>(*(p+i)+j)<==>p[i][j]

//二维数组的遍历
int i = 0;
int j = 0;
for(i = 0; i < 3; i++){
    for(j = 0; j < 4; j++){
        //printf("%d  ", s[i][j]);
        //printf("%d  ", *(s[i]+j));//这种写法不常用
        //printf("%d  ", *(*(s+i)+j));
        //printf("%d  ", p[i][j]);
        //printf("%d  ", *(p[i]+j));//这种写法不常用
        printf("%d  ", *(*(p+i)+j));
    }
 printf("\n");
 }
//p和s的区别
//还是  p是变量  s是常量

return 0;
}

之所以不能对一维数组名取地址的原因:----了解即可,记住不要这样用就行了

#include 

int main(int argc, const char *argv[])
{
    int s[5] = {1,2,3,4,5};
    
    //注意:对一维数组的数组名取 & 操作 相当于指针的升维操作
    //把本来操作空间是一个元素的指针 升维 成操作空间是一行元素的指针
    //而我们的p操作空间只有一个元素
    //所以类型不匹配 会报警告
    //int *p = &s;
    //printf("%d\n", p[2]);//3
    
    //虽然可以使用数组指针来消除这个警告
    int (*p)[5] = &s;
    //但是此时的p 基本上就没有意义了
    //因为此时的 p+1 就加了一正行 而我们的数组就只有一行
    //也就是说  p+1 就已经越界了
    //所以不要对数组名进行 & 操作 !!!

    return 0;
}

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指针和字符串

C查漏补缺_第42张图片
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; //二级指针
C查漏补缺_第43张图片
#include 

int main(int argc, const char *argv[])
{
    int a = 10;
    int *p = &a;
    int **q = &p;
    //有了上述操作之后 有下面的等价关系
    //  a  <==>  *p  <==>  **q
    //  &a <==>  p   <==>  *q
    //  &p <==> q

//通过二级指针也可以操作变量 但是需要取 ** 操作
    **q = 1314;
    printf("a = %d\n", a);//1314

    //注意:用一级指针保存一级指针的地址
    //int *q2 = &p; //可以保存
    //但是一级指针不能取 ** 操作  所以保存了也没有意义
    //**q2 = 1314;//错误的

    return 0;
}

8.11函数指针

本质指针,指针指向一个函数

#include 

void my_add(int x, int y){
    printf("%d\n", x+y);
}

int main(int argc, const char *argv[])
{
    int a = 10;
    int b = 20;
    //通过函数名可以调用函数
    my_add(a, b);

    //也可以定义函数指针指向该函数
    void (*p)(int, int) = NULL;
    p = my_add; //函数名就是函数的首地址
    //函数指针指向函数后 通过函数指针也可以调用函数 
    p(a, b);

    return 0;
}

函数指针一般作为回调函数使用

常用定义类型如下

int (*p)()

int (*p)(int ,int)

int (*p)(int)

实际使用的时候是p(a,b);其实就是一个函数,本质和myadd(a,b) mysub(a,b)没有区别

#include 
int myadd(int x,int y){
    return x+y;
}

int mysub(int x,int y){
    return x-y;
}
int calculate(int x,int y,int (*p)(int, int)){
    return p(x,y);
}
int main(int argc, const char *argv[])
{
    int a=10,b=10;
    int (*p)(int,int)=NULL;
    printf("%d\n",calculate(a,b,myadd));
    printf("%d\n",calculate(a,b,mysub));
    return 0;
}

8.12指针函数

int *p(int ,int);

int *p();

本质函数,p是函数名,如果要接指针函数的值,需要在main里额外定义一个指针变量来接函数的结果。和函数指针区分,函数指针的p是指针变量

注:1.指针函数的返回值不能是局部变量的地址,但可以是被static修饰的局部变量的地址,或者是 全局变量的地址,以及参数传递过来的地址(如 strcpy)

2.被static修饰的变量不能用变量赋值

#include 
//int sum=0;
int* myfun(int x,int y){
    //指针函数的返回值不能是局部变量的地址,
    //但可以是被static修饰的局部变量的地址或者是全局变量的地址
    //int sum =x+y;
    //return ∑
    
    
    //被static修饰的变量不能用变量赋值
    //static int sum=x+y
    //return ∑
    //static int sum=0;
    static int sum=0;
        sum=x+y;
    return ∑
}
int main(int argc, const char *argv[])
{
    int a=10,b=10;
    int (*p)(int,int)=NULL;
    int *q=NULL;
    q=myfun(a,b);
    printf("%d\n",*q);

    return 0;
}

8.13函数指针数组

本质数组,数组元素是一个函数指针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)没有区别

#include 
int myadd(int x,int y){
    return x+y;
}
int mysub(int x,int y){
    return x-y;
}
int mymul(int x,int y){
    return x*y;
}

int main(int argc, const char *argv[])
{
    int (*q[3])()={NULL};
    int a=100,b=235;
    //q[0]=myadd(a,b);不能在这赋值,下面调用在传参,因为本质是p(a,b)=myadd(a,b);
    q[0]=myadd;
    q[1]=mysub;
    q[2]=mymul;
    printf("%d\n",q[0](a,b));
    printf("%d\n",q[1](a,b));
    printf("%d\n",q[2](a,b));
    return 0;
}

8.14函数指针数组指针

int (*(*p))()

#include 

int my_add(int x, int y){
    return x+y;
}

int my_sub(int x, int y){
    return x-y;
}

int main(int argc, const char *argv[])
{
    //函数指针数组
    int (*arr[2])(int, int) = {my_add, my_sub};

    //函数指针数组指针
    int (*(*p))(int, int) = arr;

    //调用函数
    printf("%d\n", p[0](10, 20));//30
    printf("%d\n", (*(p+1))(10, 20));//-10
    printf("%d\n", (p[1])(10, 20));//也可写成这样
    return 0;
}

九、函数

全局变量和局部变量同名

函数返回值类型要和函数名前面的类型一致

形参是一级指针,也不一定是地址传递,也可能是一级指针的值传递

实参初始化形参,是把实参的值传给形参,加了取址符&存的形参存的才是实参的地址

9.1一维数组的传参方式

9.1.1字符串传参

只需传字符串首地址即可,以strcpy为例

#include 
void mystrcpy(char* p,char *q){
    while(*q){
        *p=*q;
        p++;
        q++;
    }
    *p=*q;
}
int main(){
    char s1[32]="I LOVE SEVRNTEEN";
    char s2[32]="beautiful him":
    mystrcpy(s1,s2);
    printf("%s\n",s1);
    return 0;
}
9.1.2整型数组的传参方式

遍历数组并打印为例,

整型数组传参时:既要传数组的首地址,还要传数组的长度

因为整型数组是没有 '\0' 作为结束标志的

#include 
void printarr(int* p,int len){
    int i=0;
    while(i

9.2二维数组的传参方式

用行指针做参数,也就是数组指针

数组指针必须定义列宽,所以可以不用列数这个参数

#include 
void ptrint2arr(int (*p)[5],int hang){
    for(int i=0;i

9.3main函数的参数

int main(int argc ,const char *argv[]){

}

argc:终端输入的字符的个数

*argv[]:一个名为argv的指针数组,里面放的就是从终端输入的字符串的地址

#include 
#include 
#include 
int main(int argc ,const char *argv[4]){
    int ret=0;
    if(*argv[2]=='+'){
        ret=atoi(argv[1])+atoi(argv[3])
    }
    if(strcmp(argv[2]=="-")){
        ret=atoi(argv[1])-atoi(argv[3])
    }
}
    printf("%d\n",ret);
    return 0;
}

函数的返回值

不能返回变量的地址,函数接收以后被回收了,

十、存储类型

1.const

修饰变量:不能通过变量名修改变量的值

修饰指针: const int *p:指向可以变,值不能变

int const *p:指向可以变,值不能变

int * const p:指向不能变,值可以变

const int * const p :都不能变

2.static:

  1. 延长变量生命周期至整个程序结束

  1. 限定作用域,static修饰的变量活函数只能在当前文件中访问

static修饰的全局变量,别的源文件不可用,只能本文件可用

static修饰的局部变量,生命周期是整个程序结束,函数结束以后,内部被static修饰的变量并不会消失,每次进入函数时,其值也不会被重置,而是会保留上一次运行的结果,虽然生 命周期延长了,但本质还是局部变量,函数外部不可调用。

#include 

int a = 10;

void my_test1(){
    int num = 10;
    num++;
    printf("num = %d\n", num);
}

void my_test2(){
    static int num = 10; //num占用的空间在main函数执行前就分配好了 在data段
                        //这条指令每次调用函数不会重新执行
    num++;
    printf("num = %d\n", num);
}

int main(int argc, const char *argv[])
{
    my_test1();//11
    my_test1();//11
    my_test1();//11

    my_test2();//11
    my_test2();//12
    my_test2();//13

    //static修饰的局部变量和 全局变量 生命周期一样
    //但是作用域不一样  static修饰的变量作用也还是最近的{}
    printf("a = %d\n",a);
    //printf("num = %d\n", num);//错误的 出了作用域不能访问

    return 0;
}

3.extern:

外部声明,当所使用的变量在别的源文件中,在另一个文件想要调用,在变量前加extern 就可以实现外部变量的调用

需要注意的是,被static修饰的变量,即使加了extern也不可调用

C查漏补缺_第44张图片
#include 
void myfun(){
    extern int a;
    printf("%d\n",a);//不加extern不能调用,因为全局变量在次函数后声明
    
    //加了extern也不能用,因为b是局部变量,只能在自己的作用域内使用
    extern int b;
    printf("%d\n",b);
}
void my2(){
    int b=20;
}

int main (){
    //int **p=NULL;
    //int a=10;
    //*p=&a;
    //extern int a;
    //printf("%d\n",a);
    myfun();
    return 0;
}
int a=10;

register:

寄存器,被执行效率高

CPU取数据优先级:寄存器-->cache->内存

但寄存器个数有限,一般不用寄存器存取变量

寄存器不在内存中,所以寄存器是没有地址的,地址是内存的编号,所以register修饰的指针变量不能对其进行取地址操作

volatile:

防止编译器优化,要求编译器每次取值都在内存上取

在keil会见到,有时候编译器优化以后,会影响结果

auto:

变量不加任何存储类型,默认就是auto类型

十一、动态内存的分配和回收

  1. malloc

由于栈区的内存空间都是由操作系统负责分配和回收的,

使用的过程中,不够灵活,空间的大小,和生命周期都不够灵活。

所以有些场景下,需要我们灵活操作内存的时候,就需要自己手动分配和回收了。

自己手动分配的内存是在堆区的。

功能:在堆区分配一个指定大小的内存空间

头文件:#include

参数: 内存大小

malloc(sizeof(数据类型));

函数原型:void *malloc(size_t size);

返回值: 成功:分配的空间的首地址

失败:NULL

int *p=malloc(sizeof(int));

既然分配的是地址,需要指针变量来接,此处有个隐式强转,malloc的类型是void *,将void * 转换为int *类型,一般为了方便阅读,都写成显式强转,需要什么类型写成什么类型, 不显示强转 也不会报错,因为指针的强转是安全的

int *p=(int *)malloc(sizeof(int));

#include 
#include 

int main(int argc, const char *argv[])
{    
    int *p = (int *)malloc(sizeof(int));
    if(NULL == p){ //对返回值做检查 防止malloc失败导致的错误,一般很少会出现错误
        printf("内存分配失败..\n");
        return -1;
    }

//堆区的空间如果没有赋值之前 也是随机值
    printf("*p = %d\n", *p);

//通过指针给分配的空间复制
    *p = 1314;
    //通过指针读取分配的空间里面的值
    printf("*p = %d\n", *p);//1314

    //回收空间 防止内存泄漏
    free(p);
    p = NULL; //防止野指针

    return 0;
}
  1. free

功能:回收malloc分配的空间

参数:分配空间首地址

头文件: #include

返回值:无

回收的意思是回收这块地址的使用权

堆区自己分配的空间,不会主动被操作系统回收,而是需要我们自己在不再需要使用的时候

手动的调用 free 函数来进行回收,所谓的回收,是回收使用权,

相当于告诉操作系统,这块空间我不再使用了,你可以把他分给别人了

如果自己的程序中只分配空间,而不释放,会造成内存泄漏的问题

内存泄漏就是指,只分配不回收,导致的系统内存资源越来越少的问题

内存泄漏只发生在长时间运行的服务器程序上,因为进程结束后操作系统会回收进程的所有资源

回收完资源后,记得给栈区指针赋值为NULL,防止野指针

练习:

封装一个函数 hqyj_malloc,函数的功能是:在堆区分配一个长度5的int类型的数组空间

且将数组元素赋值成 10 20 30 40 50

main函数中可以通过调用hqyj_malloc来获取分配的首地址,且遍历输出数组元素

#include 
#include "string.h"
#include 
int *allocate(){
    int *p=(int *)malloc(sizeof(int)*5);
    for(int i=0;i<5;i++){
        p[i]=10*(i+1);
    }
    return p; 
}
void  allocate2(int **p){
    *p=(int *)malloc(sizeof(int)*5);
    for(int i=0;i<5;i++){
        (*p)[i]=10*(i+1);
    }
}
void myfree2(int **p){
    free(*p);
    p=NULL;
}
void myfree(int *p){
    free(p);
}
int main(int argc, const char *argv[])
{
    /*int *q=NULL;
    q=allocate();
    for(int i=0;i<5;i++){
        printf("%d ",q[i]);
    }
    printf("\n");
    myfree2(&q);*/
/*
    int *q=NULL;
    allocate2(&q);
    for(int i=0;i<5;i++){
        printf("%d ",q[i]);
    }
    printf("\n");
    myfree(q);
    q=NULL;*/

    //int **q=NULL;不行的原因是,在函数里是在操作NULL,
    //让NULL指向堆区的地址,实际上是不能给NULL赋值的,
    //对NULL进行操作,会段错误
    int *p = NULL;
    int **q=&p;    
        allocate2(q);
    for(int i=0;i<5;i++){
        printf("%d ",(*q)[i]);
    }
    printf("\n");
    myfree(*q);
    *q=NULL;
    q=NULL;
    
    return 0;
}

最后二级指针为什么会出错:

NULL的本质是(void *)0,应用程序对编号为零的地址取*操作,一定是段错误

C查漏补缺_第45张图片

十二、typdef

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语言中类型名

        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 原来的变量名 就变成了新的类型名

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);

typedef和宏定义的区别,宏定义是无脑替换

#include 

int main(){
    typedef  int *  aaaa
    define   int * bbbb
    int *p=NULL;
    aaaa p1,p2;//都是指针类型的变量
    p1 = &a;
    p2 = &b;
    bbbb q1,q2;//只有q1是指针类型
    //int *q1,q2;宏定义的写法相当于
    q1 = &a;
    q2 = b;

    return 0;
}

十三、枚举

1.概念

枚举是一个基本类型,一旦订好了就是常量,是数据的有限罗列。

枚举是用来防止“魔鬼数字”的

2.定义枚举的格式

enum 类型名 {
    成员1,//用逗号分隔
    成员2,
    ...
    成员n //最后一个成员不需要逗号
};

1.enum和类型名构成一个类型,定义变量时 :enum 类型名 变量名

2.枚举变量如果不初始化,其值是0,依次递增

3.如果初始化,前面继续从零开始递增不受影响,初始化后面的依次递增

4.枚举类型的大小由最大的成员的大小决定,一般是4字节

5.枚举成员一旦定义好以后就都是常量

6.当局部变量名字和枚举成员冲突时,采用局部优先原则

#include 

//定义了一个枚举类型
enum Color{
    red,
    green,
    white,
    balck = 100,
    blue,
    pink,
    yellow
};

int main(){
    enum Color c1;
    //使用枚举成员可以直接给枚举变量赋值
    c1 = red;
    printf("c1 = %d\n", c1); //0

    //初始化的写法
    enum Color c2=white;
    printf("c2 = %d\n", c2); //2

    //枚举变量之间可以直接相互赋值
    c1 = c2;
    printf("c1 = %d\n", c1); //2

    c1 = blue;
    printf("c1 = %d\n", c1); //101

    //可以用整数直接给枚举变量赋值 但是这样写就失去了枚举的意义了
    c1 = 102;
    printf("c1 = %d\n", c1); //102

    //枚举一旦定义好了 成员就是常量
    //pink=1000; //错误的

    printf("sizeof(enum Color) = %ld  sizeof(c1) = %ld\n",
        sizeof(enum Color), sizeof(c1));//4 

    //冲突时 局部优先
    int yellow = 2;
    c1 = yellow;
    printf("c1 = %d\n", c1);//2

    return 0;
}

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. 概念

构造类型,定义不需要()

结构体里面既可以是相同的数据类型的集合

也可以是不同的数据类型的集合

一般情况下,结构体多用于管理一组不同数据类型的数据

注意:结构体的使用方式和枚举类似,又略有不同

1.结构体成员之间用 分号 分隔

2.结构体的成员都是变量

3.结构体的成员在内存上都是连续的(涉及到内存对齐的问题)

4.结构体变量之间可以直接相互赋值

  1. 结构体写完也有分号!!!

  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 

//定义结构体类型
struct Test{
    int a;
    char b;
};

int main(){
    //每个结构体变量都有自己独立的内存空间
    //都有自己独立的成员  t1.a 和 t2.a 是不同的 
    struct Test t1;
    struct Test t2;
    //结构体变量访问成员
    t1.a = 10;
    t1.b = 20;
    t2.a = 30;
    t2.b = 40;
    printf("t1.a = %d  t1.b = %d\n", t1.a, t1.b);//10 20
    printf("t2.a = %d  t2.b = %d\n", t2.a, t2.b);//30 40

    //结构体指针访问成员
    struct Test *p1 = &t1;
    struct Test *p2 = (struct Test *)malloc(sizeof(struct Test));
    p2->a = 50;
    p2->b = 60;
    printf("p1->a = %d  p1->b = %d\n", p1->a, p1->b);//10 20
    printf("p2->a = %d  p2->b = %d\n", p2->a, p2->b);//50 60
    free(p2);
    p2 = NULL;//防止野指针

    return 0;
}

结构体数组的定义方式:

struct 结构体类型名 数组名[下标];

给内存空间清0

memset(首地址,0, 长度);

练习:使用结构体实现学生管理系统

./a.out

----------------------------------------------------------------

| 1.添加 2.查找 3.修改 4.删除 |

| 5.学员信息排序 6.输出所有学员信息 7.退出 |

----------------------------------------------------------------

请输入你的选择:

代码实现:

stu.h

#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->indexsql[L->index]=s;//可以直接这样写欸,不用在L->sql[index].name啥的了
        L->index++;
        }
        else {
            printf("人数已满\n");
            //break;
            }
    //}
}
int randominsert(Sql *L,int pos){
    stu s;
    //int pos=0;
    printf("index=%d",L->index);
    if (L->indexnum){
        //printf("输入数据有误");
        return -1;
    }
        printf("请输入学生信息:");
        scanf("%s",s.name);
        scanf("%s",s.sex);
        scanf("%d",&s.id);
        scanf("%d",&s.age);
        scanf("%d",&s.score);
        for(int i=L->index;i>pos;i--){
        L->sql[i]=L->sql[i-1];
        }
        L->sql[pos]=s;
        L->index++;
    }
    else{
        printf("人数已满\n");
        return -1;
    }
}
int randominsert2(Sql *L){
    stu s;
    int pos=0;
    printf("index=%d",L->index);
    if (L->index>=num){
       printf("人数已满\n");
        return -1;
        }
    else{
      if(pos>num){
        printf("输入数据有误");
        return -1;
        }   
        printf("请输入插入位置:");
        scanf("%d",&pos);
        printf("请输入学生信息:");
        scanf("%s",s.name);
        scanf("%s",s.sex);
        scanf("%d",&s.id);
        scanf("%d",&s.age);
        scanf("%d",&s.score);
        for(int i=L->index;i>pos;i--){
        L->sql[i]=L->sql[i-1];
        }
        L->sql[pos]=s;
        L->index++; 
    } 
    }
/*int randominsert3(Sql *L,int pos,stu data){
    if ((L->index>num)|(L->index=num)){
       printf("人数已满\n");
        return -1;
        }
    else{
      if(pos>num){
        printf("输入数据有误");
        return -1;
        }   
         for(int i=L->index;i>pos;i--){
        L->sql[i]=L->sql[i-1];
        }
        L->sql[pos]=data;
        L->index++; 
    }
}*/
//学号删除
void iddelete(Sql *L){
    int id=0;
    printf("请输入学号:");
    scanf("%d",&id);
    for(int i=id;iindex-1;i++){
        if(L->sql[i].id==id){
            L->sql[i]=L->sql[i+1];
        }
    }
    L->index--;
}
//任意位置删除
int posdelete(Sql *L,int pos){
    //printf("请输入位置:");
    //scanf("%d",&pos);
    for(int i=pos;iindex-1;i++){
        L->sql[i]=L->sql[i+1];
    }
    L->index--;
}
//姓名查找
stu* namefind(Sql *L){
    char name[32]={0};
    printf("请输入学生姓名:");
    //gets(name);
    scanf("%s",name);
    for(int i=0;iindex;i++){
    if(strcmp(L->sql[i].name,name)==0){
        printf("%s %s %d %d %d\n",L->sql[i].name,L->sql[i].sex,L->sql[i].id,
    L->sql[i].age,L->sql[i].score);
        //return &(L->sql[i]);
    }
    else return NULL;
    }
}
//根据位置查找
void  posfind(Sql *L,int pos){
    printf("请输入位置:");
    scanf("%d",&pos);
    printf("%s %s %d %d %d\n",L->sql[pos].name,L->sql[pos].sex,L->sql[pos].id,
    L->sql[pos].age,L->sql[pos].score);

}

//根据姓名修改
stu* changeinfo(Sql *L){
    char name[32]={0};
    int choose=0;
    int n=0;
    printf("请输入学生姓名:");
    //gets(name);
    scanf("%s",name);
    for(int i=0;iindex;i++){
        if(strcmp(L->sql[i].name,name)==0){
            n=i;
            break;
        }
        else return NULL;
    }
    printf("1.学号  2.年龄  3.成绩  4.性别\n");
        printf("请输入修改信息:");
            scanf("%d",&choose);
        switch(choose){
            case 1:
                printf("请输入学号:");
                scanf("%d",&L->sql[n].id);
                break;
            case 2:
                printf("请输入年龄:");
                scanf("%d",&L->sql[n].age);
                break;
            case 3:
                printf("请输入成绩:");
                scanf("%d",&L->sql[n].score);
                break;
            case 4:
                printf("请输入性别:");
                scanf("%s",L->sql[n].sex);
                break;
            default:
                printf("输入无效\n");
                break;
        }
        //L->sql[n]=s;不这样写
        printf("插入成功\n");
}

//去重
void quchong(Sql *L){
    stu s;
    if((L->index==0) |(L->index==1)){
        printf("无需去重");
    }
    for(int i=0;iindex;i++){
        for(int j=i+1;jindex-i;j++){
            if((strcmp(L->sql[i].name,L->sql[j].name)==0)&&(strcmp(L->sql[i].sex,L->sql[j].sex)==0)&&\
            (L->sql[i].id==L->sql[j].id)&&(L->sql[i].age==L->sql[j].age)&&(L->sql[i].score==L->sql[j].score)){
                //s=L->sql[i];
                posdelete(L,j);
                j--;//跳过一个没比,自减一次,然后在执行for里的++最终就回到当前位置了
            }
        }
    }
}

//按成绩排序
void sort(Sql *L){
    for(int i=0;iindex-1;i++){
        for(int j=0;jindex-i-1;j++){
            if(L->sql[j].scoresql[j+1].score){
                stu temp={0};
                temp=L->sql[j];
                L->sql[j]=L->sql[j+1];
                L->sql[j+1]=temp;
            }
        }
    }
}

//遍历
void printlist(Sql *L){
    for(int i=0;iindex;i++){
        printf("%s %s %d %d %d\n",L->sql[i].name,L->sql[i].sex,L->sql[i].id,
    L->sql[i].age,L->sql[i].score);
    }
    
}
//释放
void freelist(Sql **L){
    free(*L);
    //L=NULL;
}

stu.c

#ifndef _LIST_H_
#define _LIST_H_

#include 
#include 
#include 

#define num 5

typedef struct student{
    char name[32];
    char sex[5];
    int id;
    int age;
    int score;
}stu;

typedef struct SQL{
    stu sql[num];
    int index;
}Sql;

//创建顺序表
Sql *createlist();
void printmenu();

//尾插
void tailinsert(Sql *L,stu s);
void tailinsert2(Sql *L);

//随机插入
int randominsert(Sql *L,int pos);
int randominsert2(Sql *L);

//删除
void iddelete(Sql *L);
int posdelete(Sql *L,int pos);

//查找
stu* namefind(Sql *L);
void posfind(Sql *L,int pos);

//修改
stu* changeinfo(Sql *L);

//去重
void quchong(Sql *L);
//排序
void sort(Sql *L);

//遍历
void printlist(Sql *L);

//释放
void freelist(Sql **L);


#endif  /*_LIST_H_*/

main.c

#include 
#include "list.h"

int main(){
    Sql *L=createlist();
    stu s;
    int choose=0;
    int pos=0;
    //L->index=0;memset初始化index,不需要单独初始化index
    /*while(1){
        memset(&s,0,sizeof(stu));
        if (L->indexsql[count].name);
        //scanf("%s",L->sql[count].sex);
        //scanf("%d",L->sql[count].id);
        //scanf("%d",L->sql[count].age);
        //scanf("%d",L->sql[count].score);
        scanf("%s",s.name);
        scanf("%s",s.sex);
        scanf("%d",&s.id);
        scanf("%d",&s.age);
        scanf("%d",&s.score);
        //tailinsert(L,L->sql[count]);参数应该是前面定义的stu类型的s
        tailinsert(L,s); 
        }
        else {
            printf("人数已满\n");
            break;}
    }*/
    while(1){
    printmenu();
    scanf("%d",&choose);
    if(choose==9){
        freelist(&L);
        break;
    }
    switch (choose)
    {
    case 1:
        tailinsert2(L);
        break;
    case 2:
        //randominsert(L);
        randominsert2(L);
        break;
    case 3:
        namefind(L);
        //posfind(L,pos);
        break;
    case 4:
        //posdelete(L,pos);
        iddelete(L);
        break;
    case 5:
        sort(L);
        break;
    
    case 6:
        printlist(L);
        break;

    case 7:
        changeinfo(L);
        break;
    case 8:
        quchong(L);
        break;

    default:
    printf("输入数字无效\n");
        break;
    }

    }
    
    return 0;
}

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.结构体清零

memset(首地址,0,长度sizeof(结构体类型名/变量名))

结构体大小也由成员决定

头文件: #include

函数原型:void *memset(void *s, int c, size_t n);

功能:从指针s指向的地址开始 向后填充 n 个字节的 c表示字符

 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语言结构体不能定义函数

C++中,结构体里面允许定义函数

C语言中,结构体里面是不允许定义函数的,但是可以有函数指针

#include 

int my_add(int x, int y){
    return x+y;
}

int my_sub(int x, int y){
    return x-y;
}

struct test{
    int (*p)(int ,int);
};

int main(int argc, const char *argv[])
{
    struct test t1={.p=my_add};//部分初始化    
    struct Test t2;
    t2.p=my_sub;
    printf("%d\n", t1.p(10, 20));//30
    printf("%d\n", t2.p(10, 20));//-10

    return 0;
}

9.结构体对齐

64位系统:按最大的成员对齐

long double:32位系统 12字节

64位系统 16字节

32位系统:

  1. 如果成员都小于4字节,按照最大成员对齐

  1. 如果有成员大于4字节,都按4字节对齐

  1. 要特别注意char和short连续存储的问题!!!

存储数据一般都从偶数地址开始,没有从单数开始的

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
C查漏补缺_第46张图片

结构体中包含结构体时的对齐规则:

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 

//结构体位域的用法
struct Led{
    unsigned char led0:1;   //本来unsigned char 应该占用8bit
    unsigned char led1:1;   //使用 :1 的方式指定 每个成员只占1个bit位
    unsigned char led2:1;
    unsigned char led3:1;
    unsigned char led4:1;
    unsigned char led5:1;
    unsigned char led6:1;
    unsigned char led7:1;
};

int main(int argc, const char *argv[])
{
    printf("%ld\n", sizeof(struct Led)); //1
    struct Led my_led;  //1字节
    my_led.led0 = 1;
    my_led.led1 = 0;
    printf("led0 = %d\n", my_led.led0);
    printf("led1 = %d\n", my_led.led1);

    //注意 结构体压缩后 每个成员占用的bit少了 能存储的数据范围也随之变小了
    //my_led.led2 = 2; //溢出了
    //printf("led2 = %d\n", my_led.led2); //  10  1溢出  结果是0

    return 0;
}

十五、共用体(联合体)--union

  1. 定义共用体的格式以及共同体访问成员的方式,和结构体的用法一模一样

只不过是将struct改成union即可

  1. 共用体中所有的成员共用一块内存空间

  1. 共用体的所有成员首地址都是一样的

  1. 共用体的大小取决于成员中最大的那个

定义共用体的格式

union Test{

数据类型1 成员1;

数据类型2 成员2;

...

数据类型n 成员n;

};

例:

#include 
#include 

union Test{
        char a;
        short b;
        int c;
};

int main(int argc, const char *argv[])
{
        printf("sizeof(union Test) = %ld\n", sizeof(union Test)); //4

        union Test t1;
        memset(&t1, 0, sizeof(t1));
        t1.a = 123;
        //由于共用体的所有成员是共用同一块内存空间的 所以
        //修改其中一个成员  其他成员的值 也会随之发生变化
        //所以使用共用体时要谨慎,他是不安全的
        printf("t1.c = %d\n", t1.c);//123

        //一样的
        printf("&t1.a = %p\n", &t1.a);
        printf("&t1.b = %p\n", &t1.b);
        printf("&t1.c = %p\n", &t1.c);

        return 0;
}

请使用共用体,判断你使用的主机是大端存储还是小端存储?

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");
    }

你可能感兴趣的:(ubuntu,linux,运维)