当前位置:   article > 正文

C语言入门教程合集_c语言基础知识入门

c语言基础知识入门

目录

第一章   初识C语言

1.C语言基础知识

2.C语言的构成

3.第一个C语言程序

4.数据类型

5.C的标识符

5.1标识符的命名规则

5.2标识符的分类

6.变量

6.1定义变量的方法

6.2变量的分类

 6.3变量的使用

6.4变量的作用域和生命周期

7.常量

7.1符号常量

7.2字符串常量

7.3字符常量

7.4实型常量

7.5整型常量

7.6其它常量

8.字符串+转义字符+注释

8.1字符串

8.2转义字符

8.3注释

第二章  C语言分支与循环语句

1. 什么是语句?

2. 分支语句(选择结构)

2.1 if语句

2.2 switch语句

3.循环语句

3.1while循环

3.2 for循环

3.3 do...while()循环

4. goto语句

第三章 函数

1.C语言中函数分类:

1.库函数

2.自定义函数

2.函数参数和函数返回值

1.形式参数和实际参数

2.函数的返回值

3.函数的调用

1.函数声明

2.函数调用的一般形式

3.函数调用的方法

4.函数的嵌套调用

4.函数的递归调用

第四章数组

1.一维数组的创建和初始化

1.1 数组的创建

1.2 数组的初始化

1.3 一维数组的使用

 1.4 一维数组在内存中的存储

1.5字符数组

2. 二维数组的创建和初始化

2.1 二维数组的创建

2.2 二维数组的初始化

2.3 二维数组的使用

2.4 二维数组在内存中的存储

 第五章  运算符与表达式

1.操作符种类

2. 算术操作符

3. 移位操作符

3.1 左移操作符

3.2 右移操作符  

4. 位操作符

 5. 赋值操作符

5.1复合赋值符

6. 单目操作符

6.1自加、 自减运算符

  6.2 sizeof 和 数组

 7. 关系操作符

8. 逻辑操作符

 9. 条件操作符

10. 逗号表达式

11. 下标引用、函数调用和结构成员

1. [ ] 下标引用操作符

2. ( ) 函数调用操作符

3. 访问一个结构的成员

第六章   指针

1.什么是指针?

2. 指针和指针类型

2.1 指针加减整数

2.2 指针的解引用

3. 野指针

3.1 为什么会出现野指针

3.2 如何避免出现野指针

4. 指针运算

4.1 指针+-整数

4.2 指针-指针

5. 指针和数组

5.1数组名为数组首元素地址

5.2 使用指针来访问数组

6. 二级指针

7. 指针数组

8. 字符指针

8.1字符指针 char* 的一般使用:

9. 指针数组

10. 数组指针

10.1 数组指针的一般形式

10.2 &数组名与数组名的区别

10.3 数组指针的使用

11. 数组参数、指针参数

11.1 一维数组传参

11.2 二维数组传参

11.3 一级指针传参

11.4 二级指针传参

12. 函数指针

13. 函数指针数组

14. 指向函数指针数组的指针

第七章 结构体

1. 结构体的声明

1.1 结构的基础知识

1.2 结构的声明

1.3 结构成员的类型

1.4 结构体变量的定义和初始化

2. 结构体成员的访问

3. 结构体传参


第一章   初识C语言

1.C语言基础知识

(1)结构紧凑,使用方便,程序执行效率高。

(2)有9种控制语句,32个关键字和34种运算符。

(3)数据结构丰富,可实现链表,树,栈等复杂的运算。

(4)语法不太严格,程序设计自由度大。

(5)程序可直接访问物理地址,对硬件操作,移植性好。

2.C语言的构成

(1)源程序由函数构成,每个函数完成相对独立的功能。

(2)每个源程序中必须有且只能有一个主函数(main函数),可以放在任意位置,但程序总是从主函数开始执行。

(3) 函数体:在函数后面用一对花括号{}括起来的部分。

(4)每个语句以分号结束,但预处理命令,函数头之后不能加分号。

(5)注释:多行注释: /* 注释内容 */ 单行注释: //注释一行。

(6)预处理命令:以#开头的语句。

3.第一个C语言程序

  1. #include <stdio.h>//头文件
  2. //就是一条预处理命令, 它的作用是通知C语言编译系统在对C程序进行正式编译之前需做一些预处理工作。
  3. int main()
  4. {
  5.    printf("hello world\n");
  6.    printf("he he\n");
  7.    return 0;
  8. }
  9. //main函数是程序的入口
  10. //一个工程中main函数有且仅有一个
  11. //\n为换行

4.数据类型

丰富的数据类型,更加丰富的表达生活中的各种值。

  1. char //字符数据类型
  2. short //短整型
  3. int //整型
  4. long //长整型
  5. long long //更长的整型
  6. float //单精度浮点型
  7. double //双精度浮点型

每种数据类型的大小:

  1. #include<stdio.h>
  2. //sizeof 计算类型的大小,单位是字节
  3. int main()
  4. {
  5. printf("%d\n", sizeof(char)); //1(字节/byte)
  6. printf("%d\n", sizeof(short)); //2
  7. printf("%d\n", sizeof(int)); //4
  8. printf("%d\n", sizeof(long)); //4
  9. printf("%d\n", sizeof(long long)); //8
  10. printf("%d\n", sizeof(float)); //4
  11. printf("%d\n", sizeof(double)); //8
  12. printf("%d\n", sizeof(long double)); //8
  13. return 0;
  14. }

1byte(字节)=8bit(位)

1kb=1024byte

1mb=1024kb

1gb=1024mb

1tb=1024gb

.......

5.C的标识符

标识符是用户编程时使用的名字,像变量名称,函数名称,数据类型都属于标识符。

5.1标识符的命名规则

(1)只能由字母,数字或下划线组成。

(2)第一个字符必须是字母或下划线,不能是数字。

(3)区分字母的大小写。

5.2标识符的分类

(1)关键字:C语言规定的专用的标识符,它们有着固定的含义,不能更改。

(2)预定义标识符:和关键字一样也有特定的含义。

  有库函数的名字,预编译处理命令两种类别。

  用户可以更改预定义标识符的作用,但这将失去系统规定的原来意思,不建议修改。

(3)用户标识符:由用户根据需要定义的标识符。不能与关键字相同。命名应注意到“见名知义”

6.变量

6.1定义变量的方法

  1. #include <stdio.h>
  2. int main()
  3. {
  4. int age = 20;
  5. float weight = 57.0;
  6. char ch = 'w';
  7. return 0;
  8. }

6.2变量的分类

 全局变量&局部变量

  1. #include<stdio.h>
  2. int age = 20; //这是全局变量 {}外部定义的
  3. int main()
  4. {
  5. int age = 10; //这是局部变量 {}内部定义的
  6. printf("age = %d\n", age); //当局部变量与全局变量名字冲突时,局部变量优先使用。
  7. //不建议不全局变量与局部变量的名字写成一样的。
  8. return 0;
  9. }

 当局部变量与全局变量名字冲突时,局部变量优先使用,所以输出为10;

 6.3变量的使用

  1. #include<stdio.h>
  2. int main()
  3. {
  4. int a = 0;
  5. int b = 0;
  6. int sum = 0;
  7. scanf("%d %d", &a, &b);//scanf函数 是输入函数。具体用法详看后面章节
  8. sum = a + b;
  9. printf("%d\n", sum);//printf函数是输出函数
  10. return 0;
  11. }

6.4变量的作用域和生命周期

作用域:通常来说,一段程序代码中所用到的名字并不总是有效的,而限定这个名字的可用性的代码范围就是这个名字的作用域。

局部变量:作用域为变量所在的局部范围。

全局变量: 作用域为整个工程。

生命周期:变量的生命周期指的是变量的创建到变量的销毁之间的一个时间段。

局部变量生命周期:进入作用域生命周期开始,出作用域生命周期结束。

全局变量生命周期:整个程序的生命周期。

7.常量

 定义:在程序运行中,其值不能被改变的量

7.1符号常量

符号常量是由预处理命令“define”定义的常量,在c程序中可用标识符代表一个常量。

7.2字符串常量

字符串常量是用“ ”括起来的一个或者一串字符。

7.3字符常量

一个字符常量代表ASCII码字符集里的一个字符,在程序中用‘ ’括起来,区分大小写。

特殊的字符常量:即转义字符。其中\是转义的意思,后面跟不同的字符表示不同的意思。

\n(换行)

\\(代表反斜杠\)

ddd:1-3位八进制数所代表的一个ASCII字符。

xxh:1-2位十六进制数所代表的一个ASCII字符

7.4实型常量

