C语言笔记

C编译器

gcc -> cc

虽然我们称gccC语言的编译器,但使用gcc编译C语言源代码文件不仅仅是编译的过程,而是要经历四个相互关联的步骤预处理(也称预编译)->编译->汇编->链接.

1.从一个.c文件编译成可执行文件需要经历4个阶段

预处理器

编译器

汇编器

链接器

2.预处理时需要哪些操作

文件包含

宏定义

条件编译

 

gcc

gcc 文件名 编译.c程序

 

gcc hello.c

什么也没有提示 证明程序没有问题(语法),编译通过

并不代表程序执行完全正确

报出警告 会生成可执行文件,有可能执行成功

报出错误 100%程序有问题,不会生成可执行文件

 

gcc -Wall 1.c

W 警告

all 所有

gcc -o hello hello.c

指定编译之后生成的可执行文件的名字

编译生成 二进制可执行文件a.out

执行./a.out

代码实例

#include <stdio.h>

/*

#include 文件包含

C语言中以#开头是预处理的内容

预处理阶段包括 文件包含 宏定义 条件编译

stdio.h 头文件(.h结尾)标准的输入输出

<...> 默认从/usr/include找头文件

"..." 默认从本地目录中找头文件

*/

 

int main(void)//void

{

printf("hello world!");

/*

printf是一个功能函数(打印输出的函数 终端中)

C语言中双引号引起来的表示为 字符串

C语言中单引号引起来的表示为 字符

字符串 是由 一个一个字符组成的

默认字符串最后有尾0 '\0'

"hello" = 'h' + 'e' + 'l' + 'l' + 'o' + '\0'

*/

 

return 0;

/*

return 也属于跳转语句,默认函数中碰到return函数就结束了

return之后的值会返回给调用者

返回值

1.返回的是结算结果

2.返回函数执行状态

0 函数正常结束

正值 函数有异常

负值 函数有错误

*/

}

/*

main函数(主函数)

当执行程序时是从主函数进入,从上往下逐条逐条执行,程序结束时从主函数退出

1.函数的声明

函数的返回值类型 函数名(参数);//形参

2.函数的实现

函数的返回值类型 函数名(参数)//形参

{

函数功能;

}

3.函数的调用

函数名(参数);//实参

*/

 

 

printf打印之转义字符(见C截图)

 

注意虽然在写转义字符时是 \ + X,但是是一个字符

 

转义字符 意义 ASCII码值(十进制)

\a 响铃(BEL) 007

\b 退格(BS) ,将当前位置移到前一列 008

\f 换页(FF),将当前位置移到下页开头 012

\n 换行(LF) ,将当前位置移到下一行开头 010

\r 回车(CR) ,将当前位置移到本行开头 013

\t 水平制表(HT) (跳到下一个TAB位置) 009

\v 垂直制表(VT) 011

\\ 代表一个反斜线字符''\' 092

\' 代表一个单引号(撇号)字符 039

\" 代表一个双引号字符 034

\? 代表一个问号 063

\0 空字符(NULL) 000

\ddd 13位八进制数所代表的任意字符 三位八进制

\xhh 12位十六进制所代表的任意字符 二位十六进制

 

printf打印之标准化输出格式(见C截图)

字符 对应数据类型 含义

d / i int 接受整数值并将它表示为有符号的十进制整数,i是老式写法

o unsigned int 无符号8进制整数(不输出前缀0)

u unsigned int 无符号10进制整数

x / X unsigned int 无符号16进制整数,x对应的是abcdef,X对应的是ABCDEF(不输出前缀0x)

f(lf) float(double) 单精度浮点数用f,双精度浮点数用lf(尤其scanf不能混用)

e / E double 科学计数法表示的数,此处"e"的大小写代表在输出时用的“e”的大小写

g / G double 使用以上两种中最短的形式,大小写的使用同%e%E

c char 字符型。可以把输入的数字按照ASCII码相应转换为对应的字符

s / S char * 字符串。输出字符串中的字符直至字符串中的空字符

(字符串以'\0‘结尾,这个'\0'即空字符)

p void * 16进制形式输出指针

n int * 到此字符之前为止,一共输出的字符个数,不输出文本

% 无输入 不进行转换,输出字符‘%’(百分号)本身

m 无 打印errno值对应的出错内容,(: printf("%m\n"); )

 

注:%g%G在小数点位数四位或指数大于等于精度时用%e%E,否则用%f

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

标准化输出格式

是由 % + * 组成,不是字符

% 是占位符号

%.2f 输出小数点后两位变量

%.1f 输出小数点后一位

%3d 输出的十进制数字最少占用3个字符位置

%03d 空出的补0

/*

标准化输出格式

标准化输出格式不是字符

是由一个%加上某一个东西组成

% 是占位符

%p

后面加地址,打印出的就是地址

后面加变量名,打印出的是变量在内存中怎么存的(有可能是补码也有可能是源码,看寄存器)

*/

#include <stdio.h>

 

int main(void)

{

char c = -128;

 

printf("c = %p\n", c);

/*

c = -13

内存中存的数据是补码

1 0 0 0 1 1 0 1

1 1 1 1 0 0 1 1

 

 

1 1 1 1 0 0 1 1

*/

 

return 0;

}

变量

1.变量是用来存储数据(在内存中进行存储)

2.变量的定义

数据类型 变量名;

int a;

1> 数据类型分类

整型 有/无符号 short int long

实型 有符号 float double

字符型 有/无符号 char

2> 测试自己所用机器的类型所占字节数

sizeof()是一个运算符,单位是字节

sizeof(变量名/数据类型)

1字节 = 8

3> 变量名的要求

1) 变量名是区分大小写的

2) 变量名不能与C语言的关键字重复

3) 变量名可以由数字 字母 下划线组成,但是不能以数字开头

4) 变量名尽量见名生义

4> 在使用变量时需要注意的点

1) 先定义再使用

2) 便于编译时检查错误,不同类型的变量有不同的运算要求

3) 定义变量之后,类型确定了,将来所占用的存储空间就确定了

3.变量的赋值

变量名 = 数值;

a = 1000;

在复制时,赋值运算符需要把右边的项经过计算复制给左边的项(左值)

4.变量的初始化

数据类型 变量名 = 数值;

int a = 1000;

char

char存储形式,1字节8

有符号类型的char可以存储的数的范围是(-128~127)

char c = 13;//0000 1101

XYYY YYYY

7位 为最高位,是符号位

6~0位 为数据位

char c = 127;//1111 1111

char c = -128;//1000 0000

*)float

float存储形式,4字节32

 

31位 符号位 0代表正数,1代表负数

30~23位 阶位 转化成规格化的二进制之后与127做和

22~0位 尾数

 

:

3.2

正的所以最高位符号位为 0

3 转化成二进制为11

0.2 小数部分乘2取整,然后从前往后读

0.2 * 2 = 0.4 0

0.4 * 2 = 0.8 0

0.8 * 2 = 1.6 1

0.6 * 2 = 1.2 1

0.2 * 2 = 0.4 0

11.001100110011...

然后将11.001100110011...的小数点向左移至小数点前只有一个1,即左移1.

阶码就是1+127 = 128  :1000 0000

尾数:因为小数点前必为1,所以记录小数点后面的数即可 100110011001...

 

       0100 0000 0100 1100 1100 1100 1100 1100

引入scanf概念

int a = 0;

int b = 0;

 

scanf("%d-%d", &a, &b);//接收键盘上录入的数值

/*

1. scanf中只写标准化输出格式就可以了,不要写其他的

2. scanf中后面的变量必须要加 &(取地址)

3. scanf中的双引号内不要写 '\n'

*/

printf("a = %d\nb = %d\n", a, b);//打印到屏幕上

 

return 0;

运算符

1.算术运算符:* - + / %

2.关系运算符: > < == != >= <=

3.逻辑运算符:! && ||

4.位运算符:<< >> ~ | ^ &(异或相同为0,不同为1)

5.赋值运算符:= += -= *= /= %= >>= <<= &= |= ^=

6.条件运算符:?:    max = (a > b) ? a : b

8.指针运算符:*&

* 取值

& 取地址

是可逆的

9.求字节数运算符:sizeof()

sizeof() 放变量/数据类型

10.强制类型转换运算符:(类型)

11.分量运算符:. -> (结构体)

结构体

共用体

12.下标运算符:[ ]

int arr[5]; // 5个成员

arr[0] ~ arr[4]// 1.数组名 + 下标

13.自增自减运算符:++ --

// 6 / 2 * (2 + 1)   不同的编译器结果可能不同19 所以最好多用括号

14.其他:如函数调用运算符:()

注意:条件运算符是C语言中惟一一个三目(三元)运算符

 

原码、补码

凡是位运算都需要把操作数变成补码形式 左移右移时,正数补零,负数补一

操作数为正数,那么补码是它的本身(原码 = 补码)

操作数为负数,无论原码转补码 还是 补码转原码,都是符号位不变,其他位按位取反再加1

/*

  char a = 13;    ~a;

  二进制(原码)

  0   0   0   0   1   1   0   1

  因为是个正数,所以补码 = 原码

  补码(取反操作之前)

  0   0   0   0   1   1   0   1

  ~

  1   1   1   1   0   0   1   0

  补码(取反操作之后)

  1   1   1   1   0   0   1   0

  反码

  1   1   1   1   0   0   0   1

  原码

  1   0   0   0   1   1   1   0

 

  char a = -13;   ~a;

  原码

  1   0   0   0   1   1   0   1

  反码

  1   1   1   1   0   0   1   0

  补码(取反之前)

  1   1   1   1   0   0   1   1

  ~a

  0   0   0   0   1   1   0   0

  补码(取反之后)

  0   0   0   0   1   1   0   0

  原码

  0   0   0   0   1   1   0   0

*/

 

#include <stdio.h>

 

int main(void)

{

char c = 3;//00000011

 

printf("c = %d\n", c);

 

c = c << 7;//10000000

 

printf("c = %d\n", c);

 

return 0;

}

 

#include <stdio.h>

 

int main(void)

{

char c = -13;

 

printf("c = %d\n", c);

 

c = c >> 2;

 

printf("c = %d\n", c);

 

return 0;

}

/*

char c = -13;

c = c >> 2;

c = -4;

c = -13

1 0 0 0 1 1 0 1

1 1 1 1 0 0 1 1

c = c >> 2

1 1 1 1 1 1 0 0

1 0 0 0 0 1 0 0

c = -4

*/

 

#include <stdio.h>

 

int main(void)

{

unsigned char c = 13;//00001101

 

printf("c = %d\n", c);

 

c = ~c; //11110010

 

printf("c = %d\n", c);

 

return 0;

}

/*

c = 13

0 0 0 0 1 1 0 1

0 0 0 0 1 1 0 1

~c

1 1 1 1 0 0 1 0

1 0 0 0 1 1 1 0

c = -14

*/

位运算符:

<< >> ~ | ^ &(异或相同为0,不同为1)

<< 按位左移

相当于乘法

char c = 3; c << 4 等价于 c * 24次方

>> 按位右移

相当于除法

char c = 13; c >> 2 等价于 c 除以 22次方(取商)

00001101 00000011

