一、C语言入门
1、软件安装
这里使用的是Visual C++2010
下载安装即可,建议不勾选任何东西,轻量运行
2、编写hello world
#include <stdio.h>
void main() {
printf("hello world");
getchar();
}
3、运行过程
- 编码:首先编写文件代码,也就是hello.c源文件
- 编译:将hello.c文件翻译成目标文件(hello.obj),由cl.exe完成这个操作
- 链接:将目标文件(hello.obj)和库文件(系统提供的)进行链接生成可执行文件(hello.exe),由link.exe完成
- 运行:执行.exe文件,我们写好的程序就运行起来了
注意:①cl.exe和link.exe是在我们软件安装目录下的VS/bin目录下的
? ②我们的库文件它是由C程序提供的
? ③修改过的程序要重新编译内容才会发生改变
? ④编译到连接的过程中只要发生错误都会生成失败
文件存放的位置:
- E:\Work\VS2010\MyProject01\Debug下有一个MyProject01.exe文件
- E:\Work\VS2010\MyProject01\MyProject01下有一个hello.c文件,这个就是我们的源文件
- E:\Work\VS2010\MyProject01\MyProject01\Debug下有一个hello.obj是我们编译过的文件
4、换行符的使用
- /t 制表符
- /n 换行符
- \\ 一个\
- \" 一个"
- \’ 一个’
- \r 回车,将当前位置移到本行开头
代码实现
#include <stdio.h>
void main() {
printf("小智\r很帅\n");
printf("姓名\t年龄\t籍贯\t住址\njohn\t12\t河北\t北京");
getchar();
}
5、注释
c语言有两种注释方式:
- 单行注释 //
- 多行注释 /**/
6、标准库的基本使用
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
void main() {
double res = pow(2.0,3.0);
printf("res=%2f",res);
system("peuse");
}
二、变量
变量时程序的基本组成单位,它想相当于是内存中一个数据存储空间的表示
1、快速入门
#include <stdio.h>
void main() {
int a = 1;
double b = 1.1;
char c = 'A';
char name[] = "小智哥";
printf("num=%d source=%f.2f gender=%c name=%s", a, b, c, name);
getchar();
}
2.1 概念
变量相当于内存中一个数据存储空间的表示,你可以把变量看做是一个门牌号,通过门牌号找到我们的房间,而通过变量名可以访问到变量(值)
变量可以在声明的时候赋值,也可以先声明,后赋值,和java的一致
2、变量的数据类型
注意:①在C语言中没有字符串类型,使用字符数组表示字符串
? ②在不同系统上,部分数据类型字节长度不一样,int2个字节或者4个字节
2.1 整型类型
C语言的整数类型就是用于存放整数值的,比如12 , 30, 3456等等
整数使用的细节
-
各种类型的存储大小与操作系统、系统位数和编译器有关,目前通用的以64位的为主 -
在实际工作中,c程序通常运行在linux/unix操作系统下.二级考试,使用windows -
C 语言的整型类型,分为有符号signed 和无符号unsigned 两种,默认是signed -
C 程序中整型常声明为int型,除非不足以表示大数,才使用long或者long long #include <stdio.h>
void main() {
long num1 = 12147483647;
long long num2 = 12147483647;
printf("%lld", num2);
printf("\nlength=%d", sizeof(int));
getchar();
}
-
bit(位): 计算机中的最小存储单位。byte(字节):计 算机中基本存储单元。 1byte = 8bit [二进制再详细说,简单举例一个short3 和int 3 ] 示意图: short 3 在内存中占有2字节 int3 在内存中占有 4个字节
2.2 浮点类型
C语言的浮点类型可以表示一一个小数,比如123.4,7.8,0.12等等
注意:①关于浮点数在机器中存放形式的简单说明,浮点数=符号位+指数位+尾数位,浮点数是近视值
? ②尾数部分可能丢失,造成精度损失。
浮点型使用细节
-
浮点型常量默认为double型,声明float 型常量时,须后加‘f’ 或‘F’。 -
浮点型常量有两种表示形式 十进制数形式:如: 5.12 512.0f .512 (必须有小数点) 科学计数法形式:如: 5.12e2 、5. 12E-2 -
通常情况下,应该使用double型,因为它比float型更精确。 -
printf(“d1=%f”, d1);//在输出时,默认保留小数点6位
代码实现
#include <stdio.h>
void main()
{
float d1 = 1.18923432;
float d2 = 1.1f;
double d3 = 1.3;
double d4 = 5.12;
double d5 = .512;
double d6 = 5.12e2;
double d7 = 5.12e-2;
printf("d1=%.15f d2=%f d3=%f d4=%f d5=%f d6=%f d7=%f", d1, d2, d3, d4, d5, d6, d7);
getchar();
}
2.3 字符类型
字符类型可以表示单个字符,字符类型是char,char 是1个字节(可以存字母或者数字),多个字符称为字符串,在C语言中使用char数组表示,数组不是基本数据类型,而是构造类型[关于数组我们后面详细讲解.]
字符类型使用细节
- 字符常量是用单引号(")括起来的单个字符。例如: charcl = ‘a’; charc3= ‘9’;
- C中还允许使用转义字符‘\’ 来将其后的字符转变为特殊字符型常量。例如: charc3= ‘\n’ ; // "\n’表示换行符
- 在C中,char的本质是- 一个整数,在输出时,是ASCII码对应的字符。
- 可以直接给char赋一个整数,然后输出时,会按照对应的ASCII字符输出[97]
- char类型是可以进行运算的,相当于-一个整数,因为它都对应有Unicode码.
代码演示
#include <stdio.h>
void main()
{
char c1 = 'a';
char c2 = 97;
int sum = c1 + 10;
printf("c1=%c c2=%c sum = %d", c1, c2, sum);
getchar();
}
结果显示
2.4 布尔类型
1)C语言标准(C89)没有定义布尔类型,所以C语言判断真假时以0为假,非0为真[案例]
2)但这种做法不直观,所以我们可以借助C语言的宏定义。
-
C语言标准(C99)提供了_ Bool 型,Bool仍是整数类型,但与- -般整型不同的是, Bool变量只能赋值为0或1,非0的值都会被存储为1,C99 还提供了一个头文件<stdbool.h> 定义了bool 代表_ Bool, true 代表1,false 代表0。只要导入stdbool.h ,就能方便的操作布尔类型了,比如bool flag = false;[了解] ?条件控制语句; if ?循环控制语句; while …
代码演示
#include <stdio.h>
#define BOOL int
#define TURE 1
#define FALSE 0
void main() {
int isPass = -1;
BOOL isOk = TURE;
if (isPass) {
printf("通过考试");
}
if (isPass) {
printf("ok");
}
int i = 1;
int sum = i + TURE + FALSE;
printf("sum=%d", sum);
getchar();
}
2.5 基本数据类型转换
自动类型转换
1)有多种类型的数据混合运算时,系统首先自动将所有数据转换成精度最大的那种数据类型,然后再进行计算(如int型和short型运算时,先把short转成int型后再进行运算)。
2)若两种类型的字节数不同,转换成字节数大的类型,若两种类型的字节数相同,且一种有符号,-种无符号,则转换成无符号类型
3)在赋值运算中,赋值号两边量的数据类型不同时,赋值号右边的类型将转换为左边的类型,如果右边变量的数据类型长度比左边长时,将丢失一部分数据, 这样会降低精度,丢失的部分按四舍五入向前舍入
代码演示
#include <stdio.h>
void main() {
char c1 = 'a';
int num1 = c1;
double d1 = num1;
float f1 = 2.2222f;
double d2 = 4.2342342322;
f1 = d2;
printf("d1 = %.2f f1 = %f", d1, d2);
getchar();
}
强制类型转换
将精度高的数据类型转换为精度小的数据类型。使用时要加上强制转换符(),但可能造成精度降低或溢出,格外要注意。
?强制类型转换一般格式如下:
? (类型名)表达式
? 什么是表达式:任何有 值都可以称为表达式,比如1+2, int num=2
? 这种强制类型转换操作并不改变操作数本身
要注意的是:不加强制转换符的是四舍五入,加了强转符就是直接截断小数点后面的
代码演示
#include <stdio.h>
void main() {
double d1 = 2.34343;
int num = (int)d1;
int num2 = (int)(3.5 * 20 + 6 * 1.5);
int num3 = (int)3.5 * 20 + 6 * 1.5;
printf("num=%d d1=%f num2=%d num3 = % d", num, d1, num2, num3);
getchar();
}
3、指针入门
简单的来说,指正表示一个地址(存放的就是一个地址)
写法:int* ptr或者int *ptr都可以
注意:指针的类型一定要和数据类型是一致的,int类型的指针只能存放int类型的地址
代码演示
#include <stdio.h>
void main() {
int num = 1;
int* ptr = #
printf("num的值=%d num地址=%p", num, ptr);
printf("\nptr的地址是%p ptr存放的值为%p ptr指向的值=%d", &ptr, ptr, *ptr);
*ptr = 99;
printf("\nptr指向的值=%d", *ptr);
float a = 22;
float* ptr2 = &a;
double sum = *ptr + *ptr2;
printf("\nsum=%.2f", sum);
getchar();
}
4、值传递和地址传递
C 语言传递参数(或者赋值)可以是值传递(pass by value), 也可以传递指针(a pointer passedbyvalue),传递指针也叫地址传递。
- 默认传递值的类型:基本数据类型(整型类型、小数类型,字符类型),结构体,共用体。
- 默认 传递地址的类似:指针、数组
值传递和地址传递使用特点:
1)值传递:将变量指向的存储内容,在传递/赋值时,拷贝- -份给接收变量.
2)地址传递也叫指针传递:如果是指针,就将指针变量存储的地址,传递给接收变量,如果是数组,就将数组的首地址传递给接收变量。
代码演示
#include <stdio.h>
void main() {
int num = 100;
int* p = #
int* p2 = p;
*p2 = 66;
printf("num=%d", num);
getchar();
}
三、常量
3.1 基本介绍
1)常量是固定值, 在程序执行期间不能改变。这些固定的值,又叫做字面量。
2)常 量可以是任何的基本数据类型,比如整数常量、浮点常量、字符常量,或字符串字面值,也有枚举常量。
3)常量的值在定义后不能进行修改.
3.2 经常使用的常量
1 整数常量
1)整数常量可以是十进制、八进制或十六进制的常量。前缀指定基数: 0x或0X表示十六进制,0表示八进制,不带前缀则默认表示十进制。整数常量也可以带一个后缀,后缀是U和L的组合,∪表示无符号整数( unsigned),L表示长整数(long)。后缀可以是大写,也可以是小写,U和L的顺序任意
2)整数常量举例
int n1 = 0213;
int n2 = 0x4b;
2 浮点常量
char c1 = 'a';
char c2 = '\t';
3 字符常量
char c1 = 'a';
char c2 = '\t';
4 字符串常量
char str1[20] = "我是靓仔";
char str2[30] = "春暖花开,世界和平";
3.3 常量的定义
1 定义常量的两种方式
① 使用#define预处理器
② 使用const关键字
2 #define预处理器
形式:#define 常量名 常量值
代码演示
#include <stdio.h>
#define PI 3.14
void main() {
double area;
double r = 1.2;
area = PI * r * r;
printf("面积:%.2f", area);
getchar();
}
3 const关键字
#include <stdio.h>
const double PI = 3.14;
void main() {
double area;
double r = 1.2;
area = PI * r * r;
printf("面积:%.2f", area);
getchar();
}
4 const和#define的区别
- const 定义的常量时,带类型,define 不带类型
- const是在编译、运行的时候起作用,而define是在编译的预处理阶段起作用
- define 只是简单的替换,没有类型检查。简单的字符串替换会导致边界效应 [案例演示].
- const常量可以进行调试的,define是不能进行调试的,主要是预编译阶段就已经替换掉了,调试的时候就没它了
- const 不能重定义,不可以定义两个一样的,而define通过undef取消某个符号的定义,再重新定义[案例]
- define 可以配合#ifdef、#ifndef、 #endif 来使用,可 以让代码更加灵活,比如我们可以通过#define来启动或者关闭调试信息。[案例]
案例
#define A 1;
#define B A + 3;
#define B2 (A + 3)
#define C A/B*3;
#define C2 A/B*3
#include <stdio.h>
#define DEBUG
void main() {
#ifdef DEBUG
printf("ok, 调试信息");
#endif
#ifdef DEBUG
printf("hello,另外的信息");
#endif
#undef DEBUG
#define DEBUG 123
printf("DEBUG=%d", DEBUG);
getchar();
}
四、运算符
1 算数运算符
代码演示
#include <stdio.h>
void main() {
int i = 10;
int j = 3;
int sum = i / j;
double i2 = 10;
int j2 = 4;
double sum2 = i2 / j2;
printf("sum=%d,sum2=%.2f", sum, sum2);
getchar();
}
注意事项:
对于除号“1”,它的整数除和小数除是有区别的:整数之间做除法时,只保留整数部分而舍弃小数部分
? 例如: intx=10/3,结果是3
标识符命名规范
1)程序中不得出现仅靠大小写区分的相似的标识符,intx,X;变量x与X容易混淆
2)所有宏定义、 枚举常数、常量(只读变量)全用大写字母命名,用下划线分隔单词
? 比如:const double TAX RATE = 0.08; //TAX RATE只读变量
? #define FILE_ PATH “/usr/tmp”
3)定义变量别忘了初始化。定义变量时编译器并不一定清空了这块内存,它的值可能是无效的数据,运行程序,会异常退出.
4)变量名、 函数名:多单词组成时,第-一个单词 首字母小写,第二个单词开始每个单词首字母大写: xxxYyyZzz [驼峰法,小驼峰,比如short stuAge = 20;]
? 比如:tankShotGame tankShotGame
关键字
键盘输入语句
scanf,类似于java的scanner,控制台输入
五、二进制和位运算符
进制
对于整数,有四种表示方式:
-
二进制: 0,1,满2进1,C语言中没有二进制常数的表示方法。 -
2)十进制: 0-9 ,满10进1。 -
3)八进制: 0-7,满8进1.以数字0开头表示。 -
十六进制: 0-9及A-F,满16进1.以0x或0X开头表示。此处的A-F不区分大小写。[A->10B->11C->12D->13E->14 F->15 ] 如: 0x21AF +1= 0X21B0
?举例说明:
int num2 = 210;
int num3 = 01010;
int num4 = 0x1010;
进制的图示
进制转换
1 其他进制转换成十进制
①二进制转十进制
规则:从最低位开始,将每个位上的数提取出来,乘以2的(位数-1)次方,然后求和。
案例:将二进制1011转成十进制的数
1011 = 1 * 2^0 + 1 * 2^1 + 0 * 1^2 + 1 * 2^3 = 1 + 2 + 0 + 8 = 11
②八进制转十进制
规则:从最低位开始,将每个位上的数提取出来,乘以8的(位数-1)次方,然后求和。
案例:将0123转成十进制的数
0123 = 3 * 8^0 + 2 * 8^1 + 1 * 8^2 = 3 + 16 + 64 = 83
③十六进制转十进制
规则:从最低位开始,将每个位上的数提取出来,乘以16的(位数-1)次方,然后求和
案例:将0X34A转成十进制的数
0X34A = 10 * 16^0 + 4 * 16^1 + 3 * 16^2 = 10 + 64 + 768 = 842
课后练习
110001100转成干进制
2^2 + 2^3 + 2^7 + 2^8 = 4 + 8 + 128 + 256 = 396
02456转成十进制
6 * 8^0 + 5 * 8^1 + 4 * 8^2 + 2 * 8^3 = 6 + 40 + 256 + 1024 = 1326
0xA45转成十进制
5 * 16^0 + 4 * 16^1 + 10 * 16^2 = 5 + 64 + 2560 = 2629
2 十进制转其他进制
①十进制转二进制
规则:将该数不断除以2,直到商为0为止,然后将每步得到的余数倒过来,就是对应的二进制。
案例:将56转成二进制
②十进制转八进制
规则:将该数不断除以8,直到商为0为止,然后将每步得到的余数倒过来,就是对应的八进制。
案例:请将156转成八进制
234
③十进制转十六进制
规则:将该数不断除以16,直到南为0为止,然后将每步得到的余数倒过来,就是对应的十六进制。
案例:请将356转成十六进制
164
课堂练习
123 -> 0111 1011
678 -> 01246
8912 -> 0X22D0
3 二进制转八进制和十六进制
①二转八
规则:从低位开始,将=二进制数每三位一组, 转成对应的八进制数即可。
案例:请将11010101转成八进制
11 010 101 -> 0325
②二转十六
规则:低位开始,将二进制数每四位一组, 转成对应的十六进制数即可。
案例:请将11010101转成十六进制
1101 0101 -> 0XD5
练习
11 100 101 -> 0345
11 1001 0110 -> 0X396
4 八进制和十六进制转二进制
①八转二
规则:将八进制数每1位,转成对应的一一个3位的二进制数即可。
案例:请将0237转成二进制
0237 -> 01 011 111
②十六转二
规则:将十六进制数每1位,转成对应的4位的-一个二进制数即可。
案例:请将0x23B转成二进制
0x23B -> 0010 0011 1011
练习
01230 -> 1 010 011 000
0XAB29 -> 1010 1011 0010 1001
原码、反码、补码
必须要记住,很重要
这里演示用四个字节
正数三码合一
2
原码: 00000000 00000000 00000000 00000010
反码: 00000000 00000000 00000000 00000010
补码: 00000000 00000000 00000000 00000010
-2
原码: 10000000 00000000 00000000 00000010
反码: 11111111 11111111 11111111 11111101
补码: 11111111 11111111 11111111 11111110
位运算符
示例
~2
~2
2的补码: 00000000 00000000 00000000 00000010
补码取反: 11111111 11111111 11111111 11111101
然后将上面取反的补码转成原码就是我们的结果了
最高符号位为1,它是一个负数,所以要按照负数的规则来转
思路:先转成反码,反码再转成原码
转反码 = 补码 - 1
-> 11111111 11111111 11111111 11111100
反码 -> 原码
-> 10000000 00000000 00000000 00000011
所以~2的结果是 -3
~-5
~-5
-5的原码 -> 10000000 00000000 00000000 00000101
反码 -> 01111111 11111111 11111111 11111010
补码 -> 01111111 11111111 11111111 11111011
取反 -> 10000000 00000000 00000000 00000100
反码 -> 01111111 11111111 11111111 11111011
原码 -> 00000000 00000000 00000000 00000100
结果是 4
2&-3
2&-3
-3的原码: 10000000 00000000 00000000 00000011
反码: 11111111 11111111 11111111 11111100
补码: 11111111 11111111 11111111 11111101
2的补码: 00000000 00000000 00000000 00000010
& : 00000000 00000000 00000000 00000000
结果为0
2|3
2|3
2的补码: 00000000 00000000 00000000 00000010
3的补码: 00000000 00000000 00000000 00000011
| : 00000000 00000000 00000000 00000011
结果为3
2^3
2^3
2的补码: 00000000 00000000 00000000 00000010
3的补码: 00000000 00000000 00000000 00000011
^ : 00000000 00000000 00000000 00000001
结果为1
4&-5
-5的原码: 10000000 00000000 00000000 00000101
反码: 11111111 11111111 11111111 11111010
补码: 11111111 11111111 11111111 11111011
4的补码:00000000 00000000 00000000 00000100
& : 00000000 00000000 00000000 00000000
结果为0
左移和右移
正数的右移就是除以2,又移几位就是除于几个2,左移就是乘于2,左移几位就是乘于几个2
负数的要通过推
举例子:
2的补码: 00000000 00000000 00000000 00000010
2>>1: 00000000 00000000 00000000 00000001
就是整体向右移动一位,符号位为0,所以用符号位补全溢出的高位
-1 >> 2
-1的原码: 10000000 00000000 00000000 00000001
反码: 11111111 11111111 11111111 11111110
补码: 11111111 11111111 11111111 11111111
-1>>2: 11111111 11111111 11111111 11111111
反码: 11111111 11111111 11111111 11111110
原码: 10000000 00000000 00000000 00000001
结果是-1
-1 << 2
-1的补码: 11111111 11111111 11111111 11111111
-1<<2: 11111111 11111111 11111111 11111100
反码: 11111111 11111111 11111111 11111011
原码: 10000000 00000000 00000000 00000100
结果为-4
六、程序流程控制
switch要注意的细节
goto
类似于水门的飞雷神,通过标志然后进行传送
#include <stdio.h>
void main() {
printf("start\n");
goto lablel;
printf("ok l\n");
printf("ok2\n");
lablel:
printf("ok3\n");
printf("ok4\n");
getchar();
}
练习
水仙花数
#include <stdio.h>
void main() {
int num = 154;
int num1 = num / 100;
int num2 = num % 100 / 10;
int num3 = num % 10;
if (num == num1 * num1 * num1 + num2 * num2 * num2 + num3 * num3 * num3) {
printf("%d是水仙花数",num);
}
else {
printf("%d不是水仙花数", num);
}
getchar();
}
求天数
#include <stdio.h>
void main () {
int year = 2021;
int month = 2;
switch (month)
{
case 1:
case 3:
case 5:
case 7:
case 8:
case 10:
case 12:
printf("%d年的%d月份是%d天", year, month, 31);
break;
case 2:
if (year % 4 == 0 && year / 100 != 0) {
printf("%d年的%d月份是%d天", year, month, 29);
}
printf("%d年的%d月份是%d天", year, month, 28);
break;
default:
printf("%d年的%d月份是%d天", year, month, 30);
break;
}
getchar();
}
判断星期
#include <stdio.h>
void main() {
int week = 1;
switch (week)
{
case 1:
case 2:
case 3:
printf("AAA");
break;
case 4:
case 5:
printf("BBB");
break;
case 6:
case 7:
printf("CCC");
break;
}
getchar();
}
输出a-z和A-Z
#include <stdio.h>
void main() {
for (char c1 = 'a'; c1 <= 'z'; c1++)
{
printf("%c\n", c1);
}
for (char c2 = 'A'; c2 <= 'Z'; c2++)
{
printf("%c\n", c2);
}
getchar();
}
七、枚举
-
枚举是C语言中的一种构造数据类型,它可以让数据更简洁,更易读,对于只有几个有限的特定数据,可以 使用枚举. -
枚举对应英文(enumeration,简写enum) -
枚举是一组常量的集合,包含- -组有限的特定的数据 -
枚举语法定义格式为 enum
枚举名{枚举元素 1,枚举元素...;.
快速入门
#include <stdio.h>
enum DAY
{
MON = 1,TUE = 2, WED = 3, THU = 4, FRI = 5, SAT = 6,SUN = 7
};
void main() {
enum DAY day = WED;
printf("%d", day);
getchar();
}
遍历枚举
for循环
#include <stdio.h>
enum DAY
{
MON , TUE, WED, THU, FRI, SAT, SUN
}day;
void main() {
for (day = MON; day <= SUN; day++) {
printf("%d\n", day);
}
getchar();
}
switch循环
#include <stdio.h>
enum SEASONS
{
SPRING=1, SUNMMER, AUTUMN, WINTER
}season;
void main() {
printf("请输入你喜欢的季节:(1.spring 2.summer 3.autumn 4.winter):");
scanf_s("%d", &season);
switch (season)
{
case SPRING:
printf("你喜欢的是春天");
break;
case SUNMMER:
printf("你喜欢的是夏天");
break;
case AUTUMN:
printf("你喜欢的是秋天");
break;
case WINTER:
printf("你喜欢的是的冬天");
break;
default:
printf("没有你喜欢的天");
break;
}
getchar();
getchar();
}
枚举的注意事项和细节
-
第一个枚举成员的默认值为整型的0, 后续枚举成员的值在前一个成员上加1。 我们在这个实例中把第一一个枚 举成员的值定义为1,第二个就为2,以此类推. [看案例] -
在定义枚举类型时改变枚举的元素的值 enum DAY {
MON, TUE, WED, THU=9, FRI, SAT, SUN
} day;
所以后面数的赋值要根据前面数来定 -
枚举变量的定义也可以先定义枚举类型,再定义枚举变量 enum DAY {
MON=1, TUE, WED, THU, FRI, SAT, SUN
};
enum DAY day;
-
枚举变量的定义可以省略枚举名称,直接定义枚举变量 enum {
MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;
注意:这种定义只能用一次 -
可以将整数转换成对应的枚举值 #include <stdio.h>
int main() {
enum SEASONS { SPRING = 1, SUMMER, AUTUMN, WINTER };
enum SEASONS season;
int n = 4;
season = (enum SEASONS)n;
printf("season = %d", season);
getchar();
return 0;
}
八、函数
头文件概述
- 头文件是扩展名为.h的文件,包含了C函数声明和宏定义,被多个源文件中引用共享。有两种类型的头文件:
程序员编写的头文件和C标准库自带的头文件 - 在程序中要使用头文件, 需要使用C预处理指令#include来引用它。前面我们已经看过stdio.h 头文件,它是
C标准库自带的头文件 - #include叫做文件包含命令,用来引入对应的头文件(.h文件)。#include也是C语言预处理命令的一-种。#include
的处理过程很简单,就是将头文件的内容插入到该命令所在的位置,从而把头文件和当前源文件连接成-一个源 文件,这与复制粘贴的效果相同。但是我们不会直接在源文件中复制头文件的内容,因为这么做很容易出错, 特别在程序是由多个源文件组成的时候。 - 建议把所有的常量、宏、系统全局变量和函数原型写在头文件中,在需要的时候随时引用这些头文件
代码实现
首先我们要定义头文件,定义好头文件之后我们就编写对应函数的代码
在头文件文件夹中创建 myfun.h 文件
int sum(int a, int b);
void sayHello();
编写对应的代码
#include <stdio.h>
int sum(int a, int b) {
return a + b;
}
void sayHello() {
printf("hello");
}
测试函数
#include <stdio.h>
#include "myfun.h"
void main() {
int a = 2;
int b = 3;
int s = sum(a, b);
printf("%d\n", s);
sayHello();
getchar();
}
头文件的注意事项和细节说明
-
引用头文件相当于复制头文件的内容 -
源文件的名字可以不和头文件一样,但是为了好管理,一**-般头文件名和源文件名一样.** -
C语言中include<> 与include ""的区别 include <>:引用的是编译器的类库路径里面的头文件,用于引用系统头文件。 include"":引用的是你程序目录的相对路径中的头文件,如果在程序目录没有找到引用的头文件则到编译器的 类库路径的目录下找该头文件,用于引用用户头文件。 所以: *引用系统头文件,两种形式都会可以,include<> 效率高 *引用用户头文件,只能使用include " "
注:" "它会先去找自定义的函数,找不到自定义的就会去找系统提供的
-
一个#include 命令只能包含一个头文件,多个头文件需要多个#include 命令 -
同一个头文件如果被多次引入,多次引入的效果和一次引入的效果相同,因为头文件在代码层面有防止重复引 入的机制 -
在一个被包含的文件(.c)中又可以包含另一个文件头文件(.h) -
不管是标准头文件,还是自定义头文件,都只能包含变量和函数的声明,不能包含定义,否则在多次引入时会 引起重复定义错误(!!!
因为include是替换头文件里面的内容,如果是多次引入函数的定义的话就没事,因为它有查重的机制,如果是多次引入函数的声明的话就会出问题。
函数的调用过程
函数的调用规则(适用于java,c++,php)
-
当调用(执行)一个函数时,就会开辟一个独立的空间(栈) -
每个栈空间都是相互独立的 -
当函数执行完毕后(或者执行到return语句),会返回调用函数的位置,继续执行下面的代码 -
如果函数有返回值,则将返回值赋给接收的变量 ①如果方法返回的数据是double类型的,我们可以ruturn比他小的类型,比如int类型,它会自动类型提升到double类型 ②如果方法返回的数据是char类型的,return 一个int类型就会报错,需要进行强制类型转换才能return -
当一个函数返回后,该函数对应的栈空间也就销毁了
举例说明
函数-递归调用
我调我自己,在方法内部调用自己
#include <stdio.h>
void test(int n) {
if (n > 2) {
test(n - 1);
}
printf("n=%d\n", n);
}
void main() {
test(4);
getchar();
}
函数递归需要遵守的重要规则
- 执行一个函数时,就创建一个独立的空间(栈)
- 函数的局部变量时独立的,不会相互影响
- 递归必须有退出递归的条件,如果没有就会无限递归,造成内存溢出
- 当一个函数执行完毕,或遇到return,就会返回,谁调用那么结果就返回给谁
递归练习题
?题1: 斐波那契数 请使用递归的方式,求出斐波那契数1,1,2,3,5,8,13… 给你-一个整数n,求出它的斐波那契数是多少?
#include <stdio.h>
int fbn(int n) {
if (n == 1 | n == 2) {
return 1;
}
else {
return fbn(n - 1) + fbn(n - 2);
}
}
void main() {
int res = fbn(5);
printf("res=%d", res);
getchar();
}
?题2:求函数值 已知f(1)=3; f(n)= 2*f(n-1)+1; 请使用递归的思想编程,求出f(n)的值?
#include <stdio.h>
int f(int n) {
if (n == 1) {
return 3;
}
else {
return 2 * f(n - 1) + 1;
}
}
void main() {
int res = f(7);
printf("res=%d", res);
getchar();
}
?题3:猴子吃桃子问题 有一堆桃子,猴子第一天吃了其中的一半,并再多吃了一个!以后每天猴子都吃其中的一-半,然后再多吃一一个。 当到第十天时,想再吃时(还没吃),发现只有1个桃子了。问题:最初共多少个桃子?
#include <stdio.h>
int peach(int day) {
if (day == 10) {
return 1;
}
else {
return (peach(day + 1) + 1) * 2;
}
}
void main() {
int res = peach(1);
printf("res=%d", res);
getchar();
}
函数的注意事项和细节讨论
-
函数的形参列表可以是多个。 -
C 语言传递参数可以是值传递(pass by value),也可以传递指针(a pointer passed by value)也叫引用传递。 -
函数的命名遵循标识符命名规范,首字母不能是数字,可以采用驼峰法或者下划线法,比如getMax() get_ max(). -
函数中的变量是局部的, 函数外不生效 -
基本数据类型默认是值传递的,即进行值拷贝。在函数内修改,不会影响到原来的值。 -
如果希望函数内的变量能修改函数外的变量,可以传入变量的地址&,函数内以指针的方式操作变量。从效果 上看类似引用(即传递指针) [ 案例演示:] #include <stdio.h>
void test(int* p) {
(*p)++;
}
void main() {
int p = 3;
test(&p);
printf("p=%d", p);
getchar();
}
-
C语言不支持函数重载。 -
C语言支持可变参数函数1/知道即可[案例演示] #include <stdio.h>
#include <stdarg.h>
void test(int* p) {
(*p)++;
}
int fun(int num, ...) {
int i, totalSum = 0;
int val = 0;
va_list v1;
va_start(v1, num);
printf("*v=%d\n", v1);
for (i = 0 ; i < num; i++)
{
val = va_arg(v1, int);
printf("val=%d\n", val);
totalSum += val;
}
va_end(v1);
return totalSum;
}
void main() {
int res = fun(8, 1, 2, 3, 4, 5, 5, 6, 9);
printf("res=%d", res);
getchar();
}
练习题
请编写一个函数swap(intnl, int **n2) 可以交换nl和:n2的值
#include <stdio.h>
void swap(int *n1, int *n2) {
int temp = *n1;
*n1 = *n2;
*n2 = temp;
}
void main() {
int n1 = 2;
int n2 = 3;
swap(&n1, &n2);
printf("n1=%d, n2=%d", n1, n2);
getchar();
}
函数参数的传递方式
两种传递方式
- 值传递
- 引用传递(传递指针、地址)
值传递和引用传递的特点
如果希望函数内的变量能修改函数外的变量,可以传入变量的地址&,函数内以指针的方式操作变量(*指针)。
变量的作用域
变量作用域就是指变量的有效范围
- 函数内部声明/定义的局部变量,作用域仅限于函数内部。
- 函数的参数, 形式参数,被当作该函数内的局部变量,如果与全局变量同名它们会优先使用局部变量(编译器
使用就近原则) - 在一个代码块,比如for/if中的局部变量,那么这个变量的的作用域就在该代码块
- 4)在所有 函数外部定义的变量叫全局变量,作用域在整个程序有效
初始化局部变量和全局变量
-
局部变量,系统不会对其默认初始化,必须对局部变量初始化后才能使用,否则,程序运行后可能会异常退出. -
全局变量,系统会自动对其初始化,如下所示 -
正确地初始化变量是一个良好的编程习惯,否则有时候程序可能会产生意想不到的结果,因为未初始化的变量 会导致一些在内存位置中已经可用的垃圾值
作用域的注意事项和细节
-
全局变量(Global Variable)保存在内存的全局存储区中,占用静态的存储单元,它的作用域默认是整个程序,也 就是所有的代码文件,包括源文件(.c 文件)和头文件(.h 文件)。[c 程序内存布局图!!!] -
局部变量(IocalVariable)保存在栈中,函数被调用时才动态地为变量分配存储单元,它的作用域仅限于函数内 部。[内存布局分析] -
C语言规定,只能从小的作用域向大的作用域中去寻找变量,而不能反过来,使用更小的作用域中的变量 -
在同一个作用域,变量名不能重复,在不同的作用域,变量名可以重复,使用时编译器采用就近原则. -
由{ }包围的代码块也拥有独立的作用域
练习
思考:下面的代码输 出什么内容?
#include <stdio.h>
double price = 200.0;
void test01() {
printf("%.2f\n", price);
}
void test02() {
double price = 250.0;
printf("%.2f\n", price);
}
void main() {
printf("main price = %.2f\n", price);
test01();
test02();
test01();
getchar();
}
思考:下面的代码输 出什么内容?
#include <stdio.h>
int n = 10;
void fun1() {
int n = 20;
printf("fun1 n:%d\n", n);
}
void fun2(int n) {
printf("fun2 n:%d\n", n);
}
void fun3() {
printf("fun3 n:%d\n", n);
}
void main() {
int n = 30;
fun1();
fun2(n);
fun3();
getchar();
}
static关键字
c语言的static和java的是相反的,它和java中的private关键字的作用一样
局部变量使用static修饰
- 局部变量被static 修饰后,我们称为静态局部变量
- 对应静态局部变量在声明时未赋初值,编译器也会把它初始化为0。
- 静态局部变量存储于进程的静态存储区(全局性质),只会被初始一-次,即使函数返回,它的值也会保持不变
代码演示
void main() {
static int n;
printf("n=%d", n);
getchar();
}
#include <stdio.h>
void fun(void) {
int n = 10;
printf("n=%d\n", n);
n++;
printf("n++=%d\n", n);
}
void fun_static(void) {
static int n = 10;
printf("\nstatic n=%d\n", n);
n++;
printf("\nn++=%d\n", n);
}
void main() {
fun();
printf("-----------\n");
fun_static();
printf("-----------\n");
fun();
printf("-----------\n");
fun_static();
getchar();
}
全局变量使用static修饰
- 普通全局变量对整个工程可见,其他文件可以使用exterm外部声明后直接使用。也就是说其他文件不能再定义
一个 与其相同名字的变量了( 否则编译器会认为它们是同一个变量),静态全局变量仅对当前文件可见,其他 文件不可访问,其他文件可以定义与其同名的变量,两者互不影响[案例]
file01
int n = 10;
static int n2 = 20;
file02
extern int n2;
void main() {
printf("%d", n2);
getchar();
}
- 定义不需要与其他文件共享的全局变量时,加上static关键字能够有效地降低程序模块之间的耦合,避免不同
文件同名变量的冲突,且不会误使用
函数用static修饰
和修饰全局变量一个作用,都是只能在本文件中使用,其他文件不能使用
-
函数的使用方式与全局变量类似,在函数的返回类型前加上static,就是静态函数 -
非静态函数可以在另一个文件中通过extern 引用[ 案例] -
静态函数只能在声明它的文件中可见,其他文件不能引用该函数[案例] -
不同的文件 可以使用相同名字的静态函数,互不影响[案例] 如果引用了其他文件的全局变量,那么就不能定义和这个相同的全局变量了
代码演示
file03
#include <stdio.h>
void fun1() {
printf("我是靓仔智");
}
static void fun2() {
printf("我是傻逼智");
}
file04
#include <stdio.h>
extern void fun1();
void main() {
fun2();
getchar();
}
常用的系统函数
字符串常用的系统函数
- 得到字符串的长度
size_ t strlen( const char *str) 计算字符串str 的长度,直到空结束字符,但不包括空结束字符。 - 拷贝字符串
char *strcpy(char *dest, const char *src) 把SrC所指向的字符串复制到dest。 - 连接字符串(类似java的append)
char *strcat( char * dest, const char *src) 把src所指向的字符串追加到dest所指向的字符串的结尾。
代码演示
#include <stdio.h>
#include <string.h>
void main() {
char str[30] = "abcde";
char a[20] = "asd";
char b[30] = "世界上最靓仔的是";
int length = strlen(str);
strcpy(a, "cccc");
strcat(b, "靓仔智");
printf("length=%d\n", length);
printf("a=%s\n", a);
printf("b=%s\n", b);
getchar();
}
时间和日期相关函数
-
获取当前时间 char *ctime( const time t *timer) 返回一个表示当地时间的字符串,当地时间是基于参数timer。 void main() {
time_t curtime;
time(&curtime);
printf("当前时间:%s", ctime(&curtime));
getchar();
}
-
编写一段代码来统计函数test执行的时间 double difftime(time_ t timel, time_ _t time2) 返回timel 和time2之间相差的秒数(timel-time2)。 . #include <stdio.h>
#include <time.h>
void test() {
printf("test函数开始执行\n");
int sum = 0;
for (int i = 0; i < 66666; i++)
{
sum = 0;
for (int j = 0; j < 10; j++)
{
sum += j;
printf("%d", i);
}
}
}
void main() {
time_t start_t, end_t;
double diff_t;
printf("程序启动\n");
time(&start_t);
test();
time(&end_t);
diff_t = difftime(end_t, start_t);
printf("执行test函数耗用了%.2f秒", diff_t);
getchar();
}
数学相关函数
math.h头文件定义了各种数学函数和一个宏。在这个库中所有可用的功能都带有-一个double类型的参数,且都返 回double类型的结果 举例说明:
- double exp(double x)
返回e的x次幂的值。 - double log(double x)
返回x的自然对数(基数为e的对数) - double pow(double x, double y)
返回x的y次幂。 - double sqrt(double x)
返回x的平方根。 - double fabs(double x)
返回x的绝对值。
代码实现
#include <stdio.h>
#include <math.h>
void main() {
double res = fabs(-3);
double res2 = sqrt(2);
double res3 = exp(0);
double res4 = log(1);
double res5 = pow(4, 5);
printf("res=%.2f res2=%.2f res3=%.2f res4=%.2f res5=%.2f", res, res2, res3, res4, res5);
getchar();
}
基本数据类型和字符串类型的转换
在程序开发中,我们经常需要将基本数据类型转成字符串类型(即char数组)。或者将字符串类型转成基本数 据类型。
sprintf 函数的用法
- sprintf和平时我们常用的printf函数的功能很相似。sprintf函数打印到字符串中,而printf函数打印输出到屏幕
上。sprintf函数在我们完成其他数据类型转换成字符串类型的操作中应用广泛 - 该函数包含在stdio.h的头文件中
基本数据类型转字符串类型
#include <stdio.h>
void main() {
char str1[30];
char str2[30];
char str3[30];
int a = 20;
double b = 30.0;
sprintf(str1, "%d", a);
sprintf(str2, "%.2f", b);
sprintf(str3, "%8.2f", b);
printf("str1=%s str2=%s str3=%s", str1, str2, str3);
getchar();
}
字符串类型转基本数据类型
语法:通过<stdlib.h>的函数调用atoi .atof即可
注意事项:
- 在将 char数组类型转成基本数据类型时,要确保能够转成有效的数据,比如我们可以把"123",转成一一个
整数,但是不能把"hello"转成一-个整数 - 如果格式不正确,会默认转成0或者0.0
代码实现
#include <stdio.h>
#include <stdlib.h>
void main() {
char str1[20] = "23423";
char str2[20] = "343.3";
char str3[20] = "ab";
char str4[20] = "1111";
int a = atoi(str1);
double b = atof(str2);
char c = str3[0];
long d = atol(str4);
printf("a=%d b=%.2f c=%c d=%d", a, b, c, d);
getchar();
}
九、预处理命令
1 基本介绍
预处理是在编译之前执行的操作
- 使用库函数之前,应该用#include引入对应的头文件。这种以#号开头的命令称为预处理命令。
- 这些在编译之前对源文件进行简单加工的过程,就称为预处理(即预先处理、提前处理)
- 预处理 主要是处理以#开头的命令,例如#include <stdio.h> 等。预处理命令要放在所有函数之外,而且-般都放
在源文件的前面 - 预处理是C语言的一个重要功能,由预处理程序完成。当对一个源文件进行编译时,系统将自动调用预处理程
序对源程序中的预处理部分作处理,处理完毕自动进入对源程序的编译 - C语言提供了多种预处理功能,如宏定义、文件包含、条件编译等,合理地使用它们会使编写的程序便于阅读、
修改、移植和调试,也有利于模块化程序设计
2 快速入门
具体要求
开发-一个C语言程序,让它暂停5秒以后再输出内容"春暖花开,世界和平",并且要求跨平台,在Windows和 Linux、下 都能运行,如何处理
提示
- Windows 平台下的暂停函数的原型是void Sleep(DWORD dwMilliseconds), 参数的单位是“毫秒”,位
于<windows.h>头文件。 - Linux 平台下暂停函数的原型是unsigned int sleep (unsigned int seconds),参数的单位是“秒”,位于<unistd.h>
头文件 - #if、 #elif、 #endif 就是预处理命令,它们都是在编译之前由预处理程序来执行的。
代码实现
#include <stdio.h>
#if _WIN32
#include <Windows.h>
#elif _linux_
#include <unistd.h>
#endif
void main() {
#if _WIN32
Sleep(5000);
#elif _linux_
Sleep(5);
#endif
puts("春暖花开,世界和平");
getchar();
}
3 C语言宏定义
- #define 叫做宏定义命令,它也是C语言预处理命令的- -种。所谓宏定义,就是用一个标识符来表示一个字符
串,如果在后面的代码中出现了该标识符,那么就全部替换成指定的字符串
- 宏定 义我们在讲解常量时,做过介绍,这里我们再系统的讲解一下.
#defineN 100就是宏定义,N为宏名,100 是宏的内容(宏所表示的字符串)。在预处理阶段,对程序中所有出现 的“宏名”,预处理器都会用宏定义中的字符串去代换,这称为**“宏替换”或“宏展开”**。 宏定义是由源程序中的宏定义命令#define完成的,宏替换是由预处理程序完成的
4 宏定义的形式
- #表示这是一条预处理命令,所有的预处理命令都以#开头。宏名是标识符的一种,命名规则和变量相同。字.
符串可以是数字、表达式、if语句、函数等 - 这里所说的字符串是- -般意义.上的字符序列,不要和C语言中的字符串等同,它不需要双引号
- 程序中反复使用的表达式就可以使用宏定义
代码演示
#include <stdio.h>
#define M (n*n+3*n)
void main() {
int sum, n;
printf("请输入一个数字");
scanf("%d", &n);
sum = 3 * M + 4 * M + 5 * M;
printf("sum=%d\n", sum);
getchar();
getchar();
}
宏定义注意事项和细节
-
宏定义是用宏名来表示-一个字符串,在宏展开时又以该字符串取代宏名,这只是一种简单的替换。字符串中可 以含任何字符,它可以是常数、表达式、if语句、函数等,预处理程序对它不作任何检查,如有错误,只能在 编译已被宏展开后的源程序时发现。 -
宏定义不是说明或语句,在行末不必加分号,如加上分号则连分号也一起替换 -
宏定义必须写在函数之外,其作用域为宏定义命令起到源程序结束。如要终止其作用域可使用#undef命令[案 例]
#define PI 3.14159
int main(){
printf("PI=%f", PI); .
return 0;
}
#undefPI
void func({
printf("PI=%f", PD);
- 代码中的宏名如果被引号包围,那么预处理程序不对其作宏代替[案例]
#include <stdio.h>
#define OK 100
int main(){
printf("OK\n");
return 0;
}
- 宏定义允许嵌套,在宏定义的字符串中可以使用已经定义的宏名,在宏展开时由预处理程序层层代换[ 案例]
#define PI 3.1415926
#define S PI*y*y
print("%f", S);
print("%f", 3.1415926*y*y);
-
习惯上宏名用大写字母表示,以便于与变量区别。但也允许用小写字母 -
可用宏定义表示数据类型,使书写方便[案例] #define UINT unsigned int
void main() {
UINT a,b;
}
-
宏定义表示数据类型和用typedef定义数据说明符的区别:宏定义只是简单的字符串替换,由预处理器来处理; 而typedef是在编译阶段由编译器处理的,它并不是简单的字符串替换,而给原有的数据类型起一-个新的名字, 将它作为–种新的数据类型。
5 带参数的宏定义
- C语言允许宏带有参数。在宏定义中的参数称为“形式参数”,在宏调用中的参数称为“实际参数”,这点和
函数有些类似 - 对带 参数的宏,在展开过程中不仅要进行字符串替换,还要用实参去替换形参
- 带参宏定义的一般形式为**#define 宏名(形参列表)字符串**,在字符串中可以含有各个形参
- 带参宏调用的一般形式为:宏名(实参列表); [案例+说明]
代码演示
#include <stdio.h>
#define MAX(a, b) (a > b) ? a : b
void main() {
int x, y, max;
printf("input two numbers\n");
scanf("%d%d", &x, &y);
max = MAX(x, y);
printf("max=%d", max);
getchar();
getchar();
}
带参宏定义的注意事项和细节
-
带参宏定义中,形参之间可以出现空格,但是宏名和形参列表之间不能有空格出现 #define MAX(a,b) (a>b)?a:b
如果写成了#define MAX (a, b) (a>b)?a:b
将被认为是无参宏定义,宏名MAX代表字符串(a,b) (a>b)?a:b
而不是: MAX(a,b)代表(a>b)?a:b 了
-
在带参宏定义中,不会为形式参数分配内存,因此不必指明数据类型。而在宏调用中,实参包含了具体的数据, 要用它们去替换形参,因此实参必须要指明数据类型 -
在宏定义中,字符串内的形参通常要用括号括起来以避免出错。 #include <stdlib.h>
#define SQ(y) (y)*(y)
int main(){
int a, sq;
printf("input a number: "); .
scanf("%d", &a);
sq= SQ(a+1);
printf("sq=%d\n", sq);
system("pause");
return 0;
}
6 带参宏定义和函数的差别
- 宏展开仅仅是字符串的替换,不会对表达式进行计算;宏在编译之前就被处理掉了,它没有机会参与编译,也
不会占用内存。 - 函数是一 段可以重复使用的代码,会被编译,会给它分配内存,每次调用函数,就是执行这块内存中的代码.
- 案例说明 :要求使用函数计算平方值,使用宏计算平方值, 并总结二者的区别
#include <stdio.h>
#include <stdlib.h>
#define SQ(y) (y)*(y)
void main() {
int i = 1;
while (i <= 5) {
printf("%d^2=%d\n", i - 2, SQ(i++));
}
system("pause");
}
7 预处理命令总结
预处理指令是以#号开头的代码行,#号必须是该行除了任何空白字符外的第-一个字符。#后是指令关键字, 在关键字和#号之间允许存在任意个数的空白字符,整行语句构成了一条预处理指令,该指令将在编译器进行编 译之前对源代码做某些转换
预处理指令使用注意事项
- 预处理功能是C语言特有的功能,它是在对源程序正式编译前由预处理程序完成的,程序员在程序中用预处理
命令来调用这些功能。 - 宏定义可以带有参数,宏调用时是以实参代换形参,而不是“值传送”。
- 为了避免宏代换时发生错误,宏定义中的字符串应加括号,字符串中出现的形式参数两边也应加括号。
- 文件包含是预处理的-一个重要功能,它可用来把多个源文件连接成一- 个源文件进行编译,结果将生成-一个 目标
文件。 - 条件编译允许只编译源程序中满足条件的程序段,使生成的目标程序较短,从而减少了内存的开销并提高了程
序的效率。 - 使用预处理功能便于程序的修改、阅读、移植和调试,也便于实现模块化程序设计
十、数组
数组可以存放多个同–类型数据。数组也是一种数据类型,是构造类型。传递是以引用的方式传递(即传递的 是地址)
1 快速入门
#include <stdio.h>
void main() {
double arr[6];
double total = 0.0;
double avg = 0.0;
int i, length;
arr[0] = 3;
arr[1] = 5;
arr[2] = 1;
arr[3] = 3.4;
arr[4] = 2;
arr[5] = 50;
length = sizeof(arr) / sizeof(double);
for (i = 0; i < length; i++)
{
total += arr[i];
}
avg = total / length;
printf("total=%.2f avg=%.2f", total, avg);
getchar();
}
2 数据的定义和内存布局
①数据的定义
数据类型 数组名[数组大小]; inta[5]; //a 数组名,类型int,[5] 大小,即a数组最多存放5个int数据 赋初值a[0]= 1;a[1]= 30; …
②数据的内存分布
说明:1.数组名就代表该数组的首地址,既a[0]的地址
? 2.数组各个元素是连续分布的
? 例:int类型的数组 -> a[0] 的地址为0x1233 ,那么a[1]的就是0x1233 + int的字节数 = 0x1237, a[2]的地址0x123B,后面以此类推。。。;是什么类型的数组后面就加对应的字节数
小案例
#include <stdio.h>
void main() {
double arr[5];
int i, length;
length = sizeof(arr) / sizeof(double);
for (i = 0; i < length; i++)
{
printf("\n 请输入你的分数");
scanf("%lf", &arr[i]);
}
for (i = 0; i < length; i++)
{
printf("%d=%.2f", i, arr[i]);
}
getchar();
getchar();
}
③三种初始化数组的方式
int arr[2];
arr[0] = 200;
int arr2[3] = { 1, 2, 3 };
int arr3[] = { 5, 4, 6 };
3 数据使用注意事项和细节
- 数组是多个相同类型数据的组合,一个数组一-旦声明/定义了,其长度是固定的,不能动态变化。
- 数组创建后, 如果没有赋值,则遵守如下规则
全局数组默认值0 非全局数组初值是机器垃圾值(即:原来系统分配给这块空间的值) - 使用 数组的步骤1. 定义数组2给数组各个元素赋值3使用数组,也可以一一步到位
- 数组的 下标是从0开始的,不是从1开始。
- 数组下标必须在指定范围内使用,编译通过,在运行时会因为数组越界而异常中断:
比如intarr[5] 有效下标为0-4 - C的数组属构造类型,是引用传递(传递的是地址),因此当把一个数组传递给–个函数时/或者变量,函数/变
量操作数组会影响到原数组(因为传参的时候传过去的是指针,也就是地址)
4 数组应用案例
1)创建一个 char类型的26个元素的数组,分别放置’A’-Z‘。使用for循环访问所有元素并打印出来。提示:字符数据运算’A’+1 -> ‘B’
#include <stdio.h>
void main() {
char arr[26];
int i;
for (i = 0; i < 26; i++)
{
arr[i] = 'A' + i;
}
for ( i = 0; i < 26; i++)
{
printf("%c ", arr[i]);
}
getchar();
}
2)请求出一个数组的最大值,并得到对应的下标。
#include <stdio.h>
void main() {
int arr[] = {2, 3, 456, 554, 34, 5464};
int i, length, maxIndex;
double max = arr[0];
length = sizeof(arr) / sizeof(int);
for (i = 1; i < length; i++)
{
if (arr[i] > max) {
max = arr[i];
maxIndex = i;
}
}
printf("最大值为%.2f, 对应的下角标为%d", max, maxIndex);
getchar();
}
5 字符数组与字符串
字符数组实际上是一系列字符的集合,也就是字符串(String) 。在C语言中,没有专门的字符串变量,没有 string类型,通常就用一个字符数组来存放-一个字符串
#include <stdio.h>
void main() {
char str[] = "莫个超是靓仔";
printf("str=%s", str);
getchar();
}
本 次
1 字符数组注意事项
- 在C语言中,字符串实际上是使用null 字符(\0’) 终止的一维字符数组。因此,一个以null 结尾的字符串,
包含了组成字符串的字符。 - ^\0’是 ASCII码表中的第0个字符,用NUL表示,称为空字符。该字符既不能显示,也不是控制字符,输出该
字符不会有任何效果,它在C语言中仅作为字符串的结束标志。 - 字符数组(字符 串)在内存中的布局分析[案例]
说明:"?"表示是不知道的东西,可能是垃圾值,也可能是其他的东西
结论如果在给某个字符数组赋值时,(1 )赋给的元素的个数小于该数组的长度,则会自动在后面加\0’, 表示 字符串结束,(2)赋给的元素的个数等于该数组的长度,则不会自动添加\O’ char str2[]= {‘t,m’,‘o’} 输出什么?输出的是tmo 乱码.
补充:
char str2[] = { 't', 'o', 'm' };
2 字符串的访问和遍历
因为字符串的本质就是字符数组,因此可以按照数组的方式遍历和访问某个元素
代码演示
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void main() {
char str[] = "hello";
int i;
int length = strlen(str);
printf("str=%s", str);
printf("\n str的长度是%d", length);
printf("\n 字符串第三个字是%c", str[2]);
for ( i = 0; i < length; i++)
{
printf("\n %c", str[i]);
}
system("pause");
}
6 字符串的表现形式
① 用字符数组存放字符串
char str[] = "我是靓仔智";
char str2[] = {'h','e','l','l','o'};
② 用字符指针指向一个字符串
-
C语言对字符串常量"hellotom"是按字符数组处理的,在内存中开辟了一一个字符数组用来存放字符串常量,程 序在定义字符串指针变量str时==只是把字符串首地址(即存放字符串的字符数组的首地址)赋给pStr==. -
printf("%s\n",str); 可以输出str 指向的字符串 -
对应的内存布局图(!!)
③ 两种方法表示字符串的讨论
-
字符数组由若千个元素组成,每个元素放一一个字符;而字符指针变量中存放的是地址(字符串/字符数组的首地址),绝不是将字符串放到字符指针变量中(是字符串首地址)[图] -
对字符数组只能对各个元素赋值,不能用以下方法对字符数组赋值 char str[5];
str = "hello";
str[2] = 'i';
system("pause");
-
对字符指针变量,采用下面方法赋值,是可以的 char* str = "我是靓仔智";
str = "世界和平,春暖花开";
-
如果定义了一个字符数组,那么它有确定的内存地址(即字符数组名是一个常量);而定义一个字符指针变量时, 它并未指向某个确定的字符数据,并且可以多次赋值[代码+图解]
7 字符串相关函数
① 常用函数
② 字符串函数应用案例
#include <stdio.h>
#include <string.h>
void main() {
char str[] = "hello";
char str2[] = "world";
char str3[] = "";
strcpy(str3, str2);
printf("复制的是%s\n", str);
strcat(str, str2);
printf("append为%s\n", str);
int result = strcmp(str, str2);
printf("result=%d\n", result);
char* p = strchr(str, 'e');
printf("p=%s\n", p);
char* p2 = strstr(str, "ll");
printf("p2=%s\n", p2);
getchar();
}
8 字符串使用注意项和细节
-
程序中往往依靠检测\0’ 的位置来判定字符串是否结束,而不是根据数组的长度来决定字符串长度。因此,字 符串长度不会统计"\0’, 字符数组长度会统计[案例] #include <stdio.h>
#include <string.h>
void main() {
char str[] = "hello";
int len = strlen(str);
int arrLen = sizeof(str);
printf("%d %d", len, arrLen);
getchar();
}
-
在定 义字符数组时应估计实际字符串长度,保证数组长度始终大于字符串实际长度,否则, 在输出字符数组 时可能出现未知字符. -
系 统对字符串常量也自动加一个\0’作为结束符。例如"C Program”共有9个字符,但在内存中占10个字节, 最后一个字节\0’是系统自动加上的。( 通过sizeof()函数可验证) -
定 义字符数组时,如果给的字符个数比数组的长度小,则系统会默认将剩余的元素空间,全部设置为’\0’, 比 如char str[6] = “ab” , str内存布局就是 [a] [b] [\0] [\0] [\0] [\0]
字符数组练习
9 多维数组 - 二维数组
数组里面放了个数组(套娃)
语法:类型 数组名[大小] [大小]
①快速入门
代码演示
#include <stdio.h>
void main() {
int i, j;
int arr[4][6];
for (i = 0; i < 4; i++)
{
for (j = 0; j < 6; j++)
{
arr[i][j] = 0;
}
}
arr[1][2] = 1;
arr[2][1] = 2;
arr[2][3] = 3;
for (i = 0; i < 4; i++)
{
for (j = 0; j < 6; j++)
{
printf("%d", arr[i][j]);
}
printf("\n");
}
getchar();
}
②二维数组内存分布
#include <stdio.h>
void main() {
int i, j;
int arr[4][6];
for (i = 0; i < 4; i++)
{
for (j = 0; j < 6; j++)
{
arr[i][j] = 0;
}
}
printf("二维数组arr的首地址=%p\n", arr);
printf("二维数组arr[0]的地址=%p\n", arr[0]);
printf("二维数组arr[0][0]的地址=%p\n", &arr[0][0]);
printf("二维数组arr[0][1]的地址=%p\n", &arr[0][1]);
getchar();
}
说明:arr、arr[0]和&arr[0] [0]的地址是一样的,都是首地址
内存分析图
注意:他们并不是一行行分开的,而是连续的地址
③直接初始化数组
- 定义 类型 数组名[大小] [大小] = {{值1, 值2, 值3},{值1, 值2, 值3},{值1, 值2, 值3}};
- 或者 类型 数组名[大小] [大小] = 值1,值2,值3,值4,值5,值…};
④二维数组练习
#include <stdio.h>
void main() {
int i, j;
double score[3][3];
int rows = sizeof(score) / sizeof(score[0]);
int cols = sizeof(score[0]) / sizeof(double);
double totalScore = 0.0;
double avgScore = 0.0;
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
score[i][j] = 0;
}
}
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
printf("请输入%d班第%d个学生的成绩:", i + 1, j + 1);
scanf("%lf", &score[i][j]);
}
}
printf("\n\n");
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
printf("%.2f ", score[i][j]);
}
printf("\n");
}
printf("\n\n");
for (i = 0; i < rows; i++)
{
for (j = 0; j < cols; j++)
{
totalScore += score[i][j];
avgScore += score[i][j];
}
printf("%d班的平均分为:%.2f\n", i + 1, avgScore / 3);
avgScore = 0.0;
}
printf("\n\n所有学生的总分:%.2f", totalScore);
getchar();
getchar();
}
⑤二维数组使用细节和注意事项
-
可以只对部分元素赋值,未赋值的元素自动取“零”值[案例] int main({
int a[4][5]= {{1}, {2}, {3},{}};
int ij;
for(i=0;i<4;i++) {
for(j=0;j<5;j++){
printf("%d ",a[i][j]);
}
print("\n"); .
getchar(;
}
-
如果对全部元素赋值,那么第一维的长度可以不给出。比如: int a[3][3]= {1,2,3,4,5,6, 7,8, 9};
可以写为:
inta[][3]= {1,2,3,4,5,6, 7,8, 9};
-
二维数组可以看作是由一维数组嵌套而成的;如果- - 个数组的每个元素又是-一个数组,那么它就是二维数组。 二维数组a[3][4]可看成三个- -维数组,它们的数组名分别为a[0]、 a[1]、 a[2]。
这三个-维数组都有4个元素,如,一维数组 a[0] 的元素为a[0][0]、 a[0][1]、 a[0][2]、 a[0][3]
十一、排序和查找
1 冒泡排序
代码实现
#include <stdio.h>
void bubbleSore(int arr[], int len) {
int i, j, t;
for (i = 0; i < len - 1; i++)
{
for (j = 0; j < len - 1 - i; j++)
{
if (arr[j] > arr[j + 1])
{
t = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = t;
}
}
}
}
void main() {
int arr[] = {65, 43, 75, 23, 100};
int i, len;
len = sizeof(arr) / sizeof(int);
bubbleSore(arr, len);
printf("排序后\n");
for (i = 0; i < len; i++)
{
printf("%d\n", arr[i]);
}
getchar();
}
2 查找
①顺序查找
逐个比较
#include <stdio.h>
int searchSeq(int arr[], int length, int index) {
int i;
for (i = 0; i < length; i++)
{
if (arr[i] == index)
{
return arr[i];
}
}
}
void main() {
int length;
int arr[] = {2, 4, -2, 22, 90};
length = sizeof(arr) / sizeof(int);
int index = searchSeq(arr, length, 90);
printf("index=%d", index);
getchar();
}
②二分查找
前提:必须是有序数组
说明:从中间开始查找,大于中间的数就往右边查找,小于就往左边查
代码实现
#include <stdio.h>
int binarySearch(int arr[], int leftIndex, int rightIndex, int findVal) {
int i;
int midIndex = (leftIndex + rightIndex) / 2;
int midVal = arr[midIndex];
if (midVal > findVal)
{
for (i = midIndex - 1; i > leftIndex; i--)
{
if (arr[i] == findVal)
{
return i;
}
}
}
else if (midIndex < findVal) {
for (i = midIndex + 1; i < rightIndex; i++)
{
if (arr[i] == findVal)
{
return i;
}
}
}
else
{
return 0;
}
}
void main() {
int arr[] = { 2, 30, 34, 45, 90 };
int arrLen = sizeof(arr) / sizeof(int);
int index = binarySearch(arr, 0, arrLen, 90);
if (index != 1) {
printf("找到了,它的索引值是:%d", index);
}
else
{
printf("没有找到");
}
getchar();
}
十二、断点调试
十三、指针
1 指针的基本介绍
和java中的引用数据类型类似,指针就是一个可以操作对应地址的变量的东西
- 指针是C语言的精华,也是C语言的难点。
- 指针, 也就是内存的地址;所谓指针变量,也就是保存了内存地址的变量。关于指针的基本使用,在讲变量的
时候做了入门级的介绍 - 获取 变量的地址,用&,比如:
int num= 10,获取num的地址: &num - 指针类型, 指针变量存的是一个地址,这个地址指向的空间存的才是值
比如: intptr = # ptr 就是指向int 类型的指针变量,即ptr是int 类型。 - 获取指针类 型所指向的值,使用: (取值符号), 比如: int ptr,使用*ptr 获取ptr指向的值
什么是指针
指针是一一个变量,其值为另一个变量的地址(前示意图已经说明),即,内存位置的直接地址。就像其他变量或 常量一样,在使用指针存储其他变量地址之前,对其进行声明。指针变量声明的一般形式为:
int *ip;
double *dp;
float *fp;
char *ch;
2 指针的算数运算
指针是一个用数值表示的地址。可以对指针执行算术运算。可以对指针进行四种算术运算: ++、-、+. -。
就是对指针中的地址进行算数运算,以单位来计算的,是什么类型就是什么单位,比如int类型的指针就是一个单位4个字节
①指针递增操作(++)
代码演示
#include <stdio.h>
void main() {
int i, length, *ptr;
int val[] = {23, 53, 45};
length = sizeof(val) / sizeof(int);
ptr = val;
for (i = 0; i < length; i++)
{
printf("val[%d] 地址=%p \n", i, ptr);
printf("val[%d]=%d \n", i, *ptr);
ptr++;
}
getchar();
}
内存示意图
②指针递减操作(–)
原理和++一样
#include <stdio.h>
void main() {
int i, *ptr, length;
int var[] = {23, 34, 54};
length = sizeof(var) / sizeof(int);
ptr = &var[length - 1];
for ( i = length - 1; i >= 0; i--)
{
printf("val[%d] 地址=%p \n", i, ptr);
printf("val[%d]=%d \n", i, *ptr);
ptr--;
}
getchar();
}
注意:指针接收的是地址,不是值
③指针+、-操作
和普通的类型,不过指针是以单位来计算的,单位也就是对应指针类型的字节数
#include <stdio.h>
void main() {
int arr[] = {10, 30, 230};
int i, * ptr;
ptr = arr;
ptr += 2;
printf("arr[2]=%d var[2]的地址=%p\nptr存储的地址=%p ptr指向的值=%d", arr[2], &arr[2], ptr, *ptr);
getchar();
}
④练习
#include <stdio.h>
void main() {
int i, * ptr;
int var[] = { 20, 30, 50 };
ptr = &var[2];
printf("ptr指向的值为%d, 地址为%p a[2]的地址为%p", *ptr, ptr, &var[2]);
getchar();
}
3 指针的比较
void main() { int length; int arr[] = {2, 4, -2, 22, 90}; length = sizeof(arr) / sizeof(int); int index = searchSeq(arr, length, 90); printf(“index=%d”, index); getchar(); }
### ②二分查找
**前提**:必须是有序数组
说明:从中间开始查找,大于中间的数就往右边查找,小于就往左边查
**代码实现**
```c
#include <stdio.h>
// 二分法查找
int binarySearch(int arr[], int leftIndex, int rightIndex, int findVal) {
int i;
int midIndex = (leftIndex + rightIndex) / 2; // 中间值的索引
int midVal = arr[midIndex]; // 中间值
if (midVal > findVal) // 中间值大于要找的值,往左边找
{
for (i = midIndex - 1; i > leftIndex; i--)
{
if (arr[i] == findVal)
{
return i;
}
}
}
else if (midIndex < findVal) { // 中间值小于要找的值,往右边找
for (i = midIndex + 1; i < rightIndex; i++)
{
if (arr[i] == findVal)
{
return i;
}
}
}
else
{
return 0; // 返回该数的下标
}
}
void main() {
int arr[] = { 2, 30, 34, 45, 90 };
int arrLen = sizeof(arr) / sizeof(int);
int index = binarySearch(arr, 0, arrLen, 90);
if (index != 1) {
printf("找到了,它的索引值是:%d", index);
}
else
{
printf("没有找到");
}
getchar();
}
十二、断点调试
[外链图片转存中…(img-EyvPfxk0-1630841155427)]
十三、指针
1 指针的基本介绍
和java中的引用数据类型类似,指针就是一个可以操作对应地址的变量的东西
- 指针是C语言的精华,也是C语言的难点。
- 指针, 也就是内存的地址;所谓指针变量,也就是保存了内存地址的变量。关于指针的基本使用,在讲变量的
时候做了入门级的介绍 - 获取 变量的地址,用&,比如:
int num= 10,获取num的地址: &num - 指针类型, 指针变量存的是一个地址,这个地址指向的空间存的才是值
比如: intptr = # ptr 就是指向int 类型的指针变量,即ptr是int 类型。 - 获取指针类 型所指向的值,使用: (取值符号), 比如: int ptr,使用*ptr 获取ptr指向的值
什么是指针
指针是一一个变量,其值为另一个变量的地址(前示意图已经说明),即,内存位置的直接地址。就像其他变量或 常量一样,在使用指针存储其他变量地址之前,对其进行声明。指针变量声明的一般形式为:
int *ip;
double *dp;
float *fp;
char *ch;
[外链图片转存中…(img-nBA1rrDC-1630841155428)]
2 指针的算数运算
指针是一个用数值表示的地址。可以对指针执行算术运算。可以对指针进行四种算术运算: ++、-、+. -。
就是对指针中的地址进行算数运算,以单位来计算的,是什么类型就是什么单位,比如int类型的指针就是一个单位4个字节
①指针递增操作(++)
代码演示
#include <stdio.h>
void main() {
int i, length, *ptr;
int val[] = {23, 53, 45};
length = sizeof(val) / sizeof(int);
ptr = val;
for (i = 0; i < length; i++)
{
printf("val[%d] 地址=%p \n", i, ptr);
printf("val[%d]=%d \n", i, *ptr);
ptr++;
}
getchar();
}
内存示意图
[外链图片转存中…(img-N0ofAckF-1630841155429)]
②指针递减操作(–)
原理和++一样
#include <stdio.h>
void main() {
int i, *ptr, length;
int var[] = {23, 34, 54};
length = sizeof(var) / sizeof(int);
ptr = &var[length - 1];
for ( i = length - 1; i >= 0; i--)
{
printf("val[%d] 地址=%p \n", i, ptr);
printf("val[%d]=%d \n", i, *ptr);
ptr--;
}
getchar();
}
注意:指针接收的是地址,不是值
③指针+、-操作
和普通的类型,不过指针是以单位来计算的,单位也就是对应指针类型的字节数
#include <stdio.h>
void main() {
int arr[] = {10, 30, 230};
int i, * ptr;
ptr = arr;
ptr += 2;
printf("arr[2]=%d var[2]的地址=%p\nptr存储的地址=%p ptr指向的值=%d", arr[2], &arr[2], ptr, *ptr);
getchar();
}
④练习
#include <stdio.h>
void main() {
int i, * ptr;
int var[] = { 20, 30, 50 };
ptr = &var[2];
printf("ptr指向的值为%d, 地址为%p a[2]的地址为%p", *ptr, ptr, &var[2]);
getchar();
}
3 指针的比较
|