表示形式:小数形式和指数形式。

书写形式:

1,十进制小数形式:小数点两边必须有数字。

2,指数形式:e前必须有数字(不需是整数),e后面必须为整数。

7.5整型常量

 表示形式:十进制整型常量,八进制整型常量,十六进制整型常量。

书写形式:

1,十进制整型常量:基本数字范围为0-9;

2,八进制整型常量:以0(数字0)开头,基本数字范围为0-7;

3,十六进制整型常量:以0X(数字0)开头,基本数字范围为0-15,其中10-15写为a-f或A-F;

7.6其它常量

const 修饰的常变量

#define 定义的标识符常量

枚举常量

  1. #include <stdio.h>
  2. //举例
  3. enum Color
  4. {
  5. bule,
  6. green,
  7. yellow //最后一个不用逗号隔开
  8. };
  9. //括号中的bule,green,yellow是枚举常量
  10. int main()
  11. {
  12.    //字面常量演示
  13.    3.14;//字面常量
  14.    1000;//字面常量
  15.    
  16.    //const 修饰的常变量
  17.    const float pai = 3.14f;   //这里的pai是const修饰的常变量
  18.    pai = 5.14;//是不能直接修改的!
  19.    
  20.    //#define的标识符常量 演示
  21. #define MAX 100
  22.    printf("max = %d\n", MAX);
  23.    
  24.    //枚举常量演示
  25.    printf("%d\n", bule);
  26.    printf("%d\n", green);
  27.    printf("%d\n", yellow);
  28.    //注:枚举常量的默认是从0开始,依次向下递增1的
  29. return 0;
  30. }

上面例子上的 pai 被称为 const 修饰的常变量, const 修饰的常变量在C语言中只是在语法层面限制了,变量 pai 不能直接被改变,但是 pai 本质上还是一个变量的,所以叫常变量。

8.字符串+转义字符+注释

8.1字符串

"hello world\n"

这种由双引号(Double Quote)引起来的一串字符称为字符串字面值(String Literal),或者简称字符串。

注:字符串的结束标志是一个 \0 的转义字符。在计算字符串长度的时候 \0 是结束标志,不算作字符串的内容。

8.2转义字符

转义字符顾名思义就是转变意思

\?

在书写连续多个问号时使用,防止他们被解析成三字母词

\'

用于表示字符常量'

\“

用于表示一个字符串内部的双引号

\\

用于表示一个反斜杠,防止它被解释为一个转义序列符。

\a

警告字符,蜂鸣

\b

退格符

\f

进纸符

\n

换行

\r

回车

\t

水平制表符

\v

垂直制表符

\ddd

ddd表示1~3个八进制的数字。 如: \130 X

\xdd

dd表示2个十六进制数字。 如: \x30 0

8.3注释

1. 代码中有不需要的代码可以直接删除,也可以注释掉

2. 代码中有些代码比较难懂,可以加一下注释文字

  1. #include <stdio.h>
  2. int Add(int x, int y) {
  3.    return x+y; }
  4. /*C语言风格注释
  5. int Sub(int x, int y)
  6. {
  7.    return x-y;
  8. }
  9. */
  10. int main()
  11. {
  12.    //C++注释风格
  13.    //int a = 10;
  14.    //调用Add函数,完成加法
  15.    printf("%d\n", Add(1, 2));
  16.    return 0;
  17. }

注释有两种风格:

C语言风格的注释 /*xxxxxx*/

缺陷:不能嵌套注释

C++风格的注释 //xxxxxxxx

可以注释一行也可以注释多行

第二章  C语言分支与循环语句

1. 什么是语句?

C语言中由一个分号隔开的就是一条语句。例如:

  1. printf("nihao\n");
  2. ;//这一行除了有一个分号其他什么都没有,这是空语句

2. 分支语句(选择结构)

如果你好好学习,校招时拿一个好offer。如果你不学习,毕业等于失业,回家搬砖这就是选择!

2.1 if语句

如果表达式的结果为真,则语句执行。

在C语言中如何表示真假?

0表示假,非0表示真。注意:负数也为真

语法结构:

if(表达式)

语句;

  1. #include <stdio.h> //如果输入小于18的数,输出打印未成年。
  2. int main()
  3. {
  4. int age = 0;
  5.    scanf("%d", &age);
  6.    if(age<18) //当输入的数小于18时条件为真,输出打印未成年。
  7.   { //当输入的数大于等于18时条件为假,直接下一代码,这里直接执行return 0 ;
  8.        printf("未成年\n");
  9.   }
  10. return 0 ;
  11. }
  12. //if后面只能跟一条有效语句,如果需要多条语句时可以加个{}把需要的语句新进大括号里。

if(表达式)

   语句1;

else

   语句2;

  1. //如果输入小于18的数,输出打印未成年,输入大于等于18的数打印成年。
  2. #include <stdio.h>
  3. int main()
  4. {
  5. int age = 0;
  6.    scanf("%d", &age);
  7.    if(age<18)
  8.   {
  9.        printf("未成年\n");
  10.   }
  11.    else
  12.   {
  13.        printf("成年\n");
  14.   }
  15. return 0 ;
  16. }

 //多分支    

if(表达式1)

   语句1;

else if(表达式2)

   语句2;

else

  语句3;

  1. /*如果输入小于18的数,输出打印未成年,输入大于等于18并且小于30的数输出打印青年,
  2. 输入大于等于30并且小于50的输出打印中年... */
  3. #include <stdio.h>
  4. int main()
  5. {
  6. int age = 0;
  7.    scanf("%d", &age);
  8.    if(age<18)
  9.   {
  10.        printf("少年\n");
  11.   }
  12.    else if(age>=18 && age<30) //当输入的数大于等于18并且小于30时条件为真,输出打印青年。
  13.   {
  14.        printf("青年\n");
  15.   }
  16.    else if(age>=30 && age<50)
  17.   {
  18.        printf("中年\n");
  19.   }
  20.    else if(age>=50 && age<80)
  21.   {
  22.        printf("老年\n");
  23.   }
  24.    else
  25.   {
  26.        printf("老寿星\n");
  27.   }
  28.    
  29. return 0 ;
  30. }

else的匹配:else是和它离的最近的if匹配的。

  1. //适当的使用{}可以使代码的逻辑更加清楚。
  2. //代码风格很重要
  3. #include <stdio.h>
  4. int main()
  5. {
  6.    int a = 0;
  7.    int b = 2;
  8.    if(a == 1) //程序输出的结果为haha
  9.   {
  10.        if(b == 2)
  11.       {
  12.            printf("hehe\n"); //a等于零不等于一,为假,执行else语句。
  13.       }
  14.   }
  15.    else
  16.   {
  17.         printf("haha\n");
  18.   }      
  19.    return 0;
  20. }

2.2 switch语句

switch(整型表达式)

{

   语句项;

}

语句项是什么呢?

//是一些case语句:

//如下:

case 整形常量表达式:

   语句;

switch语句中的 break:

  1. #include <stdio.h>
  2. int main()
  3. {
  4.    int day = 0;
  5. scanf("%d",&day);
  6.    switch(day)
  7.   {
  8.        case 1: //case 整形常量表达式: case 1+0 也是没有问题的;估计也没有人会这样写。
  9.            printf("星期一\n");
  10.            break;
  11.        case 2:
  12.            printf("星期二\n");
  13.            break;
  14.        case 3:
  15.            printf("星期三\n");
  16.            break;    
  17.        case 4:
  18.            printf("星期四\n"); //最好在你想停止的case语句后面加上一条break语句。
  19.            break;    
  20.        case 5:
  21.            printf("星期五\n");// case 语句遇到break 时才会停止;不懂下一个代码就懂了。
  22.            break;
  23.        case 6:
  24.            printf("星期六\n");
  25.            break;
  26.        case 7:
  27.            printf("星期天\n");    
  28.            break;
  29. default:
  30. printf("输入错误\n"); //尽量每个switch语句都放一条deafult语句,最多只能有一条;
  31. break;
  32.   }
  33.    return 0; }

break语句 的实际效果是把语句列表划分为不同的分支部分。

编程好习惯

在最后一个 case 语句的后面加上一条 break语句。

(之所以这么写是可以避免出现在以前的最后一个 case 语句后面忘了添加 break语句)。

  1. #include <stdio.h>
  2. int main()
  3. {
  4.    int day = 0;
  5. scanf("%d",&day);
  6.    switch(day)
  7.   {
  8.        case 1:
  9.        case 2:
  10.        case 3: //当你输入1时,由于没有case 1:后没有跟break,所以会执行case 2 case 3...
  11.        case 4: //直到遇到break为止。
  12.        case 5:
  13.            printf("weekday\n");
  14.            break;
  15.        case 6:
  16.        case 7:
  17.            printf("weekend\n");
  18.            break;
  19. default:
  20. printf("输入错误\n");
  21.   }
  22.    return 0; }