& 按位与 清零

1才是1,0就是0

| 按位或 置1

0才是0,1就是1

~ 按位取反

0变成1,1变成0

^ 按位异或

相同为0,不同为1

 

#include <stdio.h>

程序实例:

int main(void)

{

char a = 13;//00001101

char b = 10;//00001010

char c = 0;

 

printf("c = %d\n", c);

 

c = a ^ b; //c = 1101 ^ 1010 = 0111

 

printf("c = %d\n", c);

 

return 0;

}

 

条件运算符:

? :

唯一的一个三目运算符

part.1 ? part.2 : part.3;

判断 part.1 的内容 如果 part.1 为真, 会执行 part.2 的内容

如果 part.1 为假, 会执行 part.3 的内容

 

#include <stdio.h>

 

int main(void)

{

int a, b;

 

scanf("%d-%d", &a, &b);

 

a > b ? printf("a大于b\n") : printf("a 小于等于 b\n");

 

return 0;

}

分支语句

if

int main()

{

语句1;

if(条件) // 真假

{

语句2;

}

语句3;

}

语句1-->判断if条件()-->语句2-->语句3-->结束

|

-->语句3-->结束

 

if...else

 

int main(void)

{

语句1

if(条件)

{

语句2

}

else

{

语句3

}

语句4

}

语句1-->条件判断()-->语句2-->语句4

|

-->语句3-->语句4

if...else if...else if...

int main(void)

{

语句1

if(条件1)

{

语句2

}

else if(条件2)

{

语句3

}

else

{

语句4

}

语句5

}

语句1-->判断条件1()-->语句2-->语句5

 |

 -->判断条件2()-->语句3-->语句5

|

-->语句4-->语句5

 

//比较两个值是否相等一定要用 ==

//if 里的大括号一定不要丢

switch

语句1

switch(变量)

{

case 条件1 ://条件是整常量 可以是 数字 或者 字符

语句2

break;

case 条件2 :

语句3

break;

default:

语句4

break;

}

语句5

语句1->(变量匹配)条件1()->语句2->break->语句5

|

->条件2()->语句3->break->语句5

|

->语句4->break->语句5

注意:

switch里加break,

匹配完条件->执行完语句->直接跳出switch

 

如果不加break

匹配条件->执行语句->顺序往下执行

 

如果case里加return

执行到return语句直接退出程序

***************

break(可以在switch和循环中用)

跳出switch

break return区别

break 是跳出 switch

return是跳出这个函数

(main函数也是函数,如果main()函数里

 执行到return,代表程序的结束)

循环语句

for(循环的初始化; 循环控制部分; 循环的修改部分)

{

循环主体;

}

for

循环初始部分……为循环变量附初值

循环控制部分……按照循环条件控制循环正常进行

循环的修改部分……循环执行中,循环变量按照一定规律变化

循环体(工作部分)……要多次循环的代码部分

#include <stdio.h>

 

int main(void)

{

int i = 0;//循环变量

int sum = 0;

for(i = 0; i <= 100; i++)

//i = 0循环的初始部分为循环变量赋初值

//i <= 100循环的控制部分,控制循环的进行

//i++循环的修改部分,修改循环变量

//sum += i循环体,循环的主体

{

sum += i;

}

printf("sum = %d\n", sum);

return 0;

}

 

其他形态

(1)

for(a = 0, b = 0; a != 3 && b != 3; a++, b++)

{

 

}

(2)

for(; ;)

{

需要循环的部分;

}

//死循环

(3)

for(a = 0; a < 10; a++)

{

for(b = 0; b < 10; b++)

{

 

}

}

随机函数

rand()//产生随机数

srand()//随机种子

 

int a = 0;

int b = 0;

 

srand(time(NULL));

//rand()     产生随机数

//srand()     随机种子

//time(NULL) 时间

//NULL       

for(a = 0; a < 10; a++) //随机取出10(50~100以内的数)

{

b = rand() % 50 + 50;

//rand() % 50      [0~49]范围

//rand() % 50 + 50   [50~99]范围

printf("b = %d\n", b);

}break

 

用来产生随机数的函数是 rand();

如果产生100以内的随机数 rand() % 100; [0 ~ 99]

如果产生3位的随机数 rand() % 900; [0 ~ 899]

rand() % 900 + 100; [100 ~ 999]

如果产生-50 ~ 50随机数 rand() % 100 - 50;

 

随机种子

随机种子的函数 srand();

当在调用srand函数时需要把可变的因子放到srand参数中

 

可变的因子

获取时间的秒数 time();

当调用时参数可以传NULL

time(NULL);

 

获取进程ID getpid();

当调用时,getpid的返回值当做可变因子即可

 

注意 :

1.随机种子函数只需要调用一次就够了

把随机种子的函数调用放到变量定义之后即可

跳转语句

goto

使用goto做循环

注意 :

1.goto使用灵活,可以轻易改变代码结构

2.goto 需要加标志(标志的命名要求与变量命名要求一致)

3.同名的标志不能重复出现

4.goto不能跨函数使用

5.标志名可以和变量名重复

 

#include <stdio.h>

 

int main(void)

{

int flag = 100;

 

printf("world\n");

 

goto flag;

 

flag:

printf("hello\n");

 

return 0;

}

指针

1.指针是用来做什么的?

1>变量是用来存储数据的(数据会存储在内存当中<二进制补码>)

2>指针是用来存储地址的(地址会存储在内存当中<二进制>,以十六进制的形式展现给程序员)

2.指针的定义

type * name ;

数据类型 变量名 ;

int a ;

int * p ;

定义了int *类型的指针p; p是指针名 int *是类型

1>由数字字母下划线组成,不能一数字开头

2>区分大小写

3>不能与C语言关键字重复

3.通过sizeof来测试

指针无论何种类型在当前机器中都占了8个字节

因为指针存的是地址,在64位系统中一个地址占64位,所以是8个字节

4.指针的赋值

变量名 = 数值 ;

a = 100 ;

p = &a ;

5.指针的初始化

数据类型 变量名 = 数值 ;

int a = 100 ;

int * p = &a ;

int * q = NULL ; NULLASCII0x0,相当于给 指针 q赋值0*p并不为0,p指针没有实际的指向,*p是不存在的;

6.指针运算符

& 取地址的运算符

* 取值的运算符 *后面必须是地址(或者可以表示为地址的东西)

7.指针的运算

指针支持加减法运算

指针

p + 1

p += 1

p++

偏移了几个字节和数据类型有关int * 偏移了4个字节;char *偏移了1个字节

不同数据类型的指针在做加减法运算时,所偏移的字节数不同,和指针本身要保存的变量的数据类型有关。

8.特性

1>无论何种类型的指针,所占字节数相同(和硬件/造作系统位数有关)

2>指针可以做加减法运算。

3>指针是用来操作变量的。

9.特殊指针

1>空类型的指针(无准确类型的指针/万能指针)

void *p; 强制类型转换

2>空指针(编译器不检查空指针)

指向空的指针

int *p = NULL;