default子句 :

如果表达的值与所有的case标签的值都不匹配怎么办?其实也没什么,结构就是所有的语句都被跳过而已。程序并不会终止,也不会报错,因为这种情况在C中并不认为是个错误。但是,如果你并不想忽略不匹配所有标签的表达式的值时该怎么办呢?你可以在语句列表中增加一条default子句,把下面的标签default:写在任何一个 case 标签可以出现的位置。当 switch 表达式的值并不匹配所有 case 标签的值时,这个 default 子句后面的语句就会执行。所以,每个switch语句中只能出现一条default子句。但是它可以出现在语句列表的任何位置,而且语句流会像执行一个case标签一样执行default子句。

编程好习惯

在每个 switch 语句中都放一条default子句是个好习惯,甚至可以在后边再加一个 break 。

3.循环语句

3.1while循环

//while 语法结构

while(表达式)

循环语句;

执行过程:

第一步:计算紧跟while后括号中表达式的值,当表达式的值为非0时,则接着执行while语句中都内嵌语句;当表达式值为0时,则跳过while语句,执行该while结构后的其他语句。

第二步:执行循环体内嵌语句。

第三步:返回去执行步骤1,直到条件不满足,即表达式的值为0时,退出循环,while循环结束。

例:在屏幕上打印1-10的数字。

  1. #include <stdio.h>
  2. int main()
  3. {
  4. int i = 1;
  5. while(i<=10)
  6. {
  7. printf("%d ", i);
  8. i = i+1;
  9. }
  10. return 0;
  11. }

3.1.1 while语句中的break和continue

  1. //break 代码实例
  2. #include <stdio.h>
  3. int main()
  4. {
  5. int i = 1;
  6. while(i<=10)
  7. {
  8. if(i == 5)
  9. break;
  10. printf("%d ", i);
  11. i = i+1;
  12. }
  13. return 0;
  14. }

 

总结:

break在while循环中的作用:

其实在循环中只要遇到break,就停止后期的所有的循环,直接终止循环。

所以:while中的break是用于永久终止循环的。

  1. //continue 代码实例1
  2. #include <stdio.h>
  3. int main()
  4. {
  5. int i = 1;
  6. while(i<=10)
  7. {
  8. if(i == 5) //当i=5时跳过本次循环continue后面的代码,重新进入循环,i还是等于5,不断往复。
  9. continue;
  10. printf("%d ", i); //结果如下图,到4后光标一直闪。死循环了。
  11. i = i+1;
  12. }
  13. return 0;
  14. }

总结:

continue在while循环中的作用:
continue是用于终止本次循环的,也就是本次循环中continue后边的代码不会再执行,而是直接跳转到while语句的判断部分。进行下一次循环的入口判段

3.2 for循环

3.2.1 语法

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

循环语句;

表达式1

表达式1为初始化部分,用于初始化循环变量的。

表达式2

表达式2为条件判断部分,用于判断循环时候终止。

表达式3

表达式3为调整部分,用于循环条件的调整。

执行过程:

步骤一:先求表达式1的值

步骤二:求表达式2的值,若其值为真(非0),则执行for语句中指定的内嵌语句,然后执行下面步骤三,若其值为假(0),则退出循环,执行for语句一下的其他语句;

步骤三:求表达式3的值;

步骤四:重复执行步骤二。

使用for循环 在屏幕上打印1-10的数字。

  1. #include <stdio.h>
  2. int main()
  3. {
  4. int i = 0;
  5. //for(i=1/*初始化*/; i<=10/*判断部分*/; i++/*调整部分*/)
  6. for(i=1; i<=10; i++) //for循环中的初始化部分,判断部分,调整部分是可以省略的,但是不建议初学时
  7.     //省略,容易导致出现问题。
  8. {
  9. printf("%d ", i);
  10. }
  11. return 0;
  12. }

3.2.2 break和continue在for循环中

我们发现在for循环中也可以出现break和continue,他们的意义和在while循环中是一样的。

但是还是有些差异:

  1. //代码1
  2. #include <stdio.h>
  3. int main()
  4. {
  5. int i = 0;
  6. for(i=1; i<=10; i++)
  7. {
  8. if(i == 5)
  9. break;
  10. printf("%d ",i);
  11. }
  12. return 0; }

 for 遇到break,就停止后期所有循环,直接终止循环....输出结果为1234

  1. //代码2
  2. #include <stdio.h>
  3. int main()
  4. {
  5. int i = 0;
  6. for(i=1; i<=10; i++)
  7. {
  8. if(i == 5)
  9. continue;
  10. printf("%d ",i);
  11. }
  12. return 0; }

for 遇到continue,就停止本次循环,后重新i++后进行判断,输出结果为1234678910

for循环的嵌套:

  1. #include<stdio.h>
  2. int main()
  3. {
  4. int i = 0;
  5. int j = 0;
  6. for (i = 0; i < 10; i++)
  7. {
  8. for (j = 0; j < 10; j++)
  9. {
  10. printf("你好\n"); //一共打印100个你好。
  11. }
  12. }
  13. return 0 ;
  14. }

3.3 do...while()循环

do

循环语句;

while(表达式);

执行过程:

步骤一:先执行一次指定的循环体语句。

步骤二:执行完成后,判断while后面的表达式的值,当表达式的值为非0(真)时,程序流程返回,去重新执行循环体语句。

步骤三:如此反复,直到表达式的值等于零(假)为止,此时循环结束。

3.3.1 do语句的特点

循环至少执行一次,然后判断循环条件是否成立。

  1. #include <stdio.h>
  2. int main()
  3. {
  4. int i = 10;
  5. do
  6. {
  7. printf("%d\n", i);
  8. }while(i<10);
  9. return 0;
  10. }

3.3.2 do while循环中的break和continue

  1. #include <stdio.h>
  2. int main()
  3. {
  4. int i = 5;
  5.  
  6. do
  7. {
  8.        if(5 == i)
  9.            break;
  10. printf("%d\n", i);
  11. }while(i<10);
  12.    
  13. return 0;
  14. }
  1. #include <stdio.h>
  2. int main()
  3. {
  4. int i = 2;
  5. do
  6. {
  7. printf("%d\n", i);
  8. i = i + 1;
  9. if (5 == i) //当i=5时,执行continue,执行continue则跳过本次循环中continue后的代码。
  10. continue; // 然后进行下一次循环的入口判段
  11. printf("good time\n");
  12. } while (i < 10);
  13. return 0;
  14. }

运行结果:

4. goto语句

C语言中提供了可以随意滥用的 goto语句和标记跳转的标号。

从理论上 goto语句是没有必要的,实践中没有goto语句也可以很容易的写出代码。

这里不做展开。感兴趣的可以自己查一下。

第三章 函数

1.C语言中函数分类:

1.库函数

为什么会有库函数?

1. 当我们需要在屏幕上打印程序运行结果的时候我们需要使用printf函数(库函数)进行打印。

2. 在编程的过程中我们会频繁的做一些字符串的拷贝工作(strcpy)。

3. 在编程是我们也计算,总是会计算n的k次方这样的运算(pow)。

像上面我们描述的基础功能,它们不是业务性的代码。我们在开发的过程中每个程序员都可能用的到,为了支持可移植性和提高程序的效率,所以C语言的基础库中提供了一系列类似的库函数,方便程序员 进行软件开发。

那怎么学习库函数呢?

这里我们简单的看看:www.cplusplus.com

感兴趣的可以去看看。

注:使用库函数,必须包含 #include 对应的头文件。

2.自定义函数

自定义函数和库函数一样,有函数名,返回值类型和函数参数。但是不一样的是这些都是我们自己来设计,这给程序员一个很大的发挥空间。

函数的组成:

  1. int fun_name(part1)
  2. {
  3. statement;//语句项
  4. }
  5. int 为返回类型
  6. fun_name 函数名
  7. part1    函数参数

2.函数参数和函数返回值

1.形式参数和实际参数

(1)在定义函数时,函数名后面括号中的变量为形式参数(形参)。

(2)在主调函数中,函数名后面括号中的参数(可以是一个表达式)为实际参数(实参)。

注:

1.实参可以是常量,变量或表达式

2.在被定义的函数中必须指定形参类型

3.实参与形参的类型应相同或赋值相兼容