3>野指针(编译器也不检查野指针//空指针会提示段错误,野指针段错误都不会报)

没有对象的指针就是野指针

防止使用野指针

释放完空间之后给指针指向空,让他变成空指针。

 

程序实例

#include <stdio.h>

 

int main(void)

{

int a;

int *p; //p是指针

 

a = 100;

p = &a;

 

printf("a = %d\n", a);

printf("&a = %p\n", &a); ///打印地址

printf("p = %p\n", p); ///打印地址里存的值

printf("*p = %d\n", *p);

printf("===============================\n");

 

scanf("%d", p);

 

printf("a = %d\n", a);

printf("&a = %p\n", &a);

printf("p = %p\n", p);

printf("*p = %d\n", *p);

printf("===============================\n");

 

return 0;

}

 

野指针实例

#include <stdio.h>

#include <stdlib.h>

 

int main(void)

{

/*

int *p = NULL;

 

*p = 100;

 

printf("*p = %d\n", *p);

*/

int *p = NULL;

 

p = malloc(sizeof(int)); //在堆内存空间中开辟了4个字节的空间,把首地址返回给指针p

 

*p = 100;

 

printf("p = %p\n", p);

printf("*p = %d\n", *p);

 

free(p);

p = NULL; //防止使用野指针

 

printf("p = %p\n", p);

 

*p = 200; /使用野指针

 

printf("*p = %d\n", *p); //会打印出正确结果

 

printf("p = %p\n", p); //会打印出正确结果

 

return 0;

}

二级指针

二级指针用来操作一级指针

1.二级指针是用来存储一级指针的地址

2.二级指针的定义

数据类型 变量名 ;

int a ;

int * p ;

int ** pp ;

a p pp

 

100 0x1000 0x2000

a &a &a

*p p pp

**pp **pp

 

0x1000 0x2000 0x3000

&a &p &pp

p pp

*pp

 

一级指针是用来操作变量的

二级指针是用来操作地址的

函数指针

把代码中重复使用的功能封装成函数,将来直接调用

 

指针是用来操作变量的

当函数调用时,传变量名做不到修改变量,可以传遍量的地址,用指针接收

 

调用函数时传参方式

值传递 如果只是使用参数的值,用值传递

地址传递 如果想要改变参数的值,用地址传递

 

return 返回值

返回值

计算的结果

 

函数执行的状态

0 完美运行

正数 代表函数执行异常

负数 代表函数执行错误

数组(arrary)

1.什么是数组

相同类型变量的集合

数组是由一个一个成员组成的,每个成员的类型都是相同的

2.数组的定义

数据类型 数组名 [成员个数] ;

int arr [5] ;

数组中一共有5个成员,分别是:arr[0],arr[1]...arr[4]

每个成员都是int类型

注意:数组在定义时 [] 中写的是成员个数,

当使用数组时可以使用数组名 + 下标的方式来访问,下标是从0开始的

3.数组的使用

从某种意义上来讲, * 取值运算符 [] 下标运算符 是相等的

int arr[6] = {11,22,33,44,55,66};

想要取出值为44的成员

1>数组名 + 下标 arr[3];

2>数组名 + 偏移量 *(arr + 3);

3>指针 + 偏移量 *(p + 3);

4>指针 + 下标 p[3];

5>下标 + 数组名 3[arr]; //老版本常用

4.数组的特性

1>sizeof(数组名) = 整个数组的大小

sizeof(数组名) / sizeof(一个成员大小) = 数组成员个数

int arr[4];

sizeof(arr[0]) = 4;

sizeof(arr) = 16;

sizeof(arr) / sizeof(arr[0]) = 4; //数组成员个数

2>数组中每个成员占用相同大小的空间,并且地址是连续的

可以通过某个成员的地址进行地址偏移,找到其他的成员

3>一维数组的数组名相当于是数组首成员的地址

4>数组名不是指针

指针可以自增,但数组名不行 arr++ error p++

指针是可以自增运算或者保存变量的地址的,

数组名做不了自增运算也保存不了变量的地址

5.数组的初始化

数据类型 数组名 [成员个数] = {数值,数值...} ;

int arr [5] = {11,12,13,14,15};

int arr [5] = {0}; //数组中每个成员都是0

int arr [5] = {11}; //只有第一个成员是11,其他成员都是0

int arr [5] = {,22,44,66,}; //error,逗号处报错

int i = 5;

int arr [i] = {11,22,33,44,55}; //不同平台不同标准会有不一样的结果,有的会报错。尽量不要这么使用

int arr [] = {11,22,33,44,55}; //会把后面的数值个数当作成员个数

 

 

&arr[0] 描述的是数组首成员的地址

arr 描述的是数组首成员的地址

&arr 描述的是整个数组的地址

注意:

1.如果在封装函数时不想要通过函数的形参修改实参的值

可以在声明函数时在形参的类型前面加const关键字修饰

2.在函数声明时或者函数实现的形参中出现数组的写法都是指针

形参是用来接收实参的值,实参是一个地址,形参只能用指针来接收

3.数组不要越界使用,编译器是不检查数组越界的(定义几个成员,就用几个成员,不要多用)

程序实例

#include <stdio.h>

 

void print(const int *, int);

void change(int arr[5], int);

 

void print(const int *p, int num)

{

int i;

 

for(i = 0; i < num; i++)

{

printf("p[%d] = %d\n", i, p[i]);

}

}

 

void change(int arr[5], int num)

{

int i;

 

arr++;

 

arr--;

 

printf("sizeof(arr) = %ld\n", sizeof(arr));

 

for(i = 0; i < num; i++)

{

scanf("%d", arr + i);

}

}

 

int main(void)

{

int arr[5] = {0};

 

print(arr, sizeof(arr) / sizeof(*arr)); //通过print函数打印数组中每个成员的值

 

change(arr, sizeof(arr) / sizeof(*arr)); //通过change函数录入数组中每个成员的值

 

print(arr, sizeof(arr) / sizeof(*arr));

 

return 0;

}

字符数组和字符指针

1.含义

字符数组:是用来存储多个数据(字符)

字符指针:是用来存储地址的

2.定义

char arr[10];

char *p;

3.初始化

char arr[5] = {‘h’, 'e', 'l', 'l', 'o'};

//这让的数组打印不能使用%s打印,因为%s只有在碰到‘\0’时才会结束

char arr[5] = {11, 22, 33, 44, 55};

char arr[5] = {0}; //所有成员都是0

char arr[5] = {'0'};    //只有第一个成员的值是48,其他成员都是0

char arr[5] = {'\0'}; //所有成员都是0

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

char arr[10] = "hello";  

char *p = NULL;     //指针指向空

char *p = "guojing";     //指针p保存的是“guojing”这个 字符串常量<read only> 的首地址

char arr[10] = "yangguo"; //数组中每个成员保存字符串中的每个字符

'y'保存到arr[0]

'a'保存到arr[1]

...

'\0'保存到arr[7]

'\0'保存到arr[8]

'0'保存到arr[9]

4.操作

char arr[10] = {0};

char *p = NULL;

 

arr = "guojing"; //字符数组不能操作,编译器会报错

p = “guojing”; //字符指针可以操作,保存了“guojing”字符串常量的首地址

 

arr[2] = 'A'; //字符数组可以操作,通过数组名+下标改成员的值。将数组2号空间保存的‘a’修改为‘A’

p[2] = 'A'; //字符指针不能操作,因为p保存的是 字符串常量 的首地址

当指针和数组产生关系,指针表示的是数组的首地址,就可以完成上面的操作

5.注意

1>当通过scanf("%c")的方式给字符数组录入字符时需要注意回车,

因为回车会被当作换行符,保存到数组中

2>当通过scanf("%s")的方式个字符数组录入字符时 空格 相当于 尾0

 

指针保存的字符串地址地址在常量区,const修饰的变量不在常量区

变量、数组...在栈区

6.程序实例

#include <stdio.h>

#include <string.h>

 

int main(void)

{

char arr[10] = "yangguo";

 

printf("arr = %s\n", arr);

 

arr[2] = 'A';

 

printf("arr = %s\n", arr);

 

/*

char *p = "yangguo"; //字符指针保存了字符串常量的首地址

 

printf("p = %s\n", p); //通过字符指针访问字符串常量

 

p[2] = 'A'; //通过字符指针想要修改字符串常量中的数据(段错误)

 

printf("p = %s\n", p);

*/

return 0;

}

string一族函数

strcpy strcat strcmp strlen

函数的声明

函数的返回值的类型 函数名(参数列表); //参数是形参,形参是用来接收实参的值

函数的实现

函数的返回值的类型 函数名(参数列表)

{

函数的主体

}

函数的调用

函数名(参数);//参数是实参,实参把值传递给形参

string一族函数实现

#include <stdio.h>

 

char *mystrcpy(char *dest, const char *src); //可以利用字符串的结束标志'\0'

char *mystrcat(char *dest, const char *src);

int mystrcmp(const char *s1, const char *s2);

size_t mystrlen(const char *s);

 

char *mystrcpy(char *dest, const char *src)

{

int i = 0;

 

for(i = 0; src[i] != '\0'; i++)

dest[i] = src[i];

dest[i] = src[i];

 

return dest;

}

 

char *mystrcat(char *dest, const char *src)

{

int i = 0, j = 0;

 

for(i = 0; dest[i] != '\0'; i++); //空循环,只是为了找dest'\0'的位置

 

for(j = 0; src[j] != '\0'; i++, j++)

dest[i] = src[j];

dest[i] = src[j];

 

return dest;

}

 

int mystrcmp(const char *s1, const char *s2)

{

int i = 0;

 

for(i = 0; s1[i] == s2[i] && s1[i] != '\0'; i++);

//for的退出条件两个1.两个字符串找到了不相同的字符 2.两个字符串碰到了'\0'

return s1[i] - s2[i];

}

 

size_t mystrlen(const char *s)

{

int i = 0;

 

for(i = 0; s[i] != '\0'; i++);

 

return i;

}

 

int main(void)

{

char dest[20] = "hello";

char *src = "world";

 

//printf("dest = %s\n", dest);

 

//printf("mystrcpy = %s\n", mystrcpy(dest, src)); //通过mystrcpy完成字符串拷贝

printf("mystrcat = %s\n", mystrcat(dest, src));

//printf("mystrcmp = %d\n", mystrcmp(dest, src));

printf("mystrlen = %ld\n", mystrlen(dest));

 

//printf("dest = %s\n", dest);

 

return 0;

}

内核方式实现string一族函数

#include <stdio.h>

#include <string.h>

 

char *mystrcpy(char *dest, const char *src); //可以利用字符串的结束标志'\0'

char *mystrcat(char *dest, const char *src);

int mystrcmp(const char *s1, const char *s2);

size_t mystrlen(const char *s);

char *mystrchr(const char *s, int c);

 

char *mystrcpy(char *dest, const char *src)

{

char *save = dest;

 

while(*dest++ = *src++);

 

return save;

}

 

char *mystrcat(char *dest, const char *src)

{

int i = 0, j = 0;

 

for(i = 0; dest[i] != '\0'; i++); //空循环,只是为了找dest'\0'的位置

 

for(j = 0; src[j] != '\0'; i++, j++)

dest[i] = src[j];

dest[i] = src[j];

 

return dest;

}

 

int mystrcmp(const char *s1, const char *s2)

{

int i = 0;

 

for(i = 0; s1[i] == s2[i] && s1[i] != '\0'; i++);

//for的退出条件两个1.两个字符串找到了不相同的字符 2.两个字符串碰到了'\0'

return s1[i] - s2[i];

}

 

size_t mystrlen(const char *s)

{

const char *sr = s;

 

while(*++sr);

 

return sr - s;

}

 

char *mystrchr(const char *s, int c)

{

int i = 0;

 

for(i = 0; s[i] != c && s[i] != '\0'; i++);

 

return s[i] == c ? (char *)(s + i) : NULL;

}

 

char *mystrrchr(const char *s, int c)

/*

定义一个指针sp保存s的值,sp找到字符串'\0'

'\0'的前一个字符查找c

直到找到了或者sp == s

*/

{

const char *p = s + strlen(s);//p指针保存的是最后一个字符的地址

 

do

{

if(*p == c)

return (char *)p;

}while(--p >= s);

return NULL;

}

 

int main(void)

{

char dest[10] = "hello";

char *src = "zack:x:1000:1000:zack,,,:/home/zack:/bin/bash";

char *ret = NULL;

 

//printf("dest = %s\n", dest);

 

//printf("mystrcpy = %s\n", mystrcpy(dest, src)); //通过mystrcpy完成字符串拷贝

//printf("strcpy = %s\n", strcpy(dest, src)); //通过mystrcpy完成字符串拷贝

//printf("mystrcat = %s\n", mystrcat(dest, src));

//printf("mystrcmp = %d\n", mystrcmp(dest, src));

//printf("mystrlen = %ld\n", mystrlen(dest));

 

//printf("dest = %s\n", dest);

 

//printf("strchr = %s\n", strchr(src, ';'));

 

ret = mystrchr(src, ':');

 

if(ret == NULL)

printf("Not Find It!\n");

else

printf("ret = %s\n", ret);

 

return 0;

}

变量的使用和存储方式程序实例

变量

局部变量可以和全局变量重名

如果函数中没有局部变量,使用的是全局变量

如果函数中有局部变量,使用的是局部变量

#include <stdio.h>

 

int a = 100;

/*

存储区域 全局区

作用域 整个程序中有效

生命周期 程序结束会被释放

*/

 

void func_1(void)

{

printf("func_1.a = %d\n", a);

}

 

void func(void)

{

int a = 200;

/*

存储区域 栈区

作用域 func函数内有效

生命周期 函数结束会被释放

*/

 

printf("func.a = %d\n", a);

}

 

int main(void)

{

int a = 300;

/*

局部变量

存储区域    栈区

作用域      main函数内有效

生命周期    函数结束会被释放

*/

 

printf("main.a = %d\n", a);

 

func();

 

func_1();

 

{

int a = 400;

/*

块级变量

存储区域 栈区

作用域 块内有效

生命周期 当跳出块内被释放

*/

printf("main{}.a = %d\n", a);

}

 

printf("main.a = %d\n", a);

 

return 0;

}

实现atoi

#include <stdio.h>

#include <stdlib.h>

 

int myatoi(const char *nptr);

 

int myatoi(const char *nptr)

{

int i = 0;

int ret = 0;

int flag = 1;

 

for(i = 0; nptr[i] == ' '; i++);

 

if(nptr[i] == '+' || nptr[i] == '-')

flag = nptr[i++] == '+' ? 1 : -1;

 

for(; nptr[i] != '\0'; i++)

{

switch(nptr[i])

{

case '0' : case '1' : case '2' :

case '3' : case '4' : case '5' :

case '6' : case '7' : case '8' :

case '9' :

ret *= 10;

ret += nptr[i] - '0';

break;

default : return ret * flag;

}

}

return ret * flag;

}

 

int main(void)

{

int num = 0;

 

num = myatoi("456abc123");

 

printf("num = %d\n", num);

 

return 0;

}

///sprintf 相当于 itoa

va_arg例子

可变参数列表的函数

将来实现的功能函数客户在使用时,不确定传参个数,就可以

声明可变参数列表的函数

va_list ap;

va_list 类型的变量,存储了有关额外参数和检索状态的信息

 

#include <stdio.h>

#include <stdarg.h>

 

int add(int num_args, ...);//num_args是需要几个整数加法运算

 

int add(int num_args, ...)

{

int val = 0;

va_list ap;//

int i = 0;

 

va_start(ap, num_args);

 

for(i = 0; i < num_args; i++)

val += va_arg(ap, int);

 

va_end(ap);

 

return val;

}

 

int main(void)

{

int sum = 0;

 

sum = add(10, 1, 2, 3, 4, 5, 6 ,7 ,8 ,9 ,10);

//前面的10是后面参数个数, 1 2 3 ...想要相加的数字

 

printf("sum = %d\n", sum);

 

return 0;

}

实现printf

需要使用

putchar

putc

puts

函数替换

printf

#include <stdio.h>

#include <stdarg.h>

 

int myprintf(const char *format, ...);

 

int myprintf(const char *format, ...)

{

va_list ap;

int d;

char c, *s;

float f;

 

va_start(ap, format);

 

while(*format)//format = "hello world!";

{

if(*format == '%')

{

format++;

switch(*format)

{

case 'd' : d = va_arg(ap, int);

printf("%d", d);

break;

case 's' : s = va_arg(ap, char *);

printf("%s", s);

break;

case 'c' : c = (char) va_arg(ap, int);

printf("%c", c);

break;

case 'f' : f = (float)va_arg(ap, double);

printf("%f", f);

break;

}

}

else

putchar(*format);

format++;

}

}

 

int main(void)

{

myprintf("hello world!");

//myprintf("%d-%s-%c-%f\n", 100, "hello world", 'A', 3.14);

 

return 0;

}

static

staticC语言中的关键字

static用来修饰静态的

static关键字的功能

1.仅限于当前文件使用(隐藏)

所有未加static关键字修饰的全局变量,对于其他文件来说都是可见的

如果在全局变量名前面加static关键字修饰的话,可以在不同的文件中定义相同的全局变量名,不受影响

2.保持局部变量的生命周期

如果局部变量不加static关键字修饰,变量存储在栈区中,函数结束就被释放

如果局部变量加static关键字修饰,变量存储在静态区中,当程序结束时会被释放

局部变量前加static关键字修饰,只在第一次调用时初始化,也是唯一的一次初始化

在静态区会存储 : 全局变量和static关键字修饰的变量

3.static关键字修饰的变量或者数组默认初始化的值为0

程序实例

#include <stdio.h>

 

int main(void)

{

static int arr[10];

int i = 0;

 

for(i = 0; i < 10; i++)

{

printf("arr[%d] = %d\n", i, arr[i]);

}

 

return 0;

}

/

运行结果:

arr[0] = 0;

arr[1] = 0;

arr[2] = 0;

...

arr[9] = 0;

 

局部变量用static修饰只初始化一次

static关键字修饰的变量或者数组默认初始化的值为0

/

 

#include <stdio.h>

 

void func(void)

{

static int i = 10; //静态的局部变量

int a = 10; //局部变量(当函数执行结束时被释放)

 

i++;

a++;

 

printf("i = %d a = %d\n", i, a);

}

 

int main(void)

{

func();

func();

func();

func();

 

return 0;

}

/

运行结果:

i = 11 a = 11

i = 12 a = 11

i = 13 a = 11

i = 14 a = 11

/

工程文件实例

func.h

#ifndef __FUNC_H

#define __FUNC_H

 

int add(int , int );

 

#endif

/*

3行预处理是为了防止头文件被重复包含

*/

func.c

#include "func.h"

 

int add(int a, int b)

{

return a + b;

}

main.c

/*

第三阶段

多文件编程

main.c main函数和功能函数的调用

func.c 功能函数的实现

func.h 功能函数的声明

makefile 编译规则

*/

#include <stdio.h>

#include "func.h"

 

int main(void)

{

int a = 13, b = 7;

int sum = 0;

 

sum = add(a, b);//功能函数的调用

 

printf("sum = %d\n", sum);

 

return 0;

}

makefile

main : main.o func.o

gcc -o $@ $^

# # makefile中代表注释

#   $   makefile中代表引用变量

#   $@  代表上一句话:以左的内容

#   $^  代表上一句话:以右的内容

clean :

rm -rf *.o main

 

#   makefile中注释使用的是 #

#   在第一行需要写最终生成的可执行文件的名字    最终生成的可执行文件 : 依赖    的文件

#   在第二行需要写使用的工具(必须要写tab)

#   最后的clean :   是清除工具,当执行make clean会把

#   所有的.o文件和可执行文件都删掉(还原到最初的文件)

#   当在终端中想要执行编译,直接make就可以

makefile                                 

main : main.o func.o

gcc main.o func.o -o main

clean :

rm -rf *.o main

程序实例

func.c

#include "func.h"

 

int a;

 

void func(void)

{

a++;

}

func.h

#ifndef __FUNC_H

#define __FUNC_H

 

void func(void);

 

#endif

 

main.c

#include <stdio.h>

#include "func.h"

 

int a = 100;

 

int main(void)

{

printf("a = %d\n", a);

 

func();

func();

func();

func();

 

printf("a = %d\n", a);

 

return 0;

}

//104

 

func.h

#ifndef __FUNC_H

#define __FUNC_H

 

void func(void);

 

#endif

 

func.c

#include "func.h"

 

int a;

 

void func(void)

{

a++;

}

 

main.c

/*

如果全局变量加上static关键字修饰,当前的全局变量和其他文件的全局变量不相同

*/

#include <stdio.h>

#include "func.h"

 

static int a = 100;

 

int main(void)

{

printf("a = %d\n", a);

 

func();

func();

func();

func();

 

printf("a = %d\n", a);

 

return 0;

}

100     100

 

func.h

#ifndef __FUNC_H

#define __FUNC_H

 

void func(void);

 

#endif

 

func.c

#include <stdio.h>

#include "func.h"

 

char c = 'A';

 

void func(void)

{

printf("hello world!\n");

}

 

main.c

/*

全局变量

优点

当项目工程是多文件的时候,定义全局变量减省函数传参的麻烦

缺点

当其他.c文件定义的全局变量和它同名,会有影响

*/

#include <stdio.h>

#include "func.h"

 

char c;

 

int main(void)

{

func();

 

printf("c = %c\n", c);

 

return 0;

}

//

运行结果:

hello world

c = A

//

函数

函数名 : 就是函数的入口地址(函数名就是地址,可以打印函数名,打印出地址)

 

1.函数与函数的关系 调用与被调用的关系

main函数调用功能函数

功能函数可以调用功能函数

2.功能函数调用main函数

会造成栈区破裂(段错误,核心以转储)

3.功能函数自己调用自己(函数递归)

注意:要最先写退出条件

函数递归

注意 : 递归的退出条件

看别人写的递归时,只调用两,三次即可

 

#include <stdio.h>

 

int func(int num)

{

if(num == 1)

return 1;

return num + func(num - 1);

}

 

int main(void)

{

int sum = 0;

 

sum = func(3);

 

printf("sum = %d\n", sum);

 

return 0;

}

程序实例

/*

12345

倒叙打印出来

54321

*/

#include <stdio.h>

 

void func(int num)

{

if(num == 0)

return ;

printf("%d", num % 10);

func(num / 10);

}

 

int main(void)

{

func(12345);

 

printf("\n");

 

return 0;

}

itoa

/*

实现itoa

sprintf可以代替itoa的功能

*/

#include <stdio.h>

 

void itoa(int data, char str[]);

 

void itoa(int data, char str[])//123

{

int i = 0;

if(data < 10)

{

str[0] = data + '0';

str[1] = '\0';

return ;

}

itoa(data / 10, str);

for(i = 0; str[i] != '\0'; i++);

str[i] = data % 10 + '0';

str[i + 1] = '\0';

}

 

int main(void)

{

char buf[10] = {0};

int num = 0;

 

scanf("%d", &num);

 

itoa(num, buf);

 

printf("buf = %s\n", buf);

 

return 0;

}

存储区域介绍

代码区

管不了

栈区

系统帮助程序员管理

不需要程序员自己开辟空间,释放空间

自动释放

堆区

malloc->free

zalloc->free

程序员需要自己管理的内存空间

自己开辟,自己释放

申请和释放都是程序员管理

(忘记释放会使内存耗尽)

全局区

全局变量

常数区

程序结束才会释放

(char *p1 = NULL;)

(p1 = "abc";)

内存    真实存储 4G

只用内存真实空间 4K

虚拟内存 4G

1G kernel(内核)

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

3G environment/cmd环境变量(系统设定的全局变量)和命令->argc argv

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

栈向下 从高地址到低地址

 

 

堆向上 从低地址到高地址

.bss 静态存储区(全局变量,静态变量) !未初始化

.date 初始化的 不为0                 !已初始化

RO date 只读数据段

text 代码段

 

.bss存在可执行文件中

.date不存在可执行文件中

 

makefile

一个工程中的源文件不计其数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为makefile就像一个Shell脚本一样,其中也可以执行操作系统的命令。

可变参的函数

stdarg.h 头文件中定义了一个变量类型 va_list 和三个宏定义,

这三个宏定义可用于在参数个数未知(即参数个数可变)时获取函数中的参数.

可变参数的函数通常在参数列表的末尾时使用省略号(...)定义的.

 

va_list是一个类型

适用于 va_start(),va_arg() va_end() 这三个宏存储信息

 

void va_start(va_list ap, last_arg)

这个宏初始化 ap 变量,它与 va_arg va_end 宏是一起使用的.

last_arg 是最后一个传递给函数的已知的固定参数,即省略号之前的参数.

例子:

int printf(const char *format, ...); 中的format

int ioctl(int fd, unsigned long request, ...); 中的request

 

type va_arg(va_list ap, type)

这个宏检索函数参数列表中类型为 type 的参数.

 

void va_end(va_list ap)

这个宏允许使用了 va_start 宏的带有可变参数的函数返回

如果在从函数返回之前没有调用 va_end,则结果为未定义.

const

constC语言中的关键字

const是用来修饰只读(read only)

const关键字修饰变量时,变量名变成只读的了,

只能通过变量名读取内存空间的数据而不能通过变量名来修改(可以通过指针来修改)

const关键字修饰的变量名

 

写法 const int a; 或者 int const a;

 

volatile是一个关键字

volatile是为了防止编译器优化

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

底层操作硬件时经常会用

volatile int led_status;//led_status是用来控制led灯的亮灭(0 灯亮 1 灯灭)

 

led_status = 1;//编译器会优化

 

led_status = 0;

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

程序实例

#include <stdio.h>

 

int main(void)

{

volatile const int a = 100;

int *p = &a;

 

printf("a = %d\n", a);

 

//a = 200; //不能修改,会报错

*p = 200;

 

printf("a = %d\n", a);

 

return 0;

}

 

常量指针

指针指向常量(指向常量的指针)

定义 :

const int *p;

const char *src;

或者

int const *p;

char const *src;

*p或者*srcconst关键字修饰,只能通过*p或者*src读取指向空间的数据

但是不能通过*p或者*src修改指向空间的值

常量指针是可以改变指针的指向

程序实例

#include <stdio.h>

 

int main(void)

{

int a = 100;

int b = 200;

const int *p = &a;

char const *src = NULL;

/*

printf("a = %d\n", a);

 

*p = 200;*pread only,所以不能通过*p修改指向空间的值

 

printf("a = %d\n", a);

*/

 

printf("*p = %d\n", *p);

 

p = &b;

 

printf("*p = %d\n", *p);

 

return 0;

}

 

指针常量

指针是常量

定义

int * const p;

char * const src;

特点

指针pconst关键字修饰,意味着p指针的指向不能改变,但是可以通过*p

改变指向空间的值

程序实例

#include <stdio.h>

 

int main(void)

{

int a = 100;

int b = 200;

int * const p = &a;

/*

printf("a = %d\n", a);

 

*p = 200;可以修改

 

printf("a = %d\n", a);

*/

 

printf("*p = %d\n", *p);

 

p = &b; //不可以

 

printf("*p = %d\n", *p);

 

return 0;

}

 

指针数组

是一个数组,数组中的每一个成员都是相同类型的指针

定义

数据类型 数组名 [成员个数] ;

int arr [5] ; //5个成员arr[0] ~ arr[4],每个成员都是int类型

char str [20] ; //20个成员str[0] ~ str[19]

int * arr [5] ; //5个成员arr[0] ~ arr[4],每个成员都是int *类型

char * argv [4] ; //4个成员argv[0] ~ argv[3],每个成员都是char *

命令行参数

int main(int argc, char *argv[]);

当在终端中执行./a.out就相当于调用main函数

main函数的参数有两个argc argv

argc 统计命令行参数的个数

argv argv中的每个成员指向命令行参数中的每个字符串

程序实例

#include <stdio.h>

 

int main(void)

{

int a, b, c, d;

int *arr[4];

int i = 0;

 

arr[0] = &a;

arr[1] = &b;

arr[2] = &c;

arr[3] = &d;

 

for(i = 0; i < 4; i++)

{

scanf("%d", arr[i]);

}

 

printf("a = %d b = %d c = %d d = %d\n", a, b, c, d);

 

return 0;

}

 

#include <stdio.h>

 

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

{

int i = 0;

 

printf("argc = %d\n", argc);

 

for(i = 0; i < argc; i++)

{

printf("argv[%d] = %s\n", i, argv[i]);

}

 

return 0;

}

数组、指针区别

#include <stdio.h>

#include <string.h>

 

int main(void)

{

char *str[3] = {

"hello world",

"hello China",

"uplooking"

  };

/*

定义的str是指针数组,3个成员,每个成员为char *类型

str[0] ~ str[2]3个成员每个成员获取一个字符串常量的首地址,意味着都指向常量区

*/

char arr[3][20] = {

"hello world",

"hello China",

"uplooking"

 };

/*

定义的arr(字符)二维数组,60个成员,每个成员为char类型

二维数组中的每一个成员都会获取字符串中的一个字符进行保存

*(arr[1] + 2) = 'L';

*/

int i = 0;

 

/*==================区别一=================*/

printf("sizeof(str) = %ld\n", sizeof(str)); //24

printf("sizeof(arr) = %ld\n", sizeof(arr)); //60

/*=========================================*/

 

/*==================区别二=================*/ //运行结果相同

for(i = 0; i < 3; i++)

{

printf("str[%d] = %s\n", i, str[i]);

}

 

for(i = 0; i < 3; i++)

{

printf("arr[%d] = %s\n", i, arr[i]);

}

/*=========================================*/

 

/*==================区别三=================*/ //运行结果相同

for(i = 0; i < 3; i++)

{

printf("strlen(str[%d]) = %ld\n", i, strlen(str[i]));

}

 

for(i = 0; i < 3; i++)

{

printf("strlen(arr[%d]) = %ld\n", i, strlen(arr[i]));

}

/*=========================================*/

 

/*==================区别四=================*/

 

//printf("str[1] = %s\n", str[1]);

 

//*(str[1] + 2) = 'L';//str[1]成员指向的是常量区

 

//printf("str[1] = %s\n", str[1]);

 

printf("arr[1] = %s\n", arr[1]);

 

*(arr[1] + 2) = 'L';//str[1]成员指向的是常量区

 

printf("arr[1] = %s\n", arr[1]);

 

/*=========================================*/

 

return 0;

}

 

#include <stdio.h>

 

int main(void)

{

char arr[10] = "uplooking";

char *p = "uplooking";

/*

arr数组和p指针都保存在栈区中

只不过指针p保存的是字符串常量的首地址

或者指针p保存的是常量区的一个地址

*/

 

 

return 0;

}

 

指针数组的定义

type *arr_name[arrnum];

int *arr[4];

char *argv[4];

数组指针

是一个指针,指向数组(二维数组)的指针

定义

type (*point_name)[arrnum];

int (*p)[4];

定义了一个数组指针p,指针p应该指向一行有4个成员,每个成员为int类型的数组

程序实例

#include <stdio.h>

 

int main(void)

{

int arr[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};

int (*p)[4];

int i, j;

 

p = arr;

 

for(i = 0; i < 3; i++)

{

for(j = 0; j < 4; j++)

{

//printf("arr[%d][%d] = %d\n", i, j, arr[i][j]);

//printf("*(*(arr + %d) + %d) = %d\n", i, j, *(*(arr + i) + j));

//printf("p[%d][%d] = %d\n", i, j, p[i][j]);

printf("*(*(p + %d) + %d) = %d\n", i, j, *(*(p + i) + j));

}

}

 

return 0;

}

 

#include <stdio.h>

 

void print(const int (*p)[4]);

void change(int (*p)[4]);

 

void print(const int (*p)[4])

{

int i, j;

 

for(i = 0; i < 3; i++)

{

for(j = 0; j < 4; j++)

{

printf("p[%d][%d] = %d\n", i, j, p[i][j]);

}

}

}

 

void change(int (*p)[4])

{

int i, j;

 

for(i = 0; i < 3; i++)

{

for(j = 0; j < 4; j++)

{

scanf("%d", &p[i][j]);

}

}

}

 

int main(void)

{

int brr[2][4] = {11,22,33,44,55,66,77,88};

int arr[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};

 

print(arr);//通过print函数遍历二维数组arr

 

change(arr);//通过change函数改变二维数组arr成员的值

 

print(arr);

 

return 0;

}

 

typedef

typedefC语言中的关键字

使用typedef来定义类型,或者给已有的数据类型取别名

使用方法

typedef 已有的数据类型 别名; //最后必须要有分号 ;

typedef int abc; //凡是用 abc 类型定义的变量都是 int 类型

typedef struct stu

{

int id;

char name[20];

int math;

}STU;

 

STU a;

 

*/

程序实例

#include <stdio.h>

 

typedef int abc;

 

int main(void)

{

abc a = 100;

 

printf("a = %d\n", a);

 

printf("sizeof(a) = %ld\n", sizeof(a));

 

printf("sizeof(abc) = %ld\n", sizeof(abc));

 

//printf("sizeof(abcd) = %ld\n", sizeof(abcd)); 会报error abcd未定义

 

return 0;

}

 

typedef int arr_type[5];

 

#include <stdio.h>

 

typedef int arr_type[5];

/*

typedef int[5] arr_type;

*/

 

int main(void)

{

arr_type a = {11,22,33,44,55};

int i = 0;

 

printf("sizeof(a) = %ld\n", sizeof(a)); //20

 

printf("sizeof(arr_type) = %ld\n", sizeof(arr_type)); //20

 

printf("a = %p\n", a); //

printf("a + 1 = %p\n", a + 1); //+4

 

for(i = 0; i < 5; i++)

{

printf("a[%d] = %d\n", i, a[i]);

}

 

return 0;

}

 

#include <stdio.h>

 

typedef int (*Arr_Point)[4]; //定义了数组指针类型

/*

typedef int(*)[4] Arr_Point;

*/

 

int main(void)

{

int arr[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};

//想要定义指针和数组产生关系

Arr_Point p = arr;//用数组指针类型定义的变量都是数组指针

int i, j;

 

for(i = 0; i < 3; i++)

{

for(j = 0; j < 4; j++)

{

printf("p[%d][%d] = %d\n", i, j, p[i][j]);

}

}

 

return 0;

}

 

#include <stdio.h>

 

int main(void)

{

int *arr[3];

int (*p)[3];

 

printf("sizeof(arr) = %ld\n", sizeof(arr));

//指针数组的大小=一个成员的大小*成员个数 24

printf("sizeof(p) = %ld\n", sizeof(p));

//数组指针的大小=8;无论何种指针在当前机器上测试出来的都是8个字节

 

return 0;

}

 

指针函数

是一个函数,函数的返回值是一个指针

char *strcpy(char *dest, const char *src);

char *strcat(char *dest, const char *src);

void *malloc(size_t size);

FILE *fopen(const char *path, const char *mode);

上述函数都是指针函数

*/

#include <stdio.h>

 

int *func(void)

{

int a = 100;//a变量是一个局部变量,当函数执行结束后被释放

 

return &a;

}

 

int main(void)

{

int *p = NULL;

 

p = func();

 

printf("p = %p\n", p); //p = (nil

printf("*p = %d\n", *p); //段错误

 

return 0;

}

功能函数内定义数组,想要返回数组

#include <stdio.h>

 

int *func(void)

{

static int arr[5] = {11,22,33,44,55}; //局部变量

 

return arr;

}

 

int main(void)

{

int *p = NULL;

int i = 0;

 

p = func();

 

for(i = 0; i < 5; i++)

{

printf("p[%d] = %d\n", i, p[i]);

}

 

return 0;

}

函数指针

int *func(int a, int b);

 

函数指针

是一个指针,指向函数的指针

定义

int (*p)(int ,int);

定义了函数指针p,指针p指向函数的返回值为int类型,参数为int,int,这样的函数

 

#include <stdio.h>

 

int add(int a, int b)

{

return a + b;

}

 

int sub(int a, int b)

{

return a - b;

}

 

int main(void)

{

int a = 13, b = 7;

int (*p)(int,int);

 

p = add;

 

printf("a + b = %d\n", p(a, b));

 

p = sub;

 

printf("a - b = %d\n", p(a, b));

 

return 0;

}

 

/*

typedef void (*sighandler_t)(int);

 

typedef void (*)(int) sighandler_t;

 

sighandler_t p;

 

*/

#include <stdio.h>

 

typedef int (*Func_Point)(int ,int);

 

int add(int a, int b)

{

return a + b;

}

 

int sub(int a, int b)

{

return a - b;

}

 

int main(void)

{

int a = 13, b = 7;

Func_Point p, q;

 

p = add;

 

printf("a + b = %d\n", p(a, b));

 

q = sub;

 

printf("a - b = %d\n", q(a, b));

 

return 0;

}

回调函数

#include <stdio.h>

#include <signal.h>

#include <unistd.h>

 

void func(int none)

{

printf("No I Don't Even Like You A Little Bit!\n");

sleep(1);

//alarm(1);

}

 

int main(void)

{

signal(SIGALRM, func);

alarm(1);

 

while(1)

{

printf("Hey! Girl You Like Me Little Bit?\n");

sleep(1);

}

 

return 0;

}

函数指针数组

是一个数组,数组中的每一个成员都是函数指针

定义

int (*arr[4])(int , int);

定义了一个函数指针数组,数组中有4个成员,arr[0] ~ arr[3],每个成员都是函数指针

指向函数返回值为int类型,参数为int int的函数

 

#include <stdio.h>

 

typedef int (*Func_Point)(int, int);

 

int add(int a, int b)

{

return a + b;

}

 

int sub(int a, int b)

{

return a - b;

}

 

int mul(int a, int b)

{

return a * b;

}

 

int div(int a, int b)

{

return a / b;

}

 

int main(void)

{

int a = 13, b = 7;

Func_Point arr[4];

int i = 0;

 

arr[0] = add;

arr[1] = sub;

arr[2] = mul;

arr[3] = div;

 

for(i = 0; i < 4; i++)

{

printf("arr[%d] = %d\n", i, arr[i](a, b));

}

 

return 0;

}

结构体

1.定义结构体

为了定义结构,必须使用 struct 关键字

struct tag //tag是结构体标签

{

member-list; //member-list是结构体成员,一般来说写的是定义(变量 数组 指针)

member-list;

member-list;

...

}variable-list; //variable-list是结构体类型的变量,定义在结构体的末尾,可以定义多个结构体类型的变量

 

注意 : 当定义完结构体之后必须要加 ;

2.定义结构体类型的变量

数据类型 变量名 ;

int a ;

struct stu s ;

3.结构体类型的变量访问成员

分量运算符 . ->

变量名.成员名

指针->成员名

4.结构体类型的变量初始化

数据类型 变量名 = {数值, 数值};

struct stu s = {10086, "yidong", 59};

5.通过sizeof结构体测试

sizeof(结构体) = 得到的是结构体的大小

是在成员大小的和的基础上做了字节对齐

6.结构体成员的地址

结构体的成员在存储空间中是连续的

程序实例

*/

#include <stdio.h>

#include <string.h>

 

struct stu

{

int id;

char name[20];

int math;

};

 

int main(void)

{

struct stu s, t;

 

s.id = 100;

//s.name = "xiaoming"; //error 因为name是字符数组的数组名

//strcpy(s.name, "xiaoming");

sprintf(s.name, "xiaoming");

s.math = 100;

 

t = s;

 

printf("t.id = %d\n", t.id);

printf("t.name = %s\n", t.name);

printf("t.math = %d\n", t.math);

 

return 0;

}

 

#include <stdio.h>

#include <string.h>

 

struct stu

{

int id;

char name[20];

int math;

};

 

int main(void)

{

struct stu t;

 

scanf("%d", &t.id);

scanf("%s", t.name);

scanf("%d", &t.math);

 

printf("t.id = %d\n", t.id);

printf("t.name = %s\n", t.name);

printf("t.math = %d\n", t.math);

 

return 0;

}

 

#include <stdio.h>

#include <string.h>

 

struct stu

{

long id;

char name[21];

int math;

};

 

int main(void)

{

printf("sizeof(struct stu) = %ld\n", sizeof(struct stu));

//40

return 0;

}

 

#include <stdio.h>

#include <string.h>

 

struct stu

{

//char a; //4

//int c; //4

//short b; //4一共12

};

 

int main(void)

{

printf("sizeof(struct stu) = %ld\n", sizeof(struct stu));

/0,因为结构体中都注释了,结构体为空

return 0;

}

 

 

struct stu

{

//char a; //2

//short b; //2

//int c; //4一共8字节

};

 

#include <stdio.h>

#include <string.h>

 

struct stu

{

int id;

char name[20];

int math;

};

 

int main(void)

{

struct stu s = {10010, "liantong", 59};

struct stu *p = &s; //sizeof(p) = 8;

 

printf("p->id = %d\n", p->id);

printf("p->name = %s\n", p->name);

printf("p->math = %d\n", p->math);

 

printf("&s = %p\n", &s); //

printf("&s.id = %p\n", &s.id); //与结构体地址相同

printf("s.name = %p\n", s.name); //+4

printf("&s.math = %p\n", &s.math); //+20

 

//printf("%d\n", (*p).id); //也可以这么用

 

scanf("%d", &p->id); /注意取地址

scanf("%s", p->name);

scanf("%d", &p->math);

 

printf("p->id = %d\n", p->id);

printf("p->name = %s\n", p->name);

printf("p->math = %d\n", p->math);

 

return 0;

}

 

#include <stdio.h>

#include <string.h>

 

struct wuxia

{

int wuli;

int zhili;

char name[20];

};

 

void print(const struct wuxia *p);

void change(struct wuxia *p); 注意传参

 

void print(const struct wuxia *p) ///

{

int i;

 

for(i = 0; i < 3; i++)

{

printf("武力值 : %d\n", p[i].wuli);

printf("智力值 : %d\n", p[i].zhili);

printf("姓  名 : %s\n", p[i].name);

}

}

 

void change(struct wuxia *p)

{

int i;

 

for(i = 0; i < 3; i++)

{

scanf("%d", &p[i].wuli);

scanf("%d", &p[i].zhili);

scanf("%s", p[i].name);

}

}

 

int main(void)

{

struct wuxia arr[3] = { //只有初始化的时候可以这样赋值

{100, 100, "huangshang"},

{0, 0, "songbingjia"},

{59, 59, "luyoujiao"}

 };

 

/*

int i = 0;

for(i = 0; i < 3; i++)

{

scanf("%d", &arr[i].wuli);

scanf("%d", &arr[i].zhili);

scanf("%s", arr[i].name);

}

*/

print(arr); //通过print函数遍历数组

 

change(arr); //通过change函数改变数组成员的值

 

print(arr);//打印

 

return 0;

}

老师笔记

定义方式

1.

struct

{

    int a; //4

    char b; //4

    double c; //8

}s1;

//上述定义了拥有3个成员的结构体,分别为inta,charbdoublec

//同时又定义了结构体变量s1

//这个结构体并没有标明其标签

2.

struct SIMPLE

{

    int a;

    char b;

    double c;

};

//上述定义了拥有3个成员的结构体,分别为inta,charbdoublec

//结构体的标签被命名为SIMPLE,没有定义结构体类型的变量

 

struct SIMPLE t1, t2[20], *t3;

//SIMPLE标签的结构体定义了结构体类型的变量t1 结构体类型的数组t2 结构体类型的指针t3

3.

typedef struct

{

    int a;

    char b;

    double c;

}Simple2;

//也可以用typedef创建新类型

Simple2 u1, u2[20], *u3;

注意 :

在上面的结构体定义中<第一种><第二种>声明被编译器当作两个完全不同的类型,即使他们的成员列表是一样的,  ///

如果令 t3=&s1 是违法的操作,编译器会报警告    /

定义结构体时,结构体的成员

1.

struct SIMPLE

{

int a;

char b;

double c;

};

struct COMPLEX

{

    char string[100];

    struct SIMPLE a;//结构体的成员是其他结构体类型的变量(可以和其他结构体类型的成员同名)

//因为struct COMPLEX结构体定义中存在struct SIMPLE结构体类型的成员

//所以应该把struct SIMPLE定义放到struct COMPLEX之前

};

2.

struct NODE

{

    char string[100];

    struct NODE *next_node;//结构体的成员是自己结构体类型的指针(一般来说在链表或者树中经常使用)

};

 

3.如果两个结构体互相包含,则需要对其中一个结构体进行不完整声明,如下所示:

struct B; //对结构体B进行不完整声明

struct A

{

    struct B *partner; //结构体A中包含结构体B类型的指针

    //other members;

};

struct B

{

    struct A *partner; //结构体B中包含结构体A类型的指针

    //other members;

};

 

注意 :

当定义结构体时结构体的成员可以是其他结构体类型的变量

结构体的成员也可以是自己结构体类型的指针

当然不可以是自己结构体类型的变量

而通常自己结构体类型的指针的应用是为了实现一些更高级的数据结构如链表和树等.

/*共用体    联合 union*/

使用单块内存空间,管理不同数据类型

占用的内存空间以共用体当中最大的为准

 

1.同一个内存段可以用来存放几种不同类型的成员,但是在每一瞬间只能存放其中的一种,而不是同时存放几种。

换句话说,每一瞬间只有一个成员起作用,其他的成员不起作用.

2.共用体变量中起作用的成员是最后一次存放的成员,在存入一个新成员后,原有成员就失去作用

3.共用体变量的地址和它的各成员的地址都是同一地址.

4.共用体变量的初始化

1union data a=b; //把共用体变量初始化为另一个共用体

2union data a={123}; //初始化共用体为第一个成员

3union data a={.ch='a'}; //指定初始化项目,按照C99标准

/*

共用体(联合)

需要使用C语言关键字 union

1.定义共用体

union U

{

char c;

short s;

int i;

long l;

float f;

double d;

};

2.共用体的成员共用同一块儿存储空间

用一块儿存储空间管理不同的数据类型

*/

#include <stdio.h>

 

union U

{

    int i;

    float f;

};

 

struct stu

{

int id;

char name[20];

union U math;

};

 

int main(void)

{

struct stu s;

int mode = 0;

 

printf("Please Enter Your Id : ");

scanf("%d", &s.id);

printf("Please Enter Your Name : ");

scanf("%s", s.name);

printf("Please Enter Your Math(1.int 2.float) : ");

scanf("%d", &mode);

switch(mode)

{

case 1 : scanf("%d", &s.math.i); break;

case 2 : scanf("%f", &s.math.f); break;

}

 

printf("================================\n");

 

printf("Id : %d\n", s.id);

printf("Name : %s\n", s.name);

switch(mode)

{

case 1 : printf("Math : %d\n", s.math.i); break;

case 2 : printf("Math : %.1f\n", s.math.f); break;

}

 

return 0;

}

 

/*

通过共用体测试大端格式和小端格式

 

大端格式

高字节存放低地址

 

十六进制数据 0x12345678

 

地址 存储空间

0x1000 0x12

0x1001 0x34

0x1002 0x56

0x1003 0x78

 

小端格式(X86平台)

低字节存放低地址

 

十六进制数据 0x12345678

 

地址 存储空间

0x1000 0x78

0x1001 0x56

0x1002 0x34

0x1003 0x12

*/

#include <stdio.h>

 

union U

{

int i;

char c[4];

};

 

int main(void)

{

union U u;

 

u.i = 13;  //00000000 00000000 00000000 00001101

 

printf("u.c[0] = %d\n", u.c[0]);

printf("u.c[1] = %d\n", u.c[1]);

printf("u.c[2] = %d\n", u.c[2]);

printf("u.c[3] = %d\n", u.c[3]);

 

return 0;

}

位域

有些信息在存储时,并不需要占用一个完整的字节,而只需占几个或一个二进制位.

例如存放一个开关量时,只有 0 1 两种状态,1 位二进位即可.

为了节省存储空间,并使处理简便,C 语言提供了"位域""位段".

 

其实<位域>是把 一个字节 中的二进制位划分为几个不同的区域,并说明每个区域的位数.

每个域有一个域名,允许在程序中按域名进行操作.这样就可以把几个不同的对象用一个字节的二进制位域来表示.

 

struct 位域结构名

{

类型说明符 位域名: 位域长度;

...//省略

};

 

/*

位域

无符号类型

*/

#include <stdio.h>

 

struct S

{

unsigned int a : 1;//a b c是位域名 a 会管理1b会管理之后的2c会管理之后的3

unsigned int b : 2;

unsigned int c : 3;

};

 

union U

{

unsigned char x;

struct S s;

};

 

int main(void)

{

union U u;

 

u.x = 100;

 

printf("u.s.a = %d\n", u.s.a);

printf("u.s.b = %d\n", u.s.b); //

printf("u.s.c = %d\n", u.s.c); //这两行如果定义不是无符号的会打印负号,最高为是符号位,在单片机上都是无符号的,所以定义的时候要用unsigned

 

return 0;

}

 

/*

位域

无符号类型

位运算符 << >> & | ~ ^

寄存器

*/

#include <stdio.h>

 

struct S

{

unsigned char a0 : 2;

unsigned char a1 : 2;

unsigned char a2 : 2;

unsigned char a3 : 2;

unsigned char a4 : 2;

unsigned char a5 : 2;

unsigned char a6 : 2;

unsigned char a7 : 2;

unsigned char a8 : 2;

unsigned char a9 : 2;

unsigned char a10: 2;

unsigned char a11: 2;

unsigned char a12: 2;

unsigned char a13: 2;

unsigned char a14: 2;

unsigned char a15: 2;

};

 

union U

{

unsigned int GPIOA_MODER;//一共32bit

struct S s;//16个位域,每个位域管理2个位

};

 

int main(void)

{

union U u;

 

u.s.a9 = 1;//现在想要把PA9管脚配置成输出模式

 

return 0;

}

枚举

enum

是一个被命名的整型常数的集合

 

枚举的说明与结构和联合相似, 其形式为:

enum 枚举名

{

标识符[=整型常数],

标识符[=整型常数],

...

标识符[=整型常数] //没有逗号

}枚举变量;

如果枚举没有初始化, 即省掉"=整型常数", 则从第一个标识符开始,

次赋给标识符0, 1, 2, ...。但当枚举中的某个成员赋值后, 其后的成员按依次

1的规则确定其值。

 

在枚举中标识符可以和变量同名

当在函数中使用时,用的是变量

 

#include <stdio.h>

 

enum E

{

a,

b = 99,

c,

d

};

 

int main(void)

{

enum E x;

 

printf("a = %d\n", a); //0

printf("b = %d\n", b); //99

printf("c = %d\n", c); //100

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

 

return 0;

}

 

#include <stdio.h>

 

enum E

{

a,

b = 99,

c,

d

};

 

int main(void)

{

int z = 100;

enum E x = z;

 

printf("x = %d\n", x); //100

 

printf("a = %d\n", a); //0

printf("b = %d\n", b); //99

printf("c = %d\n", c); //100

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

 

return 0;

}

 

/*

枚举

使用方法

1.使用枚举中的标识符作为case之后的整常量使用

enum A{HEADINSERT, TAILINSERT};

 

*/

#include <stdio.h>

 

enum E

{

Mode_1,

Mode_2,

Mode_3

};

 

int main(void)

{

int mode = 0;

 

scanf("%d", &mode);

 

switch(mode)

{

case Mode_1 : printf("菜鸟难度!\n"); break;

case Mode_2 : printf("地狱难度!\n"); break;

case Mode_3 : printf("中国难度!\n"); break;

}

return 0;

}

 

/*

枚举

enum

在枚举中标识符可以和变量同名

当在函数中使用时,用的是变量

使用方法

1.使用枚举中的标识符作为case之后的整常量使用

enum A{HEADINSERT, TAILINSERT};

 

*/

#include <stdio.h>

 

enum E

{

a,

b = 10,

c,

d

};

 

int main(void)

{

int i = 0;

 

for(i = a; i < d; i++)

{

printf("hello world!\n");

}

 

return 0;

}

 

/*

枚举

enum

在枚举中标识符可以和变量同名

当在函数中使用时,用的是变量

使用方法

1.使用枚举中的标识符作为case之后的整常量使用

enum A{HEADINSERT, TAILINSERT};

 

2.循环使用

 

3.硬件管脚的定义 寄存器赋值的定义

*/

#include <stdio.h>

 

enum E

{

GPIO_MODER_INPUT,

GPIO_MODER_OUTPUT,

GPIO_MODER_FF,

GPIO_MODER_IF

};

 

int main(void)

{

 

return 0;

}

 

枚举类型变量的赋值和使用

枚举类型在使用中有以下规定:

1.枚举值是常量,不是变量。不能在  程序中  用赋值语句再对它赋值。例如对枚举weekday的元素再作以下赋值: sun=5;mon=2;sun=mon; 都是错误的。

2. 枚举元素本身由系统定义了一个表示序号的数值,从0 开始顺序定义为012…。如在weekday中,sun值为0mon值为1…,sat值为6

3. 只能把枚举值赋予枚举变量,不能把元素的数值直接赋予枚举变量。如: a=sun;b=mon; 是正确的。而: a=0;b=1; 是错误的。如一定要把数值赋予枚举变量,则必须用强制类型转换,如: a=(enum weekday)2;其意义是将顺序号为2的枚举元素赋予枚举变量a,相当于: a=tue;

还应该说明的是枚举元素不是字符常量也不是字符串常量, 使用时不要加单、双引号。

 

注意:

1. 枚举中每个成员(标识符)结束符是"," 不是";", 最后一个成员可省略","

2. 初始化时可以赋负数, 以后的标识符仍依次加1

3. 枚举变量只能取枚举说明结构中的某个标识符常量。

 

预处理指令 描述

#define 定义宏

#include 包含一个源代码文件

#undef 取消已定义的宏 可以用#undef命令终止宏定义的作用域

#ifdef 如果宏已经定义,则返回真

#ifndef 如果宏没有定义,则返回真

#if 如果给定条件为真,则编译下面代码

#else #if 的替代方案

#elif 如果前面的 #if 给定条件不为真,当前条件为真,则编译下面代码

#endif 结束一个 #if……#else 条件编译块

/*条件编译*/

#include <stdio.h>

 

int main(void)

{

 

#if 0

printf("hello world!\n");

#else

printf("hello China!\n");

#endif

 

return 0;

}

 

/*条件编译

#ifdef #else #endif

*/

#include <stdio.h>

 

#define SUB

//定义了ADD

 

int main(void)

{

int a = 13, b = 7;

int sum = 0;

#ifdef ADD

sum = a + b;

#else

sum = a - b;

#endif

printf("sum = %d\n", sum);

 

return 0;

}

/*条件编译

#ifndef #else #endif

*/

#include <stdio.h>

 

#define SUB

//定义了ADD

 

int main(void)

{

int a = 13, b = 7;

int sum = 0;

#ifndef ADD

//如果没有定义过ADD

sum = a + b;

#else

sum = a - b;

#endif

printf("sum = %d\n", sum);

 

return 0;

}

/*条件编译

#if #elif #elif #endif

*/

#include <stdio.h>

 

#define FLAG 3

 

int main(void)

{

int a = 13, b = 7;

int sum = 0;

 

#if FLAG == 0

sum = a + b;

#elif FLAG == 1

sum = a - b;

#elif FLAG == 2

sum = a * b;

#elif FLAG == 3

sum = a / b;

#endif

 

printf("sum = %d\n", sum);

 

return 0;

}

宏函数(能用函数就不用宏)

宏定义是C程序提供的预处理功能之一.包括带参数的宏定义和不带参数的宏定义.

具体是指用一个指定的标志符来进行简单的字符串替换或者进行阐述替换.

 

1)不带参数的宏定义