4.在内存中,实参单元与形参单元是不同的储存单元,只能由实参传给形参即单向传递

5.在调用函数时,给形参分配存储单元,并将实参对应的值传递给该存储单元。调用结束后,形参单元被释放,实参单元仍保留并维持原值。

2.函数的返回值

(1)函数的返回值就是通过函数调用使主调函数能得到一个确定的值。

(2)表达式:return表达式;或return(表达式);或return ;

(3)return 语句中的表达式值的类型必须与函数首部所说明的类型一致。如果不一致,则以函数值的类型为准,由系统自动进行强制转换。

(4)当函数没有指明返回值,或没有返回语句时,函数返回一个不确定的值,为了使函数不返回任何值,可以使用void定义无类型函数。

举个例子:

写一个函数(get_max)可以找出两个整数中的最大值。

  1. #include <stdio.h>
  2. //get_max函数的设计
  3. int get_max(int x, int y) // get_max前的int为函数的返回类型;
  4. {
  5. return (x>y)?(x):(y); //x是否大于y如果是返回x如果不是返回y;
  6. }
  7. //调用结束后,形参单元被释放也就是数据无了,实参单元仍保留并维持原值。
  8. int main() //实参与形参的类型应相同或赋值相兼容,num1与num2为int类型
  9. //所以接收它们的x与y也是int类型。x与y是形参。
  10. {
  11. int num1 = 5;
  12. int num2 = 10;
  13. int max = get_max(num1, num2); //num1与num2是函数实参,用逗号隔开;
  14. printf("max = %d\n", max);
  15. return 0 ;
  16. }

3.函数的调用

1.函数声明

(1) 告诉编译器有一个函数叫什么,参数是什么,返回类型是什么。但是具体是不是存在,函数

声明决定不了。

(2) 函数的声明一般出现在函数的使用之前。要满足先声明后使用

(3)函数的声明一般要放在头文件中

2.函数调用的一般形式

一般形式:函数名(实参);

函数调用可分为调用无参函数和调用有参函数两种:

(1)调用无参函数,不用加实参,但括号不能省略;

(2)调用有参函数时,若有多个实参,各参数间用逗号隔开。实参与形参类型要一致。

3.函数调用的方法

(1)函数语句:把函数调用作为一个语句,这时该函数只需要完成一定的操作而不必有返回值。

(2)函数表达式:一个函数出现在一个表达式中,要求有一个确定的返回值提供给表达式。

(3)函数参数:函数调用作为一个函数的实参。

 调用函数和被调用函数之间的数据传递方式:

1.实参与形参之间进行数据传递。

2.通过return语句把函数返回到主调函数中。

3.通过全局变量

4.函数的嵌套调用

函数和函数之间可以根据实际的需求进行组合的,也就是互相调用的。

注: 函数可以嵌套调用,但是不能嵌套定义。

  1. #include <stdio.h>
  2. void new()
  3. {
  4. printf("你好\n");
  5. }
  6. void hello()
  7. {
  8.    int i = 0;
  9. for(i=0; i<3; i++)
  10.   {
  11.        new();
  12.   }
  13. }
  14. int main() //程序入口
  15. {
  16. hello(); //开始调用hello()函数,后进入for循环,调用3次new()函数,
  17. return 0; //new函数功能为打印你好;
  18. }

4.函数的递归调用

在调用一个函数的过程中又出现直接或间接地调用该函数本身,称为函数的递归调用。

递归做为一种算法在程序设计语言中广泛应用。 一个过程或函数在其定义或说明中有直接或间接 调用自身的 一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解, 递归策略 只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。

使用递归法解决问题,须符合的条件:

(1)可以把要解决的问题转化成一个新的问题。而这个新的问题的解决方法与原来的解决方法相同,只是所处理的对象有规律的递增或递减。

(2)存在限制条件,当满足这个限制条件的时候,递归便不再继续。

  1. #include <stdio.h>
  2. void print(int n) //n=1234
  3. {
  4. if(n>9) //1234>9为真进入print(2)函数,123>9为真又进入print(3)函数,12>9为真
  5. //又进入print(4)函数,直到1>9为假时不执行print函数,开始打印。
  6. { // 1234/10=123;123/10=12;12/10=1;(整数除整数结果为整数)
  7. print(n/10);
  8. } //返回开始进行一级一级进行打印 1%10 12%10 123%10 1234%10;
  9. printf("%d ", n%10); // 1%10=1;12%10=2;123%10=3;1234%10=4;(%前后须为整数)
  10. }
  11. int main()
  12. {
  13. int num = 1234;
  14. print(num); //num实参传递数据给形参n
  15. return 0;
  16. }

 注: 许多问题是以递归的形式进行解释的,这只是因为它比非递归的形式更为清晰。

第四章数组

1.一维数组的创建和初始化

1.1 数组的创建

数组是一组相同类型元素的集合;也就是一组数。

数组的创建方式:

类型说明符  数组名 [ 常量表达式];例:int n = 8; int arr[n]; 是错误的。

  1. char arr3[10]; //char是指数组的元素类型;10是一个常量表达式,用来指定数组的大小
  2. float arr4[5]; //float是指数组的元素类型;5是一个常量表达式,用来指定数组的大小
  3. double arr5[20]; //...

1.2 数组的初始化

  1. int arr1[10] = {1,2,3};//不完全初始化,其余内容默认初始化为零;
  2. int arr8[10] = {8}; //数组存储的内容为 8,0,0,0,0,0,0,0
  3. int arr2[] = {1,2,3,4}; //arr2与arr3其实是一样的。当数组内容确定时,一维数组[]内可不写。
  4. int arr3[4] = {1,234};//完全初始化;
  5. char arr4[3] = {'a',98, 'c'};
  6. char arr5[] = {'a','b','c'}; //字符数组初始化;
  7. char arr6[] = "abcdef"; //字符串数组初始化;

当数组创建后,系统会为该数组在内存中开辟一串连续的存储单元。

1.3 一维数组的使用

数组创建时arr[] 方括号中要为常量表达式,当访问时方括号中可以为变量;

int arr [ 10 ] = { 1 } ; [] ,下标引用操作符。它其实就数组访问的操作符。

每个数组的第一个元素都下标总是0,这也称数组下标的下界,所以上述的数组的最后一个元素下标应该是9,这也称为数组下标的上界。

  1. int arr[10] = {0};
  2. //如果数组10个元素,下标的范围是0-9

 

  1. #include <stdio.h>
  2. int main()
  3. {
  4. int arr1[10] = {0};//数组的不完全初始化
  5.    //整个数组的大小除以一个元素的大小等于数组元素个数。
  6.    int sz = sizeof(arr1)/sizeof(arr1[0]);
  7. //对数组内容赋值,数组是使用下标来访问的,下标从0开始。所以:
  8. int i = 0;//做下标
  9. for(i=0; i<sz; i++)
  10. {
  11. arr1[i] = i;
  12. }
  13. for(i=0; i<sz; ++i)
  14. {
  15. printf("%d ", arr1[i]);//输出数组的内容
  16. }
  17. return 0;
  18. }

 

 1.4 一维数组在内存中的存储

  1. #include <stdio.h>
  2. int main()
  3. {
  4. int arr[10] = {0};
  5. int i = 0;
  6.    int sz = sizeof(arr)/sizeof(arr[0]);
  7.     //整个数组的大小除以一个元素的大小等于数组元素个数。
  8. for(i=0; i<sz; ++i)
  9. {
  10. printf("&arr[%d] = %p\n", i, &arr[i]);//%p按地址的格式打印,十六进制打印。
  11. }
  12. return 0;
  13. }

每个元素的类型为int(4个字节),刚好每个元素的地址也是相差4个字节。

结论:

1.一维数组在内存中是连续存放的。

2.随着数组下标的增长,地址是由低到高变化的。

1.5字符数组

1.字符数组就是数组中每个元素都是字符。

2.对字符数组初始化,可逐个元素地赋值,把字符逐个赋给数组元素。如果花括号中提供的初值个数(字符个数)大于数组长度,则编译时会按语法错误处理。如果初值个数(字符个数),则将这些字符赋给数组中前面那些元素,其余的元素定为空字符(‘\0’)。

char ch3[5] = "hep";   //  'h'  'e'  'p'  '\0'  '\0'