格式:#define 宏名 替换内容

1.宏名一般用大写

2.提高程序的通用性和易读性,减少输入错误和便于修改。

例如:数组大小常用宏定义

3.可以用#undef命令终止宏定义的作用域

4.宏定义可以嵌套

 

2)带参数的宏定义

格式:#define 宏名(参数表) 替换内容

注意:当使用宏定义时,实参如果是表达式,将来在替换时容易出问题

 

3)带参数的宏定义和函数关系

函数调用在编译后程序运行时进行,并且分配内存.

宏替换在编译前进行,不分配内存.

宏展开不占运行时间,只占编译时间,函数调用占运行时间(分配内存、保留现场、值传递、返回值)

 

/*

宏定义(就是替换)

(不带参数的宏定义)

#define 宏名 [宏值]  []   缺省项,可以没有值

注意 :

1.[]的内容是缺省值

2.宏定义最后不要加 ;

*/

#include <stdio.h>

 

#define BUFSIZE 128

#define PAL 3.14

 

int main(void)

{

char buf[BUFSIZE];

int i;

 

for(i = 0; i < BUFSIZE; i++)

 

return 0;

}

 

#include <stdio.h>

 

#define ADD(A,B) A+B

 

int add(int a, int b)

{

return a + b;

}

 

int main(void)