char ch4[] = "hep";   // 'h'  'e'  'p'  '\0'

  1. #include<stdio.h>
  2. #include<string.h>
  3. int main()
  4. {
  5. char ch5[] = "hello"; // 'h','e','l','l','o' '\0'字符串的结束标志位'\0'
  6. char ch6[] = { 'h','e','l','l','o' }; // 'h','e','l','l','o'
  7. printf("%s\n", ch5);
  8. printf("%s\n", ch6);//当遇到\0时才会停止。
  9. printf("%d\n",strlen(ch5)); //strlen:计算字符串长度。头文件为 #include<string.h>
  10. printf("%d\n",strlen(ch6)); //当遇到\0才会停止,ch6中无\0,所以当它往后读取到\0才停止
  11. //所以为随机值。
  12. return 0;
  13. }

2. 二维数组的创建和初始化

2.1 二维数组的创建

一般形式:类型说明符 数组名 [常量表达式] [ 常量表达式]

  1. //数组创建
  2. int arr1[3][5]; //3*5(3行5列)的数组
  3. char arr2[3][2];
  4. double arr3[2][3];

2.2 二维数组的初始化

1.全部初值放在一对花括号中,每一行的初值又分别在一对花括号中,之间用逗号隔开;

2.当某行一对花括号内的初值个数少于该行中元式个数时,系统将自动地给后面的元素补初值0;

3.不能跳过每行前面的元素而给后面的元素赋初值。

4.对于二维数组,只可以省略第一个方括号中的常量表达式,不能省略第二个方括号中的常量表达式。即可以省略行,不能省略列(至于为什么不能省略在2.4中有讲解)

  1. //数组初始化
  2. int arr1[3][4] = {1,2,3,4};//{1,2,3,4,0,0,0,0,0,0,0,0}
  3. int arr2[3][4] = {{1,2},{4,5}};//{{1,2,0,0},{4,5,0,0},{0,0,0,0}}
  4. int arr3[][4] = {{2,3},{4,5}}; //{{2,3,0,0},{4,5,0,0}}

2.3 二维数组的使用

二维数组的使用也是通过下标的方式。

  1. #include <stdio.h>
  2. int main()
  3. {
  4. int arr[3][4] = { 1,2,3,4 };
  5. int i = 0;
  6. for (i = 0; i < 3; i++)
  7. {
  8. int j = 0;
  9. for (j = 0; j < 4; j++)
  10. {
  11. printf("%d ", arr[i][j]);
  12. }
  13. }
  14. return 0;
  15. }

2.4 二维数组在内存中的存储

  1. #include <stdio.h>
  2. int main()
  3. {
  4. int arr[3][4];
  5. int i = 0;
  6. for(i=0; i<3; i++)
  7. {
  8. int j = 0;
  9. for(j=0; j<4; j++)
  10. {
  11. printf("&arr[%d][%d] = %p\n", i, j,&arr[i][j]);
  12. }
  13. }
  14. return 0;
  15. }

二维数组在内存中也是连续存储的

二维数组中列确定一行有几个元素;因为二维数组在内存中是连续存放的,如果列省略了就不知道一行中有几个元素了,也不知道下一行从哪里开始存放数据了。

 第五章  运算符与表达式

1.操作符种类

算术操作符:  +     *    %

移位操作符: <<   >>

位操作符:  |    ^

赋值操作符: = +=  -=  *=  /=  &=  ^=  |=    >>=   <<=

单目操作符 :

!           逻辑反操作
-           负值
+           正值
&           取地址
sizeof      操作数的类型长度(以字节为单位)
~           对一个数的二进制按位取反
--          前置、后置--
++          前置、后置++
*           间接访问操作符(解引用操作符)
(类型)       强制类型转换

关系操作符 :

>
>=
<
<=
!=   用于测试“不相等”
==      用于测试“相等

逻辑操作符 :&&     逻辑与        ||       逻辑或

条件操作符: exp1 ? exp2 : exp3

逗号表达式 :exp1, exp2, exp3, …expN

下标引用、函数调用和结构成员: [] () . ->

2. 算术操作符

  +   -    *   /   %

1. 对于 / 操作符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除法。

2.  %运算符两端必须都是整型,其余的运算对象都可以是整型或实型,返回的是整除之后的余数。

3.双目运算符两边的数值类型必须一致才能进行运算,如果不一致,系统先进行一致性转换。

转换规则:

char —>short —>int —>unsigned —>long —>double —>float

4.所有实数的运算都是以双精度方式进行的,若是单精度数值,则需要在尾数后面补0转换为双精度。

例:

  1. #include <stdio.h>
  2. int main()
  3. {
  4. int a = 8 ;
  5. int b = 4 ;
  6. int c = 0 ;
  7. int d = 0 ;
  8. c = a/b ; // 8/4=2; 8/2.0=4.0;
  9. d = a%b ; // 8.0%2是错误的,%两端必须为整数;
  10. return 0 ;
  11. }

 

3. 移位操作符

<< 左移操作符 :将一个数的二进制位全部左移若干位。

>> 右移操作符 :将一个数的二进制位全部右移若干位。  

注:移位操作符的操作数只能是整数;对于移位运算符,不要移动负数位,这个是标准未定义的。

3.1 左移操作符

移位规则: 左边抛弃、右边补0

  1. #include <stdio.h>
  2. int main()
  3. {
  4. int a = 1 ;//整型4个字节32位;0000 0000 0000 0000 0000 0001 (1)
  5. int b = a << 1 ;//往左移一位:0000 0000 0000 0000 0000 0010 (2)
  6. printf("b=%d\n",b);
  7. return 0 ;
  8. }

 

 

3.2 右移操作符  

原码反码补码

正整数:原码反码补码相同;整数在内存中存储的是二进制的补码;

-1:

1000 0000 0000 0000 0000 0000 0000 01 (原码)

1111 1111  1111  1111  1111  1111  1111  10 (反码,符号位不用取反)

1111 1111  1111  1111  1111  1111  1111  11 (补码,反码+1)

1. 逻辑移位(<<<  >>>)

不考虑符号位,左边用0填充,右边丢弃

int a = -1; a>>>1

1111 1111 1111 1111 1111 1111 1111 1111

0111 1111 1111 1111 1111 1111 1111 1111

2. 算术移位

需考虑符号位,左边用原该值的符号位填充,右边丢弃

int a = -1; a>>1

1111 1111 1111 1111 1111 1111 1111 1111

1111 1111 1111 1111 1111 1111 1111 1111

4. 位操作符

&  按位与

|  按位或

^  按位异或

注:他们的操作数必须是整数。

  1. #include <stdio.h>
  2. int main()
  3. {
  4. int a = 1; // 0000 0000 0000 0001
  5. int b = 2; // 0000 0000 0000 0010
  6. int c = a & b; // 0000 0000 0000 0000 两个为1才为1
  7. int d = a | b; // 0000 0000 0000 0011 有1个为1就为1
  8. int e = a ^ b; // 0000 0000 0000 0011 两个为0就为0,两个为1也为0;
  9. printf("c=%d d=%d e=%d ", c, d, e);
  10. return 0;
  11. }

 

 5. 赋值操作符

一般形式:变量名 = 表达式

赋值运算符的优先级高于逗号运算符;注意= 与== 的区别;

赋值运算符的左侧只能是变量,而不能是常量或者表达式。右侧可以是表达式,包括赋值表达式;

规定最左边变量所得到的新值就是整个赋值表达式的值。

5.1复合赋值符

+=  -=   *=  /=  %=  >>=  <<=  &=  |=  ^=

注:俩个符号之间不可以有空格,复合赋值符的优先级和赋值运算符相同。

  1. int x = 2;
  2. x = x+10; //与x += 10 是一样的。
  3. x += 10;
  4. //其他运算符一样的道理。这样写更加简洁

 

6. 单目操作符

!           逻辑反操作 (真变假,假变真)

-           负值

+           正值

&           取地址

sizeof      操作数的类型长度(以字节为单位)

~           对一个数的二进制按位取反

--          前置、后置--

++          前置、后置++

*           间接访问操作符(解引用操作符)

(类型)       强制类型转换

6.1自加、 自减运算符