{

int a = 13, b = 7;

 

printf("add = %d\n", add(a, b));

 

printf("ADD = %d\n", ADD(a, b));

 

return 0;

}

 

/*

宏定义(就是替换)

(带参数的宏定义)

#define 宏名 [宏值]

注意 :

1.[]的内容是缺省值

2.宏定义最后不要加 ;

宏定义和函数

宏定义的优点 : 比函数更快

函数的优点 : 比宏定义更准确(更稳定)

*/

#include <stdio.h>

 

#define FUNC(A) A*A

//1 + 2 * 1 + 2 = 5

 

int func(int a)//

{

return a * a;

}

 

int main(void)

{

 

printf("FUNC = %d\n", FUNC(1 + 2));//FUNC = 5;

 

return 0;

}

 

 

#include <stdio.h>

 

#define MAX(A, B) A>B?A:B

 

int max(int a, int b)

{

return a > b ? a : b;

}

 

int main(void)

{

int a, b;

 

scanf("%d-%d", &a, &b);

 

printf("max = %d\n", max(a, b));

 

printf("MAX = %d\n", MAX(a, b));

 

return 0;

}

=========附加预处理=============

我们使用的文件包含、条件编译、宏定义之外的一些预处理

1)预定义宏

宏 描述

__DATE__ 当前日期,一个以 "MMM DD YYYY" 格式表示的字符常量。