(1)作用:自加运算符“++”使运算变量的值增1,自减运算符“”使运算变量的值减1。
(2)均是单目运算符。运算对象可以是整型或实型变量,但不可以是常量和表达式。
(3)均可作为前缀运算符,也可作为后缀运算符构成一个表达式。++i, --i:在使用i之前,先使i的值加1或者减1,再使用此时的表达式的值参加运算。
i++,i--:在使用i之后,使i的值加1或者减1,再使用此时的表达式的值参加运算。
(4)结合方向:自右向左。

  1. #include <stdio.h>
  2. int main()
  3. {
  4.    int a = 10;
  5.    int x = a++; //对a先使用,再增加,这样x的值是10;之后a变成11;
  6.    int b = ++a ; //a=11,先加再使用, 这样b的值为12; a变成的12 ;
  7.    int y = a--; //a=12,对a先使用,再自减,这样y的值是12;之后a变成11;
  8.    int c = --a; //a=11,先减再使用, 这样c的值为10, a变成10;
  9. printf("a=%d x=%d b=%d y=%d c=%d ",a,x,b,y,c) ;
  10.    return 0;
  11. }

 

  6.2 sizeof 和 数组

  1. #include <stdio.h>
  2. void test1(int arr[])
  3. { // arr为数组首元素地址
  4. printf("%d\n", sizeof(arr));//传过来的是地址32位平台为4个字节
  5. } //64位平台为8个字节
  6. void test2(char ch[])
  7. {
  8. printf("%d\n", sizeof(ch)); //ch为数组首元素地址
  9. }
  10. int main()
  11. {
  12. int arr[10] = {0};
  13. char ch[10] = {0};
  14. printf("%d\n", sizeof(arr));//每个元素为int类型,一共10个元素;4乘10为40;
  15. printf("%d\n", sizeof(ch));// 1乘10等于10;
  16. test1(arr);
  17. test2(ch);
  18. return 0;
  19. }

 

 7. 关系操作符

>

>=

<

<=

!=   用于测试“不相等”

==      用于测试“相等”

注意=与==的区别就可以了

8. 逻辑操作符

&&     逻辑与

 ||        逻辑或

结合性:自左向右

  1. #include <stdio.h>
  2. int main()
  3. {
  4. int i = 0, a = 0, b = 2, c = 3, d = 4;
  5. i = a++ && ++b && d++; //a++先使用再加,所以表达式为0,0与任何数都为0。
  6. //当为0时,后面表达式系统就跳过了。
  7. printf("a = %d b = %d c = %d d = %d", a, b, c, d); //1 2 3 4
  8. return 0;
  9. }

 

  1. #include <stdio.h>
  2. int main()
  3. {
  4. int i = 0, a = 0, b = 2, c = 3, d = 4;
  5. i = a++||++b||d++; //俩个有一个为1即为真(1),a++先使用再加,即表达式为0;
  6. //为0继续算后面的(为真就不用算了),++b为3(真).
  7. printf("a = %d b = %d c = %d d = %d", a, b, c, d); //i的值为1
  8. return 0;
  9. }

 

 

 9. 条件操作符

 exp1 ? exp2 : exp3

  1. #include <stdio.h>
  2. //get_max函数的设计
  3. int get_max(int x, int y) // get_max前的int为函数的返回类型;
  4. {
  5. return (x>y)?(x):(y); //x是否大于y如果是返回x如果不是返回y;表达式1为真就返回表达式2,
  6. } //否则返回表达式3;
  7. //调用结束后,形参单元被释放也就是数据无了,实参单元仍保留并维持原值。
  8. int main() //实参与形参的类型应相同或赋值相兼容,num1与num2为int类型
  9. //所以接收它们的x与y也是int类型。x与y是形参。
  10. {
  11. int num1 = 5;
  12. int num2 = 10;
  13. int max = get_max(num1, num2); //num1与num2是函数实参,用逗号隔开;
  14. printf("max = %d\n", max);
  15. return 0 ;
  16. }

10. 逗号表达式

exp1, exp2, exp3, …expN

逗号表达式,就是用逗号隔开的多个表达式。

逗号表达式,从左向右依次执行。整个表达式的结果是最后一个表达式的结果。

  1. #include <stdio.h>
  2. int main()
  3. {
  4. int a = 1;
  5. int b = 2;
  6. int c = (a>b, a=b+10, a, b=a+1);//表达式1为0,a=2+10;表达式3为12,
  7. printf("c=%d ",c); //b=12+1;取最后一个表达式的结果为整个表达式的结果。
  8. return 0;
  9. }

11. 下标引用、函数调用和结构成员

1. [ ] 下标引用操作符

操作数:一个数组名 + 一个索引值

  1. int arr[5];//创建数组
  2. arr[4] = 10;//实用下标引用操作符。
  3. // [ ]的两个操作数是arr和5。

2. ( ) 函数调用操作符

接受一个或者多个操作数:第一个操作数是函数名,剩余的操作数就是传递给函数的参数。

  1. #include <stdio.h>
  2. void test1()
  3. {
  4. printf("你好\n");
  5. }
  6. int main()
  7. {
  8. test1();   // 实用()作为函数调用操作符。         
  9. return 0;
  10. }

3. 访问一个结构的成员

. 结构体.成员名      -> 结构体指针->成员名

  1. #include <stdio.h>
  2. struct Stu
  3. {
  4. char name[10];
  5. int age;
  6. char sex[5];
  7. double score;
  8. }; //记得加; 函数不用,结构体要;
  9. void set_age1(struct Stu stu)
  10. {
  11. stu.age = 18;
  12. }
  13. void set_age2(struct Stu* pStu) {
  14. pStu->age = 18;//结构成员访问
  15. }
  16. int main()
  17. {
  18. struct Stu stu;
  19. struct Stu* pStu = &stu;//结构成员访问
  20. stu.age = 20;//结构成员访问
  21. set_age1(stu);
  22. pStu->age = 20;//结构成员访问
  23. set_age2(pStu);
  24. return 0;
  25. }

第六章   指针

1.什么是指针?

1. 指针是内存中一个最小单元的编号,也就是地址

2. 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量

指针变量

我们可以通过&(取地址操作符)取出变量的内存其实地址,把地址可以存放到一个变量中,这个

变量就是指针变量

  1. #include <stdio.h>
  2. //指针变量的大小取决于地址的大小
  3. //32位平台下地址是32个bit位(即4个字节)
  4. //64位平台下地址是64个bit位(即8个字节)
  5. int main()
  6. {
  7.    printf("%d\n", sizeof(char *));
  8.    printf("%d\n", sizeof(short *));
  9.    printf("%d\n", sizeof(int *));
  10.    printf("%d\n", sizeof(double *));
  11.    return 0; }

在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以

一个指针变量的大小就应该是4个字节。

那如果在64位机器上,有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地

址。

指针是用来存放地址的,地址是唯一标示一块地址空间的。

2. 指针和指针类型

  1. char  *pc = NULL;
  2. int   *pi = NULL;
  3. short *ps = NULL;
  4. long  *pl = NULL;
  5. float *pf = NULL;
  6. double *pd = NULL;

char* 类型的指针是为了存放 char 类型变量的地址。

short* 类型的指针是为了存放 short 类型变量的地址。

int* 类型的指针是为了存放 int 类型变量的地址。

指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。

 char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节。

2.1 指针加减整数

  1. #include <stdio.h>
  2. //演示实例
  3. int main()
  4. {
  5. int n = 10;
  6. char *pc = (char*)&n;
  7. int *pi = &n;
  8. printf("%p\n", &n);
  9. printf("%p\n", pc);
  10. printf("%p\n", pc+1);
  11. printf("%p\n", pi);
  12. printf("%p\n", pi+1);
  13. return  0;
  14. }

 指针类型决定了指针解引用的权限有多大,指针走一步能走多远。

2.2 指针的解引用

  1. #include <stdio.h>
  2. int main()
  3. {
  4. int arr[10] = {0};
  5. int* p = arr;
  6. int i = 0;
  7. for (i = 0; i<10; i++)
  8. {
  9. *(p+1) = 1;
  10. }
  11. return 0;
  12. }

指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。

比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节。

3. 野指针

 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)

3.1 为什么会出现野指针

1. 指针未初始化

  1. #include <stdio.h>
  2. int main()
  3. { //这里p为野指针
  4. int* p; //p是一个局部变量(在{}里的变量为局部变量),局部变量不初始化默认是随机值。
  5. *p = 10; //非法访问内存。
  6. return 0;
  7. }

2. 指针越界访问

  1. #include <stdio.h>
  2. int main()
  3. {
  4.    int arr[10] = {0};
  5.    int *p = arr; //arr为数组首元素地址;
  6.    int i = 0;
  7.    for(i=0; i<=11; i++)
  8.   { //循环11次,而arr中只有10个元素。
  9.         //当指针指向的范围超出数组arr的范围时,p就是野指针
  10.        *(p++) = i;
  11.   }
  12.    return 0;
  13. }

3. 指针指向的空间释放

  1. #include <stdio.h>
  2. int* test()
  3. {
  4. int a = 10;
  5. return &a; //当test函数调用结束时,里面的数据被销毁。
  6. //*p解引用时,a的地址已经不是原先的地址了;
  7. }
  8. int main()
  9. {
  10. int* p = test();
  11. *p = 20;
  12. return 0;
  13. }