__TIME__ 当前时间,一个以 "HH:MM:SS" 格式表示的字符常量。

__FILE__ 这会包含当前文件名,一个字符串常量。

__LINE__ 这会包含当前行号,一个十进制常量。

2)预处理器运算符

宏延续运算符(\

一个宏定义或者表达式通常写在一行上,但是如果语句太长一行容纳不下,则使用宏延续运算符(\)。

字符串常量化运算符(#

在宏定义中,当需要把一个宏的参数转换为字符串常量时,则使用字符串常量化运算符(#)。

标记粘贴运算符(##

宏定义内的标记粘贴运算符(##)会合并两个参数。它允许在宏定义中两个独立的标记被合并为一个标记。

defined() 运算符

预处理器 defined 运算符是用在常量表达式中的,用来确定一个标识符是否已经使用 #define 定义过。如果指定的标识符已定义,则值为真(非零)。如果指定的标识符未定义,则值为假(零)。

/*结构体程序实例*/

#include <stdio.h>

 

typedef struct stu

{

int id;

char name[20];

int math;

}STU;

 

int main(void)

{

STU s, *p;

 

p = &s; ///结构体指针p必须要有指向才能进行下面的操作

 

p->id = 100;

sprintf(p->name, "Tom");

p->math = 100;

 

printf("p->id = %d\n", p->id);

printf("p->name = %s\n", p->name);

printf("p->math = %d\n", p->math);

 

return 0;

}

指针偏移

#include <stdio.h>

 

union U

{

unsigned int *pi; //32bit

unsigned short *ps; //16bit

};

/*

用同一块8个字节空间存储地址

当使用pi指针偏移时地址偏移4个字节

当使用ps指针偏移时地址偏移2个字节

*/

 

int main(void)

{

union U u;

int a = 100;

short s = 200;

 

u.pi = &a;

 

printf("&a = %p\n", &a);

printf("u.pi = %p\n", u.pi);

printf("=========================\n");

printf("u.pi + 1 = %p\n", u.pi + 1);

 

return 0;

}

附加预处理

#include <stdio.h>

 

int main(void)

{

printf("__DATE__ = %s\n", __DATE__);

 

printf("__TIME__ = %s\n", __TIME__);

 

printf("__FILE__ = %s\n", __FILE__);

 

printf("__LINE__ = %d\n", __LINE__);

 

printf("__FUNCTION__ = %s\n", __FUNCTION__);

 

return 0;

}

/*

附加预处理

 

如果想要使用宏定义表示字符串

定义一个带参数的宏,然后在宏值的前面加#即可

*/

#include <stdio.h>

 

#define PRINT_STR(n) #n

 

int main(void)

{

printf("PRINT_STR = %s\n", PRINT_STR(abc)); //

 

return 0;

}

/*

附加预处理

 

标记粘贴运算符(##

宏定义内的标记粘贴运算符(##)会合并两个参数。它允许在宏定义中两个独立的标记被合并为一个标记。

*/

#include <stdio.h>

 

#define INT_VAR(n) var##n

 

int main(void)

{

int var1 = 100;

int var2 = 200;

int var3 = 300;

 

printf("INT_VAR = %d\n", INT_VAR(3)); //300

 

return 0;

}

通过fopen fclose 实现命令 touch

#include <stdio.h>

 

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

{

FILE *fp = NULL;

int i = 0;

 

for(i = 1; i < argc; i++)

{

fp = fopen(argv[i], "a");

if(NULL == fp)

{

perror("fopen()");

/*??????????????*/

/*

创建多个文件,如果其中某个文件打开失败了,在程序结束前关闭之前打开的文件

*/

return -1;

}

fclose(fp);

fp = NULL;

}

 

return 0;

}

fgetc

文件操作

1.标准IO

stdin

stdout

stderr

2.系统调用IO

文件描述符

0 1 2

 

#include <stdio.h>

 

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

{

FILE *fp = NULL;

int ch = 0;

 

fp = fopen("Tmp", "r");

if(fp == NULL)

{

perror("fopen()");

return -1;

}

 

while(1)

{

ch = fgetc(fp);

if(ch == EOF)

break;

fputc(ch, stdout);

}

 

fclose(fp);

fp = NULL;

 

return 0;

}

 

#include <stdio.h>

 

#define BUFSIZE 128

 

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

{

FILE *fp = NULL;

char buf[BUFSIZE] = {0};

char *ret = NULL;

 

fp = fopen(argv[1], "r");

if(fp == NULL)

{

perror("fopen()");

return -1;

}

 

while(1)

{

ret = fgets(buf, BUFSIZE - 1, fp); //BUFSIZE - 1 是留一个给‘\0’

if(ret == NULL)

break;

fputs(buf, stdout);

}

 

fclose(fp);

fp = NULL;

 

return 0;

}

实现cp功能

cp srcfile destfile

 

fgetc

fgets

homework

实现diff命令

#include <stdio.h>

 

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

{

FILE *fps = NULL;//fps文件流指针指向源文件

FILE *fpd = NULL;//fpd文件流指针指向目标文件

int ch = 0;//ch变量获取文件的字符

 

if(argc < 3)//错误判断如果命令行参数少于3

{

printf("%s: '%s' 后缺少了要操作的目标文件\n", argv[0], argv[1]);

printf("Try '%s --help' for more information.\n", argv[0]);

return -1;

}

 

fps = fopen(argv[1], "r"); //打开源文件

if(fps == NULL) //错误判断(打开文件失败)

{

perror("fopen()");

return -2;

}

 

fpd = fopen(argv[2], "w"); //打开目标文件

if(fpd == NULL) //错误判断(打开文件失败)

{

perror("fopen()");

fclose(fps); //如果程序可以执行到这里证明源文件打开成功,在退出之前需要关闭

return -3;

}

 

while(1)

{

ch = fgetc(fps); //从源文件中获取字符

if(ch == EOF) //判断文件是否到末尾

break;

fputc(ch, fpd);

}

 

fclose(fpd);

fclose(fps);

fpd = fps = NULL;

 

return 0;

}

fread\fwrite

init.h

#ifndef __INIT_H

#define __INIT_H

 

typedef struct stu

{

int id;

char name[20];

int math;

}STU;

 

#endif

 

/fread

#include <stdio.h>

#include "init.h" //init.h中只定义了一个结构体

 

int main(void)

{

FILE *fp = NULL;

STU s = {10086, "yidong", 8192};

 

printf("s.id = %d\n", s.id);

printf("s.name = %s\n", s.name);

printf("s.math = %d\n", s.math);

printf("==========我是分割线=========\n");

 

fp = fopen("data.bin", "r");

if(fp == NULL)

{

perror("fopen()");

return -1;

}

 

fread(&s, sizeof(STU), 1, fp);

 

printf("s.id = %d\n", s.id);

printf("s.name = %s\n", s.name);

printf("s.math = %d\n", s.math);

 

fclose(fp);

fp = NULL;

 

return 0;

}

/fwrite

#include <stdio.h>

#include "init.h" //init.h中只定义了一个结构体

 

int main(void)

{

FILE *fp = NULL;

STU s;

 

scanf("%d", &s.id);

scanf("%s", s.name);

scanf("%d", &s.math);

 

fp = fopen("data.bin", "w");

if(fp == NULL)

{

perror("fopen()");

return -1;

}

 

fwrite(&s, sizeof(STU), 1, fp);

 

fclose(fp);

fp = NULL;

 

return 0;

}

fprintf

#include <stdio.h>

 

int main(void)

{

FILE *fp = NULL;

 

fp = fopen("Tmp", "w");

if(fp == NULL)

{

perror("fopen()");

return -1;

}

 

fprintf(fp, "%d-%c-%f-%s\n", 100, 'A', 3.14, "hello world");

 

fclose(fp);

fp = NULL;

 

return 0;

}

fscanf

#include <stdio.h>

 

int main(void)

{

int a = 0;

char c = 0;

float f = 0;

char buf[128] = {0};

FILE *fp = NULL;

 

fp = fopen("Tmp", "r");

if(fp == NULL)

{

perror("fopen()");

return -1;

}

 

fscanf(fp, "%d-%c-%f-%s", &a, &c, &f, buf);

 

printf("a = %d\n", a);

printf("c = %c\n", c);

printf("f = %f\n", f);

printf("buf = %s\n", buf);

 

fclose(fp);

fp = NULL;

 

return 0;

}

不同方式读取键盘键值(设备文件)

/*

1.getchar() wsad

 

2.访问键盘设备文件

"r"

千万不要"w"

cat /proc/bus/input/devices 查看所有的输入设备对应的event

cat /dev/input/eventX 查看特定的输入设备文件(X = 0 1 2 3 ...)

 

标准IO 系统调用IO

stdin 0

stdout 1

stderr 2

 

*/

#include <stdio.h>

#include <termios.h>

#include <unistd.h>

 

int main(void)

{

int ch = 0;

struct termios old, new;

 

tcgetattr(0, &old);//用来获取终端信息 0 代表标准输入文件的文件描述符

new = old;

new.c_lflag &= ~ICANON;//与等于 按位取反 代表反操作

new.c_lflag &= ~ECHO;//取消回现

tcsetattr(0, TCSANOW, &new);//TCSANOW立马产生影响

 

while(1)

{

ch = getchar();

switch(ch)

{

case 'w' : printf("向上\n"); break;

case 's' : printf("向下\n"); break;

case 'a' : printf("向左\n"); break;

case 'd' : printf("向右\n"); break;

case ' ' : printf("落子\n"); break;

case 'q' : printf("退出\n");

  tcsetattr(0, TCSANOW, &old);//在程序退出之前把老的模式设置回去

  return 0;

}

}

 

return 0;

}

 

/*

1.getchar() wsad

 

2.访问键盘设备文件(系统调用IO)open  close  read  write

"r"

千万不要"w"

cat /proc/bus/input/devices 查看所有的输入设备对应的event

cat /dev/input/eventX 查看特定的输入设备文件(X = 0 1 2 3 ...)

 

标准IO 系统调用IO

stdin 0

stdout 1

stderr 2

 

open("/dev/input/event3");

 

操作设备文件

/usr/include/linux/input.h

/usr/include/linux/input-event-codes.h

*/

#include <stdio.h>

#include <termios.h>

#include <unistd.h>

#include <sys/types.h>

#include <sys/stat.h>

#include <fcntl.h>

#include <linux/input.h>//使用到了头文件中的东西,需要包含

 

int main(void)

{

int kb_fd = 0;//将来保存打开键盘设备的文件描述符

struct input_event ev;//将来保存从键盘设备中获取的信息

struct termios old, new;

 

tcgetattr(0, &old);//用来获取终端信息 0 代表标准输入文件的文件描述符

new = old;

new.c_lflag &= ~ICANON;//与等于 按位取反 代表反操作

new.c_lflag &= ~ECHO;//取消回现

tcsetattr(0, TCSANOW, &new);//TCSANOW立马产生影响

 

kb_fd = open("/dev/input/event3", O_RDONLY);//以只读的方式打开键盘设备<需要修改成自己的设备文件>

if(kb_fd < 0)//打开失败,做错误处理

{

perror("open()");

return -1;

}

 

while(1)

{

read(kb_fd, &ev, sizeof(ev));//读取键盘文件信息,保存到结构体中

if(ev.type == EV_KEY)

{

if(ev.value == SYN_REPORT)

{

switch(ev.code)

{

case 103 : printf("向上\n"); break;

case 108 : printf("向下\n"); break;

case 105 : printf("向左\n"); break;

case 106 : printf("向右\n"); break;

case 28  : printf("落子\n"); break;

case 16  : printf("退出\n");

  tcsetattr(0, TCSANOW, &old);

  close(kb_fd);

  return 0;

}

}

}

}

 

close(kb_fd);//关闭文件

kb_fd = -1;

 

return 0;

}

 

转载于:https://www.cnblogs.com/zjw2030/p/11428759.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值