3.2 如何避免出现野指针

1. 指针初始化  

即赋个明确的地址给它; int a = 10; int* p = &a;

或者 int* = NULL;

2. 小心指针越界

3. 指针指向空间释放及时置NULL

4. 避免返回局部变量的地址

5. 指针使用之前检查有效性

判断指针是否有效 :if(p != NULL)

4. 指针运算

4.1 指针+-整数

  1. #include<stdio.h>
  2. int main()
  3. {
  4. float values[5];
  5. float *vp;
  6. //指针+-整数;指针的关系运算
  7. for (vp = &values[0]; vp < &values[5];)
  8. {//指针变量vp拿到数组首元素地址。vp = vp + 1向后移动4个字节的地址;拿到values[1]的地址;
  9. *vp++ = 0; //相当于 *(vp++) *vp = 0; vp = vp + 1;
  10. //*与++同优先级,自右往左的顺序执行。++为后置++所以当执行完
  11. } //这条语句再自身+1;
  12. int i = 0;
  13. for (i = 0; i < 5; i++)
  14. {
  15. printf("%f\n", values[i]);
  16. }
  17. return 0;
  18. }

4.2 指针-指针

指针和指针相减的前提是两个指针指向同一块空间;

指针减指针得到的是两个指针之间的元素个数

  1. #include <stdio.h>
  2. int main()
  3. {
  4. int arr[10] = {1,2,3,4,5,6,7,8,9,10};
  5. printf("%d\n",&arr[9] - arr[2]);
  6. return 0;
  7. }

5. 指针和数组

5.1数组名为数组首元素地址

  1. #include <stdio.h>
  2. int main()
  3. {
  4. int arr[5] = {1,2,3,4,5};
  5.    printf("%p\n", arr);
  6.    printf("%p\n", &arr[0]);
  7.    return 0;
  8. }

5.2 使用指针来访问数组

  1. #include <stdio.h>
  2. int main()
  3. {
  4. int arr[10] = {0};
  5. int* p = arr; //拿到数组首元素地址;
  6. int i = 0;
  7. for(i = 0; i<10; i++)
  8. {
  9. *(p + i) = i; //p+i找到所需地址,然后解引用赋值;
  10. }
  11. for (i = 0; i<10; i++)
  12. {
  13. printf("%d ",*(p + i));
  14. }
  15. return 0;
  16. }

6. 二级指针

  1. #include <stdio.h>
  2. int main()
  3. {
  4. int a = 10;
  5. int* pa = &a;//pa是指针变量(一级指针)
  6. int**ppa = &pa; //ppa是一个二级指针变量;
  7. //a的地址存放在pa中,pa的地址存放在ppa中;
  8. int b = 20;
  9. *ppa = &b; //等价于 pa = &b;
  10. //*ppa 通过对ppa中的地址进行解引用,这样找到的是 pa ;*ppa 其实访问的就是 pa
  11. **ppa = 30;//等价于*pa = 30; 等价于a = 30;
  12. //**ppa 先通过 *ppa 找到 pa ,然后对 pa 进行解引用操作: *pa ,那找到的是 a .
  13. return 0;
  14. }

7. 指针数组

  1. #include <stdio.h>
  2. int main()
  3. {
  4. int arr[10];//整型数组,存放的每个元素时int型;
  5. char ch[5]; //字符数组 ,存放的是字符
  6. int* parr[2]; //整型指针数组,存放的每个元素是整型指针;
  7. char* pch[2]; //字符指针数组,存放的每个元素是字符型指针;
  8. return 0;
  9. }

8. 字符指针

8.1字符指针 char* 的一般使用:

  1. int main()
  2. {
  3.    char ch = 'q';
  4.    char *pc = &ch;
  5.    *pc = 'w';
  6.    return 0;
  7. }
  1. #include<stdio.h>
  2. int main()
  3. {
  4. char* ps = "hello world";//把一个常量字符串的首字符 h 的地址存放到指针变量 ps中。
  5. printf("%s\n", ps);//输出结果为hello world
  6. return 0;
  7. }
  1. #include <stdio.h>
  2. int main()
  3. {
  4. char str1[] = "hello";
  5. char str2[] = "hello";
  6. char *str3 = "hello";
  7. char *str4 = "hello";
  8. if (str1 == str2)
  9. printf("str1 and str2 are same\n");
  10. else
  11. printf("str1 and str2 are not same\n");
  12. if (str3 == str4)
  13. printf("str3 and str4 are same\n");
  14. else
  15. printf("str3 and str4 are not same\n");
  16. return 0;
  17. }

这里str3和str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当 几个指针指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化 不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4相同。

9. 指针数组

 指针数组是一个存放指针的数组

  1. #include <stdio.h>
  2. int main()
  3. {
  4. int arr[10];//整型数组,存放的每个元素时int型;
  5. char ch[5]; //字符数组 ,存放的是字符
  6. int* parr[2]; //整型指针数组,存放的每个元素是整型指针;
  7. char* pch[2]; //字符指针数组,存放的每个元素是字符型指针;
  8. //pch先与[2]结合,说明是一个数组
  9. return 0;
  10. }
  11. /*
  12. int* arr1[10]; //整形指针的数组
  13. char *arr2[4]; //一级字符指针的数组
  14. char **arr3[5];//二级字符指针的数组
  15. */

10. 数组指针

10.1 数组指针的一般形式

int (*p)[5];

解释:p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为5个整型的数组。所以p是一个 指针,指向一个数组,叫数组指针,本质为指针。

这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合。

10.2 &数组名数组名的区别

  1. #include <stdio.h>
  2. int main()
  3. {
  4. int arr[10] = { 0 };
  5. printf("arr = %p\n", arr);
  6. printf("&arr = %p\n", &arr);
  7. printf("arr+1 = %p\n", arr + 1);
  8. printf("&arr+1= %p\n", &arr + 1);
  9. return 0;
  10. }

实际上: &arr 表示的是数组的地址,而不是数组首元素的地址,arr表示的是首元素地址。

本例中 &arr 的类型是: int(*)[10] ,是一种数组指针类型

数组的地址+1,跳过整个数组的大小,所以 &arr+1 相对于 &arr 的差值是40(40个字节);

10.3 数组指针的使用

既然数组指针指向的是数组,那数组指针中存放的应该是数组的地址。

  1. #include <stdio.h>
  2. void print_arr1(int arr[3][5], int row, int col)
  3. {
  4.    int i = 0;
  5. int j = 0;
  6.    for(i=0; i<row; i++)
  7.   {
  8.        for(j=0; j<col; j++)
  9.       {
  10.            printf("%d ", arr[i][j]);
  11.       }
  12.        printf("\n");
  13.   }
  14. }
  15. void print_arr2(int (*arr)[5], int row, int col)
  16. {
  17.    int i = 0;
  18. int j = 0;
  19.    for(i=0; i<row; i++)
  20.   {
  21.        for(j=0; j<col; j++)
  22.       {
  23.            printf("%d ", arr[i][j]);
  24.       }
  25.        printf("\n");
  26.   }
  27. }
  28. int main()
  29. {
  30.    int arr[3][5] = {1,2,3,4,5,6,7,8,9,10};
  31.   print_arr1(arr, 3, 5);
  32.    //数组名arr,表示首元素的地址
  33.    //但是二维数组的首元素是二维数组的第一行
  34.    //所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
  35.    //可以数组指针来接收
  36.    print_arr2(arr, 3, 5);
  37.    return 0;
  38. }

  1. int arr[5]; //整型数组
  2. int *parr1[10]; //整型指针数组
  3. int (*parr2)[10]; //数组指针,该指针指向一个数组,该数组有10个元素,每个元素为int型
  4. int (*parr3[10])[5];//parr3是一个存储数组指针的数组,该数组能存放10个数组指针,
  5. //每个数组指针能指向一个数组,数组5个元素,每个元素为int型。
  6. parr3先与[10]结合,说明是一个数组,数组的每个元素类型为int(*)[5] ——————>这个为数组指针

11. 数组参数、指针参数

11.1 一维数组传参

  1. #include <stdio.h>
  2. void test1(int arr[])//写法正确,数组传过来,数组接收[]内可以不写。
  3. {}
  4. void test1(int arr[10])//写法正确
  5. {}
  6. void test1(int *arr)//写法正确 arr为数组首元素地址,用指针接收,没得问题。
  7. {}
  8. void test2(int *arr[20])//写法正确 整型指针数组传参,整形指针数组接收
  9. {}
  10. void test2(int **arr)//写法正确 arr2表示首元素地址,首元素为int* ,
  11. {} //arr2为首元素int*的地址,一级指针的地址传过来,二级指针接收。
  12. int main()
  13. {
  14. int arr1[10] = {0};
  15. int *arr2[20] = {0};//整型指针数组,存放int* 的数组 int* int* int*.......
  16. test1(arr1);
  17. test2(arr2);
  18. }

11.2 二维数组传参

  1. void test(int arr[3][5])//正确
  2. {}
  3. void test(int arr[][])//错误
  4. {}
  5. void test(int arr[][5])//正确
  6. {}
  7. //二维数组传参,函数形参的设计只能省略第一个[]的数字。
  8. //因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
  9. void test(int *arr)//错误 二维数组数组名代表第一行的地址(一维数组的地址),第一行有
  10. // 5个元素怎么能拿一级指针接收呢
  11. {}
  12. void test(int* arr[5])//错误 人家传过来的是二维数组,形参部分为指针数组,显然不行
  13. {}
  14. void test(int (*arr)[5])//正确 数组指针,指向一个数组,数组5个元素,每个元素为int;
  15. {}
  16. void test(int **arr)//错误 传过来的根本不是二级指针,显然不行
  17. {}
  18. int main()
  19. {
  20. int arr[3][5] = {0};
  21. test(arr);
  22. }

11.3 一级指针传参

  1. #include <stdio.h>
  2. void print(int *p, int sz)
  3. {
  4. int i = 0;
  5. for(i=0; i<sz; i++)
  6. {
  7. printf("%d ", *(p+i)); //原样打印arr[10]中的内容
  8. }
  9. }
  10. int main()
  11. {
  12. int arr[10] = {1,2,3,4,5,6,7,8,9};
  13. int *p = arr;
  14. int sz = sizeof(arr)/sizeof(arr[0]);
  15. //一级指针p,传给函数
  16. print(p, sz);
  17. return 0;
  18. }

11.4 二级指针传参

  1. #include <stdio.h>
  2. void test(int** ptr)
  3. {
  4. printf("num = %d\n", **ptr);
  5. }
  6. int main()
  7. {
  8. int n = 10;
  9. int*p = &n;
  10. int **pp = &p;
  11. test(pp); //输出结果为10
  12. test(&p); //输出结果为10
  13. return 0;
  14. }

12. 函数指针

指向函数的指针,存放函数地址的指针

  1. void test()
  2. {
  3. printf("hehe\n");
  4. }
  5. void (*pfun1)();
  6. pfun1先和*结合,说明pfun1是指针,指针指向的是一个函数,指向的函数无参
  7. 数,返回值类型为void
  1. #include <stdio.h>
  2. int ADD(int x, int y)
  3. {
  4. return x + y;
  5. }
  6. int main()
  7. {
  8. int (*pf)(int,int) = &ADD;//取出ADD的地址放到pf指针里面,pf指向了这个函数
  9. int ret = (*pf)(3,5); //对pf进行解引用找到了它所指向的函数,然后传参就可以了;
  10. printf("%d ",ret); //pf(3,5)也可以(*****pf)(3,5)也可以,*pf只是好理解,实际上没意义
  11. return 0;
  12. }

13. 函数指针数组

 顾名思义即存放函数指针的数组

  1. #include <stdio.h>
  2. int add(int a, int b)
  3. {
  4. return a + b;
  5. }
  6. int sub(int a, int b)
  7. {
  8. return a - b;
  9. }
  10. int mul(int a, int b)
  11. {
  12. return a * b;
  13. }
  14. int div(int a, int b)
  15. {
  16. return a / b;
  17. }
  18. int main()
  19. {
  20. int x, y;
  21. int input = 1;
  22. int ret = 0;
  23. int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //函数指针数组
  24. while (input)
  25. {
  26. printf("*************************\n");
  27. printf(" 1:add 2:sub \n");
  28. printf(" 3:mul 4:div \n");
  29. printf("*************************\n");
  30. printf("请选择:");
  31. scanf("%d", &input);
  32. if ((input <= 4 && input >= 1))
  33. {
  34. printf("输入操作数:");
  35. scanf("%d %d", &x, &y);
  36. ret = (*p[input])(x, y);//利用函数指针数组里的函数指针调用函数
  37. printf("ret = %d\n", ret);
  38. }
  39. else
  40. printf("输入有误\n");
  41. }
  42. return 0;
  43. }

14. 指向函数指针数组的指针

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

指针指向一个 数组 ,数组的元素都是函数指针;

  1. void test(const char* str)
  2. {
  3. printf("%s\n", str);
  4. }
  5. int main()
  6. {
  7. //函数指针pfun
  8. void (*pfun)(const char*) = test;//&test也是一样的
  9. //函数指针的数组pfunArr
  10. void (*pfunArr[5])(const char* str);
  11. pfunArr[0] = test;
  12. //指向函数指针数组pfunArr的指针ppfunArr
  13. void (*(*ppfunArr)[5])(const char*) = &pfunArr;
  14. return 0;
  15. }

第七章 结构体

1. 结构体的声明

1.1 结构的基础知识

结构是一些值的集合,这些值称为成员变量;结构的每个成员可以是不同类型的变量。

1.2 结构的声明

  1. struct tag
  2. {
  3. member-list;
  4. }variable-list;
  1. typedef struct Stu
  2. {
  3. char name[20];//名字
  4. int age;//年龄
  5. char sex[5];//性别
  6. char id[20];//学号
  7. }Stu;//分号不能丢
  8. struct Point
  9. {
  10. int x;
  11. int y;
  12. }p1; //声明类型的同时定义变量p1
  13. struct Point p2; //定义结构体变量p2
  14. //初始化:定义变量的同时赋初值。

1.3 结构成员的类型

结构的成员可以是标量、数组、指针,甚至是其他结构体。

  1. #include <stdio.h>
  2. struct B
  3. {
  4. char a;
  5. int b;
  6. double c;
  7. };
  8. struct Stu
  9. { //成员变量
  10. struct B sb;
  11. char name[10];
  12. int age;
  13. char id[12];
  14. }s1,s2;//s1,s2也是结构体变量,s1,s2是全局变量。
  15. int main()
  16. {
  17. struct Stu s;
  18. return 0;
  19. }

1.4 结构体变量的定义和初始化

  1. struct Point
  2. {
  3. int x;
  4. int y;
  5. }p1; //声明类型的同时定义变量p1
  6. struct Point p2; //定义结构体变量p2
  7. //初始化:定义变量的同时赋初值。
  8. struct Point p3 = {x, y};
  9. struct Stu        //类型声明
  10. {
  11. char name[15];//名字
  12. int age;      //年龄
  13. };
  14. struct Stu s = {"zhangsan", 20};//初始化
  15. struct Node
  16. {
  17. int data;
  18. struct Point p;
  19. struct Node* next;
  20. }n1 = {10, {4,5}, NULL}; //结构体嵌套初始化
  21. struct Node n2 = {20, {5, 6}, NULL};//结构体嵌套初始化

2. 结构体成员的访问

结构变量的成员是通过点操作符(.)访问的。

有时候我们得到的不是一个结构体变量,而是指向一个结构体的指针指针访问,结构体变量使用->

  1. #include <stdio.h>
  2. struct B
  3. {
  4. char a;
  5. int b;
  6. double c;
  7. };
  8. struct Stu
  9. { //成员变量
  10. struct B sb;
  11. char name[10];
  12. int age;
  13. char id[12];
  14. }s1,s2;//s1,s2也是结构体变量,s1,s2是全局变量。
  15. int main()
  16. {
  17. struct Stu s ={ {'b',10,20.25},"法外狂徒",20,"20190034235"};
  18. printf("%d\n",s.sb.b);
  19. struct Stu* ps = &s;
  20. printf("%d\n",(*ps).sb.b);
  21. printf("%d\n",ps->sb.b);
  22. return 0;
  23. }

3. 结构体传参

函数传参的时候,参数是需要压栈的。如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。

  1. #include <stdio.h>
  2. struct S
  3. {
  4. int data[1000];
  5. int num;
  6. };
  7. struct S s = {{1,2,3,4}, 1000};
  8. //结构体传参
  9. void print1(struct S s)
  10. {
  11. printf("%d\n", s.num);
  12. }
  13. //结构体地址传参
  14. void print2(struct S* ps)
  15. {
  16. printf("%d\n", ps->num);
  17. }
  18. int main()
  19. {
  20. print1(s);  //传结构体
  21. print2(&s); //传地址
  22. return 0;
  23. }

结论:

结构体传参的时候,最好传结构体的地址。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/784063
推荐阅读
  

闽ICP备14008679号