C语言知识点学习记录
前言
准备蓝桥杯,把C语言重温一遍,此文章用于记录。
一、C语言是什么?
C语言是一门通用计算机编程语
C语言的标准:ASCII(最初的标准),C11标准(第三个官方标准,也是最新的)。
二、C的入门
1.C语言的第一个程序
.c文件为源文件;.h文件为头文件
代码如下(示例):
// 第一个C程序 c_12.27-->test1.c
#include<stdio.h>//std是标准 i是in o是out
int main()
{
printf("hello world");
return 0;
}
main函数是程序的入口,必要的且只有一个main函数;
int 是整型的意思,此处的int表示main函数调用返回一个整型值;
printf()打印函数,属于库函数(系统提供的函数)
2.数据类型
有以下7种数据类型:
char字符型;
short短整型;
int整型;
long/long long长/更长的整型;
float单精度浮点型;
double双精度浮点型;
不同数据类型的数值范围不同。
//char
char a = 'K';//单引号 一个字符
printf("字符型:%c\n",a);//%c对应打印字符格式的数据
//int
int b = 1000000;
printf("整型:%d\n",b);
short int sb = 1000000;
printf("短整型:%d\n", sb);
long lb = 1000000;
printf("长整型:%d\n", lb);
//float
float d = 1.1;
printf("单精度浮点型:%f\n", d);
//double
float dd = 1.11;
printf("双精度浮点型:%lf\n", dd);
2022.12.27
不同数据类型大小不同,可用sizeof函数予以计算:
//计算各种数据类型的大小
printf("%d\n", sizeof(int));//计算int的大小
printf("%d\n", sizeof(short));
printf("%d\n", sizeof(long));//sizeof(int)>=sizeof(int)
printf("%d\n", sizeof(char));
printf("%d\n", sizeof(float));
printf("%d\n", sizeof(double));
printf("%d\n", sizeof(long long));
字节大小依次为4 2 4 1 4 8 8 ;
比特:bit 计算机最小的单位
字节:byte 一个字节是八个比特位
3.变量和常量
- 变量包括全局变量和局部变量
int num1 = 10;//全局变量
int main()
{
int num2 = 20;//局部变量
printf("%d", num2);
//全局变量和局部变量的名字最好不要相同,若相同局部变量优先
return 0;
}
-
变量的作用域和生命周期
哪里能用哪就是作用域
在该工程下中的文件声明的全局变量在使用时得先声明;
声明形式 +extern 声明外部符号;
例子:extern int g_val;
全局变量的作用域是整个工程;
局部变量的生命周期:进入作用域->出作用域;
全局变量的生命周期:整个程序的生命周期。 -
常量
有以下几种:
字面常量;const修饰的常变量;#define定义的标识符常量;枚举常量
#include<stdio.h>
#define a 100
//#define定义的标识符常量
//枚举常量snum
//枚举:一一列举
enum sex
{
//枚举常量
MALE,
FEMALE,
SECRET
};
int main()
{
enum sex SEX = MALE;
const int num = 8;//const:常属性 num的值不可改变
//const修饰常变量 num本质是变量,只是有了常属性
3;//字面常量
printf("%d\n", a);
printf("%d\n", MALE);//默认0
printf("%d\n", FEMALE);//默认1
printf("%d\n", SECRET);//默认2
printf("%d\n", SEX);
return 0;
}
4.scanf函数
int main()
{
int a = 0;
int b = 0;//注意:变量要定义在代码块前面
//scanf:用于输入数据
scanf("%d%d", &a, &b);//&:取址符
printf("a=%d,b=%d\n", a, b);
return 0;
}
5.字符串
字符串是由双引号括起来的一串字符,其结束标志是\0的转义字符,计算字符串长度时,该转义字符不计算在内;
数据在计算机上存储的是数字,每个字符也是,比如:a->97…ASCII编码;
strlen函数用于计算字符串长度(注意要包含string.h头文件)
int main()
{
//字符串可以放到一个数组里面
char str1[ ] = "abc";
char str2[ ] = { 'a' ,'b', 'c' ,'\0'};//注意需要放最后那个转义字符
printf("%s\n", str1);
printf("%s\n", str2);
printf("%d\n", strlen(str1));
printf("%d\n", strlen(str2));
return 0;
}
2022.12.28
6. 转义字符
int main()
{
//转义字符
printf("abc\n");//"\n"换行
printf("a\tb");//"\t"水平制表符 作用相当于键盘上的Tab键
printf("\?");//"\?"显示?号
printf("\a");//"\a"蜂鸣声,表示警告
printf("\\");//"\\"显示\号
printf("\'\n");//"\'"显示'号
printf("%c\n",'\32');//"\32"是两个八进制数字,32作为八进制代表的那个十进制数字作为ASCII码值对应的字符,八进制32->十进制26->ASCII对应的符号
printf("%c", '\x61');//"/xdd"中dd表示两个十六进制数字
return 0;
}
7.注释
两种注释方式://和/* */
注释的快捷键:ctrl+c再+k
取消注释:ctrl+k再+u
//注释
/*注
释*/
8.选择语句
int main()
{
//选择语句
int safe = 0;
printf("请输入你的年薪(单位:万元):");
scanf("%d", &safe);
if (safe > 30 || safe == 30)
printf("你是个成功人士!\n");
else
printf("还需要努力呢!\n");
return 0;
}
9.循环语句(while)
int main()
{
//循环结构 while语句
int mon = 0;
while (mon<500)
{
printf("请输入你赚的钱(百万元):");
scanf("%d", &mon);
}
return 0;
}
10.函数
//函数包括自定义函数和库函数
//自定义函数
int add(int x, int y)//add是函数名,int x和int y是函数的参数
{
//函数体
int sum;
sum = x + y;
return sum;//返回结果
}
int main()
{
//调用函数
int a = 0;
int b = 0;
int sum;
printf("请输入a、b:");
scanf("%d%d", &a, &b);
sum= add(a, b);
printf("a+b=%d", sum);
return 0;
}
11.数组
int main()
{
//数组:一组相同类型数据的集合
int i = 0;
int s[10] = {0,1,2,3,4,5,6,7,8,9};//数组的下标从0开始
while (i<9)
{
printf("第%d数据的值为:%d \n",i,s[i]);
i++;
}
printf("%d\n", strlen("iamagoodboy\0"));
return 0;
}
//结果:
//第0数据的值为:0
//第1数据的值为:1
//第2数据的值为:2
//第3数据的值为:3
//第4数据的值为:4
//第5数据的值为:5
//第6数据的值为:6
//第7数据的值为:7
//第8数据的值为:8
//说明下表从0开始
'\0’是一个转义字符,但在char数组中不算一个元素
12.原码、反码、补码介绍
只要是整数,内存中存储的都是二进制的补码;
对于正数来说,原码、反码、补码相同;
负数在内存中存储是以二进制的补码进行存储的;
已知补码求原码:
补码:11111111111111111111111111111101
先求反码:补码-1即可得到反码
反码:11111111111111111111111111111100
再求原码:符号位(最高位)不变,其余取反
原码:10000000000000000000000000000011(转化为十进制是-3)
13.操作符
注意:++、–的使用(前置、后置的区别)
int main()
{
//操作符
//1、算术操作符:
int a = 5 / 2;//除法
int b = 5 % 2;//取余
printf("a=%d,b=%d\n", a, b);
//2、移位操作符 二进制位 空位补0
//左移 <<
int c = 1;
int d= c<< 1;
printf("c左移一位的值为%d\n", d);//右移同理
printf("c=%d\n", c);//c本身的值是不会变的
//3、(二进制)位操作:&按位与、|按位或 、^按位异或
int e = 3;//011
int f = 5;//101
int g = e & f;//001
printf("g=%d\n", g);
//4、赋值操作符
int h = 1;//"="就是赋值操作符之一;注意“==”是判断相等
h = h + 10;//
h += 10;//运算结果与上式相同
//还有-=、*=、/=、&=、|=、>>=、<<=,这些叫做复合赋值符
//5、单目操作符
int i = 10;
//!
printf("i=%d\n", i);
printf("!i=%d\n", !i);//"!"是逻辑反操作符,在c中,0表示假,非零数表示真
//单目还有+、-、sizeof
// sizeof():计算的是变量、类型所占空间的大小,单位:字节
printf("%d\n",sizeof(i));
printf("%d\n", sizeof(int));
printf("%d\n", sizeof i);//以上三种sizeof的表示都可以,但最后一种只有变量才可以去掉括号 即sizeof int 不可用
//sizeof还可以计算数组的大小
int arr[10] = { 10 };
printf("%d\n", sizeof arr);//结果:40
//&(后面讲)、~(对一个数的二进制按位取反)、--、++、*、(类型)
//单目操作符(类型)表示强制类型转换
//~
int aa =2;//aa占用四个字节,32个bit位,即00000000000000000000000000000010
int bb = ~aa;//按位取反得11111111111111111111111111111101,注意这是补码,要转化为原码,结果是-3
printf("%d\n", bb);//-3
//涉及原码、反码、补码的知识
//--、++
int cc = 10;
int dd = cc++;
printf("cc=%d,cc++=%d\n",cc, dd);//11 10
int ee = ++cc;
printf("cc=%d,cc++=%d\n", cc, ee);//12 12
//可以发现:dd=cc++是先dd=cc赋值,后cc=cc+1,即先赋值后自增
// ee=++cc是先cc=cc+1,后ee=cc赋值,即先自增后赋值
//*是间接访问操作符(解引用操作符)(后面讲)
//强制类型转换(不建议使用)
int qq =(int) 3.14;//进行强制类型转换才不会发出警告
printf("qq=%d\n", qq);
//6、双目操作符
//a+b “+”是双目操作符有两个操作数
//7、三目操作符
//8、关系操作符:>、<、>=、<=、==、!=(不相等)
//9、逻辑操作符:&&逻辑与、||逻辑或
//10、条件操作符(三目操作符):exp1?exp2:exp3
//若表表达式1结果为真,表达式2的结果为整个表达式的结果;反之表达式3的结果为整个表达式的结果
int ww = 10;
int yy = 11;
int max = 0;
max = (ww >yy ?ww :yy);
printf("max=%d\n", max);
//11、逗号表达式:exp1,exp2,exp3,...
//12、下标引用操作符:[];
//13、函数调用操作符:();
//14、结构成员:.、->
return 0;
14.常见关键字
register寄存器,寄存器在计算机是有限的
计算机存储数据使用寄存器、高速缓存、内存、硬盘(访问速度由高到低);
//test.c
void test()
{
//static:静态变量,
static int num2 =1;//静态变量,static修饰局部变量,变量的生命周期延长了,出作用域也不再销毁
num2++;
printf("num2=%d\n", num2);//结果:输出2、3、4、5、6
}
//声明外部函数
extern int add(int, int);
int main()
{
//关键字
auto int a = 0;//局部变量 -自动变量,局部变量前面有auto,一般省略掉
//int定义的符号是有符号的 全称是signed int;
//unsigned int是无符号的
//struct是结构体关键字
//union是联合体/共用体
register int b = 1;//建议把b定义成寄存器变量
//typedef:类型定义
typedef unsigned int u_int;//相当于给unsigned int 重新起个名/别名
u_int num1 = 1;
int i = 0;
while (i < 5)
{
test();
i++;
}
//static修饰全局变量改变的是作用域,让静态的全局变量只能在自己所在的源文件内部使用
//extern-声明外部符号
extern int g_val;
printf("g_val=%d\n", g_val);
//static还能修饰函数,作用为限制外部函数的作用域,改变了函数的链接属性
//正常的函数是具有外部链接属性的,即外部源文件希望使用该源函数时只需要声明就可以使用
//用static修饰后外部连接属性就变为内部链接属性
int yy = 10;
int zz = 20;
int sum = add(yy, zz);//使用外部函数时需要声明
printf("sum=%d\n", sum);
return 0;
}
//add.c
int g_val = 2020;
//static int g_val = 2020;//全局变量,如果用static修饰就无法在别的地方使用
int add(int x, int y)//用static修饰,该函数就无法在其他源文件中使用
{
int z = x + y;
return z;
}
15.#define用于定义常量和宏
//test.c
#define max 100//#define定义标识符常量
#define MAX(X,Y) (X>Y?X:Y) //还可以定义宏-带参数
int main()
{
//#define用于定义常量和宏
//定义常量
int aaa = max;
printf("aaa=%d\n", aaa);
//利用宏来实现比大小
int ab = 10;
int cd = 20;
int maxx = MAX(ab, cd);//maxx=(X>Y?X:Y)
printf("maxx=%d\n", maxx);
return 0;
}
2023.1.8
16.指针
- 指针变量专门用来存放地址
- 在计算机中,内存被划分为一个个小的内存单元,每个内存单元都有其对应的编号,即地址;
- 地址如何产生?
在买电脑的时候,通常会遇到32位和64位的电脑,32位、64位是指有32、64根地址线/数据线,每根地址线上有正电或负电,即0或1,因此,32位可以产生2^32个不同的二进制数,这些二进制数就是地址 - 内存单元:一个内存单元占一个字节
- 指针变量的大小:在32位的平台上,一个指针变量的大小占4个字节;在64位平台上占8个字节
int main()
{
//指针
int a = 10;//4个字节
//指针变量专门用来存放地址
int* p = &a;//存放a的地址
printf("p=%p\n", p);
//利用&取地址操作符获取地址
printf("&a=%p\n", &a);//%p
//打印结果(十六进制):005AFDF0
*p=20;//p——解引用操作符,将p地址对应的内容赋值20
printf("a=%d\n", a);//a=20
char ch = 'w';
char* pc = &ch;
*pc = 'a';
printf("指针变量的大小为:%d个字节\n", sizeof(pc));
printf("%c\n", ch);
return 0;
}
2023.1.9
17.结构体
什么是结构体?
结构体主要用于描述复杂对象,是自己创造出来的类型。
//创建一个结构体类型
struct book//book是类型名
{
char name[20];//C程序设计
short price;//55
};//此处的分号不可缺少
int main()
{
//利用结构体类型创建一个该类型的结构体变量
struct book b1 = {"C语言程序设计",55};
struct book* pb = &b1;
//利用地址pd打印书名和价格
//"."操作符用于结构体变量,结构体变量.成员
printf("书名:%s\n", (*pb).name);
printf("价格:%d\n", (*pb).price);
//还有种写法
//"->" 结构体指针->成员
printf("书名:%s\n", pb->name);
printf("价格:%d\n",pb->price);
//最普通的写法
printf("书名:%s\n", b1.name);
printf("价格:%d\n", b1.price);
b1.price = 15;
//b1.name="c++";不能这么写,因为name是个数组不是变量,name代表地址
strcpy(b1.name,"c++");//strcpy-string copy是字符串拷贝,是个库函数,其头文件是string.h
printf("修改后的价格:%d\n", b1.price);
printf("修改后的书名:%s\n", b1.name);
return 0;
}
2023.1.16
三、分支和循环语句
C语言是一门结构化的程序设计语言。
包含三种结构:
1、顺序结构;2、选择结构;3、循环结构。
该节中将学习分支语句(if、switch)和循环语句(while、for、do while)以及go to语句。
什么是语句?
C语言中由一个分号隔开的就是一条语句。
int a = 0;//这就是一条语句,分号表示一条语句的结束
1.分支语句(选择结构)
1.1 if语句结构:
1、if(表达式)
语句;
2、if(表达式)
语句1;
else
语句2;
3、if(表达式1)
语句1;
else if(表达式1)
语句2;
else
语句3;
在if语句中,如果条件成立,且要执行多条语句,应该使用代码块,即用{}大括号括起来。
注意:else与其最近的if相对应
练习:判断1~100是否为奇数
//练习1:判断1~100是否为奇数?
int a = 0;
/*printf("请输入一个数a=");*/
for (a=0;a <= 100;a++)
{
if (a <= 100)
//scanf("%d", &a);
{
if (a % 2 == 0)
printf("%d不是奇数。\n", a);
else
printf("%d是奇数。\n", a);
}
}
1.2 switch语句
switch语句是一种分支语句,常用于多分支的情况。
结构:
switch(必须是int)
{
case 后面必须是整型常量表达式:语句;break;
}
搭配break才能实现真正的分支;case和default顺序没有严格的控制,但default最好在最后面。
//switch语句
int day = 0;
scanf("%d", &day);
switch (day)
{
case 1:printf("星期一\n"); break;
case 2:printf("星期二\n"); break;
case 3:printf("星期三\n"); break;
case 4:printf("星期四\n"); break;
case 5:printf("星期五\n"); break;
case 6:printf("星期六\n"); break;
case 7:printf("星期日\n"); break;
default:printf("您输错了哦!");
}
2.循环语句
2.1 while循环
while的语法结构:
while(表达式)
循环语句;
int i = 0;
while (i <= 10)
{
printf("%d\n", i);
i++;
}
2.1.1 循环中break和continue的作用
break的作用:循环遇到break,就会停止后期的所有循环,直接终止循环,是用于永久终止循环的。
continue的作用:终止本次循环,也就是在本次循环中continue后面的语句不会再执行,而是跳转到while的判断部分,进入下一次循环。
2.1.2 getchar()和 putchar()
getchar()是从键盘中获取字符;
putchar()是输出字符
//getchar() putchar()
int a = getchar();//用于接受键盘获取的字符
putchar(a);//输出字符 ,和printf差不多
2023.2.8
putchar的用法
int ret = 0;
int ch = 0;
char password[20] = { 0 };
printf("请输入密码:>");
scanf("%s", password);//注意:只会读取空格之前的东西
//缓冲区还剩余\n
while ((ch = getchar()) != '\n')//读走\n,putchar只能读走一个字符
{
;
}
printf("请确认(Y/N):>");
ret = getchar();
if (ret == 'Y')
{
printf("确认成功\n");
printf("密码:%d",password);
}
else
printf("确认失败");//1、结果为确认失败,是因为敲password后的回车被ret = getchar()读入了
2.2 for循环
用法:for(表达式1;表达式2;表达式3) 循环语句;
表达式1是初始化部分,用于初始化循环变量;
表达式2为条件判断部分,用于判断循环什么时候终止;
表达式3为调整部分,用于循环条件的调整。
使用for循环打印1~10的数字:
//打印1~10的数字
for (int i = 1; i <= 10; i++)
{
printf("%d ", i);//i++在这之后再执行
}
一些关于for循环的建议:
- 不可在循环体内修改循环变量,防止循环失去控制;
- 建议循环变量的范围采取前闭后开。
2.2.1 for循环的两个变种
//变种1
for (;;)//判断部分省略,判断结果则恒为真
{
printf("hehe\n");
}
//变种2
int x, y;
for (x = 0, y = 0; x < 2 && y < 5; ++x, y++)
{
printf("hehe\n");
}
2.3 do…while()循环
2.3.1 do语句的语法
do
循环语句;
while(表达式);
2.4 练习
2.4.1 练习1
练习1:计算n的阶乘
//练习1:计算n的阶乘(自编)
int n=0;
int m = 1;
printf("计算n的阶乘,接下来请输入n:");
scanf("%d", &n);
while(n!=1)
{
m = n * m;
n = n - 1;
}
printf("n的阶乘为:%d", m);
//参考答案:
int i = 0;
int n = 0;
int ret = 1;
scanf("%d", &n);
for (i = 1; i <= n; i++)
{
ret = ret * i;
}
printf("ret=%d", ret);
2.4.2 练习2
练习2:计算1!+2!+3!+…+n!
//练习2:计算1!+2!+3!+...+n!(自编)
int n = 0;
int m = 1;
int s = 0;
printf("计算1!+2!+3!+...+n!\n接下来请输入n:");
scanf("%d", &n);
for (int i = 1; i <= n; i++)
{
m= m * i;
s = s + m;
}
printf("1!+2!+3!+...+n!=%d", s);
2.4.3 练习3——折半查找算法
练习3:在一个有序数组中查找具体的某个数字n。(讲解二分查找)
//参考
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr) / sizeof(arr[0]);//计算元素个数
int left = 0;//左下标
int right= sz-1;//右下标
int k = 10;
while (left <= right)
{
int mid = (left + right) / 2;
if (arr[mid] > k)
{
right = mid - 1;
}
else if (arr[mid] < k)
{
left = mid + 1;
}
else
{
printf("找到了,下标是:%d\n", mid);
break;
}
}
if (left > right)
{
printf("找不到\n");
}
2.4.4 练习4
2.4.2.1 sleep()函数(windows.h头文件)
停留多少毫秒,例:sleep(1000);//停留10000ms=1s
2.4.2.2 system()函数(stdlib.h头文件)
该函数是执行系统命令的一个函数;
system(“cls”);//清空屏幕
//练习4:编写代码,演示多个字符从两端移动,到中间汇聚
//效果 I an a good boy.
char arr1[] = "i am a good boy!";//字符串结束后自带\0
char arr2[] = "################";
int left = 0;
int right = strlen(arr1)-1;//整型数组无需考虑\0,字符串或字符数组则需考虑\0是否会引起下标加1
while (left <= right)
{
system("cls");//system是执行系统命令的一个函数-cls-清空屏幕
arr2[left] = arr1[left];
left ++;
arr2[right] = arr1[right];
right--;
printf("%s", arr2);
//休息1s
Sleep(100);//休息1000ms,需要windows.h头文件
2.4.5 练习5(strcmp()函数)
//练习5: 编写代码实现,模拟用户登录情景,并且只能登录三次。(只允许输入三次密码,如果密码正确则提示登录成,如果三次均输入错误,则退出程序。
int i = 0;
char password[20] = { 0 };
for (i = 0; i < 3; i++)
{
printf("请输入密码:");
scanf("%s", password);
if (strcmp(password, "123456")==0)//字符串的比较应该用strcmp函数,相同输出0
{
printf("登录成功!");
break;
}
else
{
printf("密码错误,您还有%d次机会\n", 2 - i);
}
}
if (i == 3)
{
printf("三次密码均输入错误,请退出");
}
2023.2.10
3.小知识
- 关键字不可自己创建;
- define不是关键字,是预处理指令;
- 指针是个变量,用来存放地址;
- switch()中可以是char类型;
4.练习
Q1:写代码将三个数从大到小输出
//Q1:写代码将三个数从大到小输出
int a = 0;
int b = 0;
int c=0;
int mid = 0;
printf("请依次输入三个数:");
scanf("%d %d %d", &a, &b, &c);
if (a < b)
{
mid = a;
a = b;
b = mid;
}
if (a < c)
{
mid = a;
a = c;
c = mid;
}
if (b< c)
{
mid = b;
b = c;
c = mid;
}
printf("%d %d %d",a, b, c);
Q2:打印1~100内为三的倍数的数
//Q2:打印1~100内为三的倍数的数
for (int i = 3; i <=100; i++)
{
if ((i%3)==0)
printf("%d ", i);
}
Q3:给定两个数,求这两个数的最大公约数
//Q3:给定两个数,求这两个数的最大公约数
int a = 0;
int b = 0;
int i = 0;
printf("请输入两个数:");
scanf("%d%d", &a, &b);
for (i = a; i >=1 ; i--)
{
if (a % i == 0 && b % i == 0)
break;
}
printf("最大公约数是:%d", i);
//辗转相除法
int m = 24;
int n = 18;
int r = 0;
//scanf
while (r=m % n)
{
//r = m % n;
m = n;
n = r;
}
printf("%d\n", n);
Q4:打印1000~2000之年的闰年
//打印1000~2000之年的闰年
int year = 0;
int count = 0;
for (year = 1000; year <= 2000; year++)
{
//判断year是否为闰年:能被4整数且不能被100整除或者能被400整除是闰年
if (year % 4 == 0 && year % 100 != 0)
{
printf("%d ", year);
count++;
}
else if (year % 400 == 0)
{
printf("%d ", year);
count++;
}
}
printf("\ncount=%d ", count);
Q5:打印100~200之间的素数
//打印100~200之间的素数
int i = 0;
for (i = 101; i <= 200; i+=2)
{
//判断素数:
//试除法:只能被1和其自身整除
int j = 0;
for (j = 2; j <=sqrt(i); j++)//sqrt()开平方的函数,需要头文件math.h
{
if (i % j == 0)
{
break;
}
}
if (j > sqrt(i))
{
printf("%d ", i);
}
}
Q6:编写代码数出1-100之间的所有整数中出现多少个数字9
//编写代码数出1-100之间的所有整数中出现多少个数字9
int i = 0;
int count = 0;
for (i = 1; i <= 100; i++)
{
if (i % 10 == 9)
count++;
if (i / 10 == 9)
count++;
}
printf("%d\n", count);
2023.2.15
Q7:分数求和:计算1/1-1/2+1/3-1/4+1/5…+1/99-1/100的值
//分数求和:计算1/1-1/2+1/3-1/4+1/5......+1/99-1/100的值
int i = 0;
double sum = 0.0;
int flag = 1;
for (i = 1; i <= 100; i++)
{
sum+=flag*1.0 / i;//这样sum就是小数
flag = -flag;
}
printf("%lf\n", sum);
Q8:求最大值:求十个整数中的最大值
//求最大值:求十个整数中的最大值
int arr[] = { -1,-2,-3,-4,-5,-6,-7,-8,-9,-10};
int max =arr[0];
int i = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
for (i = 1; i < sz; i++)
{
if (arr[i] > max)
{
max = arr[i];
}
}
printf("max=%d\n", max);
Q9:输出乘法口诀表
//输出乘法口诀表
int i = 0;
int s = 0;
for (int j = 1; j <= 9; j++)
{
for (i = 1; i <= j; i++)
{
s = i * j;
printf("%d*%d=%-2d ", j, i, s);//%-2d 两位整数左对齐
}
printf("\n");
}
4.1猜数字小游戏
4.1.1 rand()函数生成随机数
生成随机数的函数,需要头文件stdlib.h,这个随机数又不是很随机,重复运行生成的随机数和上次运行生成的随机数一致。
因此需要时间戳来设置随机数。
代码如下:使用time函数需要头文件time.h
srand((unsigned)time(NULL));
4.1.2 参考代码
#include<stdlib.h>
#include<stdio.h>
#include<windows.h>
#include<time.h>
void menu()
{
printf("****************************\n");
printf("**** 1.play 0.exit ****\n");
printf("****************************\n");
}
//RAND_MAX 0-32767
void game()
{
printf("猜数字游戏开始!\n");
Sleep(1000);
system("cls");
//1.生成随机数
//时间戳:当前计算机的时间-计算机的起始时间(1917.1.1.0:0:0)=(xxxx)秒
//获取时间戳来设置随机数
int ret=rand()%100+1;//生成1~100随机数
//printf("%d", ret);
//2.猜数字
while (1)
{
int num = 0;
printf("请猜数字:>");
scanf("%d", &num);
if (num > ret)
{
printf("猜大了\n");
}
else if (num < ret)
{
printf("猜小了\n");
}
else
{
printf("恭喜你猜对了\n");
break;
}
}
Sleep(1000);
system("cls");
printf("游戏结束!\n");
Sleep(1000);
system("cls");
}
int main()
{
//猜数字游戏
srand((unsigned)time(NULL));//获取起点,只设置一次就行,不要频繁设置
//1、电脑生成随机数;2、猜数字
int input = 0;
do
{
menu();
printf("请选择>:");
scanf("%d", &input);
system("cls");
switch (input)
{
case 1:game();//猜数字游戏
break;
case 0:printf("退出游戏\n");
break;
default:printf("选择错误\n");
break;
}
} while (input);
return 0;
}
5.goto语句
少用
用法示例:
#include<stdio.h>
int main()
{
again:
printf("kxr\n");
goto again;
return 0;
}
在某些情况下,goto语句是适用的:终止程序在某些深度嵌套的结构的处理过程,例如跳出两层或多层循环,这种情况用break是不适合的,他只能从最内层循环退出到上一层循环。
关机程序:
四、函数
4.1函数是什么?
函数是子程序,在计算机科学中,子程序是一个大型程序中的某部分代码, 由一个或多个语句块组成。它负责完成某项特定任务,而且相较于其他代码,具备相对的独立性。一般会有输入参数并有返回值,提供对过程的封装和细节的隐藏。这些代码通常被集成为软件库。
例子:
#include<stdio.h>
int Add(int x, int y)
{
int z = 0;
z = x + y;
return z;
}
int main()
{
int a = 10;
int b = 20;
int sum = Add(a, b);
printf("%d\n", sum);
return 0;
}
4.1.1函数的分类
4.1.1.1 库函数
c语言本身提供的函数就叫做库函数。
常用的库函数有:
- IO函数
- 字符串操作函数
- 字符操作函数
- 内存操作函数
- 时间/日期函数
- 数学函数
- 其他库函数
库函数的查找和学习可以通过cplusplus.com或msdn或zh.cppreference.com。
4.1.1.2 自定义函数
自定义函数和库函数一样,有函数名、返回类型、函数参数,但这些都要自己来设计。
//找出两个数的较大值,定义函数get_max
int get_max(int x, int y)//第一个int是函数的返回类型
{
if (x > y)
return x;
else
return y;
}
int main()
{
int a = 10;
int b = 20;
/*get_max(a, b);*/
printf("max=%d", get_max(a, b));
return 0;
}
第二个例子,函数的参数是指针
//写一个函数交换两个整型变量的函数
void swap(int* x, int* y)//void表示没有返回值
{
int tmp;
tmp = *x;
*x = *y;
*y = tmp;
}
int main()
{
int a=10 ;
int b=20 ;
int* pa = &a;
int* pb =&b;
printf("a=%d,b=%d\n", a, b);
swap(pa, pb);
printf("a=%d,b=%d", a, b);
return 0;
}
4.1.1.1.1 函数的参数
- 实际参数(实参:真实传给函数的参数叫实参,他们必须有确定的值,以便将这些值传递给形参,实参可以是变量、常量、表达式、函数等;
- 形式参数(形参:函数后的括号中的参数,形式参数只有在函数被调用的过程中才被实例化(分配内存单元),形式参数在函数被调用完后就会被销毁;注意:形参和实参所用的内存空间不一样,因此对形参的修改是不会影响实参的。
4.1.1.1.2 函数的调用
- 传值调用:函数的形参和实参分别占有不同的内存块,对形参的修改不会影响实参;
- 传址调用:将函数外部创建的变量的内存地址传递给函数参数的一种调用函数的方式,该方式让函数和函数外边的变量建立起真正的联系,即函数内部可以直接操作函数外部的变量。
4.1.1.1.3 练习
注意练习三数组的传送;
void function1(int x)
{
int i = 0;
for ( i=2; i < x; i++)
{
if (x % i == 0)
{
printf("%d不是素数\n",x);
break;
}
}
if (i == x)
printf("%d是素数\n",x);
}
int function2(int x)//最好不要在函数中打印
{
//判断闰年
if ((x % 4 == 0 && x % 100 != 0) || (x % 400 == 0))
{
return 1;
}
else
return 0;
}
//本质上,这里的arr是一个指针,因此要计算数组元素的个数,需要在主函数中计算再传输进自定义的函数
int function3(int arr[], int k , int sz)
{
int l = 0;
int r = sz- 1;
while (l<=r)
{
int mid = (l + r) / 2;
if (arr[mid] < k)
{
l = mid + 1;
}
else if (arr[mid] > k)
{
r = mid - 1;
}
else
{
return mid;
}
}
return -1;
}
void function4(int* p)
{
(*p)++;//++的级别比较高
}
int main()
{
//练习1:写一个函数可以判断一个数是否是素数:
//int a = 0;
//while(1)
//{
//scanf("%d", &a);
//function1(a);
//}
//练习2:判断某年是不是闰年:
//int year = 0;
//scanf("%d", &year);
//if(function2(year)==1)
// printf("%d是闰年\n", year);
//else
// printf("%d不是闰年\n",year);
//练习3:写一个函数实现一个整型有序数组的二分查找
//如果找到返回下表,否则返回-1
//int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
//int k =17;
//int sz = sizeof(arr)/sizeof(arr[0]);
//int ret=function3(arr,k,sz);//数组在传参时,仅仅传过去的是第一个元素的地址
//if (ret == -1)
//{
// printf("找不到指定的数字\n");
//}
//else
//{
// printf("下标是%d", ret);
//}
//练习4:每调用一次函数,就会将num的值加1
int num = 0;
function4(&num);
printf("num=%d\n", num);
function4(&num);
printf("num=%d\n", num);
function4(&num);
printf("num=%d\n", num);
function4(&num);
printf("num=%d\n", num);
return 0;
}
4.2 函数的嵌套调用
void new_line()
{
printf("hehe\n");
}
void three_line()
{
int i = 0;
for (; i < 3; i++)
{
new_line();
}
}
int main()
{
//函数嵌套
three_line();
return 0;
}
4.3 函数的链式访问
把一个函数的返回值作为另一个函数的参数。
int main()
{
//链式访问
//1
int len = strlen("abc");
printf("%d\n", len);
//2
printf("%d\n", strlen("abc"));
//3
printf("%d", printf("%d", printf("%d", 43))); //打印结果:4321
//printf的返回值是打印的字符的个数
return 0;
}
4.4 函数的声明和定义
- 函数声明:一般写在头文件中,先声明后使用
- 函数定义:交代函数功能怎么实现
//test.c
//一般来说声明放头文件,定义写在源文件,需要使用函数是就include
//函数声明
//int add(int x, int y);//可以放头文件里
#include"add.h"//引用自己的头文件时用双引号
int main()
{
int a = 10;
int b = 20;
printf("%d\n", add(a, b));
return 0;
}
//此时,add函数的定义写在主函数后,在使用时需要声明,可以放另一个文件
//int add(int x, int y)
//{
// int z = x + y;
// return z;
//}
//add.h
//函数声明
int add(int x, int y);//可以放头文件里
//add.c
//函数定义
int add(int x, int y)
{
int z = x + y;
return z;
}
4.5 函数递归
首先,给出下面的代码分享头文件的相关知识:
//test.c
#include"add.h"
#include<stdio.h>//计算机将该文件的所有代码全部拷贝
int main()
{
int a = 10;
int b = 20;
printf("%d\n", add(a, b));
return 0;
}
//add.h
#ifndef __add_H__//if not define 如果没有定义就为真,下面就进行定义
//如果有定义就为假,不再重新定义,可以防止1个头文件被重复引入多次
#define __add_H__//define
//函数声明
int add(int x, int y);
#endif
//add.c
int add(int x, int y)
{
return x + y;
}
什么是递归?程序调用自身的技巧就叫做递归;特点:把大事化小;
递归的两个必要条件:1、存在限制条件,当满足这个限制条件的时候,递归就不再继续;
2、每次递归调用之后会越来越接近这个限制条件。
递归常见错误:栈溢出(stackoverflow),这串英文还是一个网站,相当于知乎
函数的调用都是在栈区申请空间,函数若无休止调用就会导致栈溢出。
例子:
void print(int n)
{
if (n > 9)
{
print(n / 10);
}
printf("%d ", n % 10);
}
int main()
{
//练习1:接收一个整型数,按顺序打出该数字的每一位
unsigned int num = 0;
scanf("%d", &num);
//递归
print(num);
return 0;
}
如果输入123,则一共调用print函数三次,
print(123)->print(12)->print(1)->打印1->打印2->打印3,因此最后显示在屏幕上的结果为1 2 3
递归例子2
int len(char* arr)//arr这个指针变量接受的是地址
{
if (*arr != '\0')
return 1 + len(arr + sizeof(char));
else
return 0;
}
//递归
//len(bit)
//1+len(it)
//1+1+len(t)
//1+1+1+len(\0)
//1+1+1+0
int main()
{
//练习2:编写函数是不允许创建临时变量,求字符串的长度
char arr[] = "bit";
printf("%d", len(arr));//数组传参传的是第一个元素的地址
return 0;
}
递归与迭代:
练习3:
#include<stdio.h>
int Fac1(int n)
{
int ret = 1;
for (int i = 1; i <= n; i++)
{
ret = ret * i;
}
return ret;
}
int Fac2(int n)
{
if (n <= 1)
return 1;
else
return n * Fac2(n - 1);
}
int main()
{
//练习3:求n的阶乘
int n = 0;
int ret = 0;
scanf("%d",&n);
//ret=Fac1(n);//循环求阶乘
ret = Fac2(n);//递归求阶乘
printf("%d!=% d",n, ret);
return 0;
}
练习4:
#include<stdio.h>
int Fib1(int n)
{
if (n <= 2)
return 1;
else
return Fib1(n - 1) + Fib1(n - 2);//效率极低,有大量重复计算
}
int Fib2(int n)
{
int a = 1;
int b = 1;
int c = 1;
while (n>2)
{
c = a + b;
a = b;
b = c;
n--;
}
return c;
}
int main()
{
//练习4:求第n个斐波那契数(1 1 2 3 5 8 11 ......
//前两个数之和等于第三个数
int n = 0;
scanf("%d", &n);
//printf("%d", Fib1(n));//递归的方法
printf("%d", Fib2(n));//循环的方法
return 0;
}
题目四使用循环方式最佳。
五、数组
5.1、一维数组的创建和初始化
5.1.1、数组的创建
//数组的创建
int arr[10];//数组类型 数组名[元素个数] , []里面必须是常量
5.1.2、数组的初始化
//数组的初始化
char arr1[10] = "kxr";//不完全初始化,即没放满,剩下的元素默认为0
char arr3[10] = { 'a','b' };
int arr2[10] = { 1,21,1 };
中括号内也可以不指定数组的大小。
5.1.3、sizeof()和strlen()
注意:
char arr4[] = "abcdef";
printf("%d\n", sizeof(arr4));
//结果:7,sizeof是计算arr4所占空间的大小
//7个char类型的元素,大小为7*1=7
printf("%d\n", strlen(arr4));
//结果:6,strlen是求解字符串的长度
char str1[] = "abc";
char str2[] = { 'a','b','c' };
printf("%d,%d,%d,%d", sizeof(str1), sizeof(str2), strlen(str1), strlen(str2));
//4,3,3,15(15是随机值
//strlen只能用于字符串
5.1.3、一维数组的使用
//一维数组的使用
char str3[] = "abcdef";//数组下表从0开始
printf("%c\n", str3[3]);//打印第四个元素,其下标为3
//打印该数组所有元素
int i = 0;
for (i = 0; i < (int)strlen(str3); i++)
{
printf("%c ", str3[i]);
}
5.1.4、一维数组在内存中的存储
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int sz = sizeof(arr) / sizeof(arr[0]);//计算数组元素个数
int i = 0;
for (i = 0; i < sz; i++)
{
printf("&arr[%d]=%p\n",i, & arr[i]);//%p打印地址
}
//结果:
//&arr[0] = 008FF8F8
//& arr[1] = 008FF8FC
//& arr[2] = 008FF900
//& arr[3] = 008FF904
//& arr[4] = 008FF908
//& arr[5] = 008FF90C
//& arr[6] = 008FF910
//& arr[7] = 008FF914
//& arr[8] = 008FF918
//& arr[9] = 008FF91C
//说明数组中的元素在内存中是连续存放的
return 0;
}
5.2、二维数组的创建和初始化
//二维数组的创建和初始化
int arr[3][4] = {1,2,3,4,5};//3行4列,元素是先放满一行,再放下一行
int arr1[3][4] = { {1,2,3},{4,5} };//还可以这样初始化
int arr2[][4] = { {1,2,3},{4,5} };//列不能省略
打印二维数组
//二维数组的使用
//打印二维数组
int arr[3][4] = { {1,2,3,4},{5,6,7,8} };
for (int i = 0; i < 3; i++)
{
int j = 0;
for (j= 0;j< 4; j++)
{
printf("%d ", arr[i][j]);
}
printf("\n");
}
5.3、二维数组的存储
int main()
{
//二维数组在内存中的存储也是顺序存储的,和一维数组一样
int i = 0;
int arr[3][4] = { {1,2,3,4},{5,6,7,8} };
for (int i = 0; i < 3; i++)
{
int j = 0;
for (j= 0;j< 4; j++)
{
printf("&arr[%d][%d] = %p\n",i,j, &arr[i][j]);
}
}
return 0;
}
5.4、数组作为函数参数
5.4.1、冒泡排序
相邻两个数字重复比较,10个元素就需要9躺冒泡排序,每趟比较9对数字
#include<stdio.h>
void bubble_sort(int* arr,int sz)
{
//确定冒泡排序的趟数,即数组元素个数
int i = 0;
int mid = 0;
for (i = 0; i < sz - 1; i++)
{
int flag = 1;//假设数组已经满足升序
int j = 0;
for (j = 0; j < sz - i-1; j++)
{
if (arr[j] > arr[j + 1])
{
mid = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = mid;
flag = 0;
}
}
if (flag == 1)
break;//经过一趟排序,不存在数据交换,表示数组已经有序
}
}
int main()
{
int arr[] = { 9,8,7,6,5,4,3,2,1,0 };
int sz = sizeof(arr) / sizeof(arr[0]);
//对arr数组进行冒泡排序,升序
bubble_sort(arr,sz);//对数组arr进行传参,只是传去首地址
//打印排序后的数组
for (int k = 0; k < sz; k++)
printf("%d ", arr[k]);
return 0;
}
5.4.2、数组名是什么
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
printf("%p\n", arr);
printf("%p\n", &arr[0]);
//执行结果相同,说明数组名就是首元素地址
printf("%d\n", *arr);
printf("%p\n", &arr);//取出的是数组的地址,与首元素地址相同,
//结果相同但意义不同,首元素地址加1指向第二个元素的地址,数组地址加1指向下一个数组的地址
//但需要注意以下两种情况
int sz = sizeof(arr) / sizeof(arr[0]);
//1、此处的 sizeof(arr) 计算的是整个数组的大小,单位是字节
//2、&数组名,取出的是整个数组的地址
return 0;
}
5.5、数组的应用实例
5.5.1、三子棋
代码见上传的文件
5.5.2、扫雷
代码见上传的文件
六、操作符
6.1 除法和取模
取模的左右操作数只能是整数
//操作符:/ %
int a = 5 / 2;//商2余1,结果为2
printf("a=%d\n", a);
// 由于/两边都是整数,那么结果也是证整数
double c = 5 / 2.0;
printf("c=%lf\n", c);//2.5
//对于取模运算来说左右操作数都必须是整数
int b = 5 % 2;//%得到的是余数
printf("b=%d\n", b);
6.2 移位操作符
int main()
{
int a = -1;
//负数是用补码存储的
//10000000 00000000 00000000 00000001(原码
// 11111111 11111111 11111111 11111110(反码
// 11111111 11111111 11111111 11111111(补码
//>>右移操作符:移动的是二进制位
//1、算术右移
// 右边丢弃,左边补原符号位
//2、逻辑右移
//右边丢弃,左边补零
int b=a >> 1;
printf("b=%d", b);//结果:-1,说明是算术右移
return 0;
}
6.3 sizeof
int main()
{
//int a = 0;
//char b = 'w';
//int arr[10] = { 0 };
//printf("%d\n", sizeof(a));
//printf("%d\n", sizeof a );
//printf("%d\n", sizeof(int));
//printf("%d\n", sizeof(b));
//printf("%d\n", sizeof(char));
//printf("%d\n", sizeof(arr));//10*4
//printf("%d\n", sizeof(int [10]));//数组的类型
short s = 0;
int a = 10;
printf("%d\n", sizeof(s = a + 5));//sizeof中的表达式不参与运算
printf("%d\n", s);
return 0;
}
6.4 位操作符
//按位取反~,按二进制位取反,对于负数的符号位也要取反
int a = 0;
//00000000 00000000 00000000 00000000
//11111111 11111111 11111111 11111111(补码)
//11111111 11111111 11111111 11111110
//10000000 00000000 00000000 00000001(原码)
//-1
printf("%d\n", ~a);
//按位或|和按位或&
int b = 11;
printf("%d\n", (1 << 2 )|b);
printf("%d\n", (~(1 << 2)) & b);
6.5 单目操作符
//int a = 10;
前置++,先++,后使用
//printf("%d\n", ++a);//11
后置++,先使用,后++
//printf("%d\n", a++);//11
//printf("%d\n", a);//12
//强制类型转换
int a = (int)3.14;
6.6 关系操作符
省略
6.7 逻辑操作符
//逻辑操作符
//逻辑与&&、逻辑或||
//真假判断
int a = 3;
int b = 5;
int c = a && b;
printf("%d\n", c);//1
int d = 0;
printf("%d\n", a || d);
//如果逻辑与&&操作符第一个数为假接下来的就不用计算了
int i = d && a++ && b && c;
printf("%d\n", a );//a=3
//如果逻辑或||操作符第一个数为真接下来的就不用计算了
int j = (d+1) || a++ || b || c;
printf("%d\n", a);//a=3
6.8 条件操作符
//条件操作符:exp1?exp2:exp3
int b;
int a = 6;
int c=(a > 5) ? (b = 3) : (b = -3);//a>5为真,b=3;反之b=-3
printf("%d\n", c);//3
6.9 逗号表达式
//逗号表达式:exp1,exp2,exp3......
//从左往右依次执行,整个表达式的结果就是最后一个表达式的结果
int a = 1;
int b = 2;
int c = (a > b, a = b + 10, a, b = a + 1);//13
printf("%d\n", c);
6.10 函数调用和结构成员
//函数调用操作符
int a = 10;
int b = 20;
//调用函数时用的()就是函数调用操作符,get_max、a、b都是操作数
int max = get_max(a, b);//get_max是自定义函数
printf("max=%d\n", max);
//访问结构体成员
//创建一个结构体类型-struct stu
struct stu
{
//成员变量
char name[20];
int age;
char id[20];
};
int main()
{
//使用structstu创建了一个学生对象s1,并初始化
struct stu s1 = { "张三",20,"12128" };
printf("%s\n", s1.name);
printf("%d\n", s1.age);
printf("%s\n", s1.id);
//结构体变量.成员名
printf("\n");
struct stu* ps=&s1;
printf("%s\n", ps->name);
printf("%d\n", ps->age);
printf("%s\n", ps->id);
//结构体指针变量->成员名
return 0;
}
6.11 表达式求值
1、 隐式类型转换
整型提升:为了获得精度,表达式中的字符和短整型操作数在使用前会被转换成普通整型;
如何进行整型提升?按照变量的数据类型的符号位。
//整型提升:为了获得精度,表达式中的字符和短整型操作数在使用前会被转换成普通整型,这种转换称为整型提升
char a = 3;
//00000000 00000000 00000000 00000011
//00000011 char是一个字节,只能放8个比特位,会发生截断
char b = 127;
//同上:01111111
char c = a + b;
//a+b
//整型提升的过程:(变整型)
//00000000 00000000 00000000 00000011
//00000000 00000000 00000000 01111111
//相加:
//00000000 00000000 00000000 10000010
//c-10000010=-126
printf("%d\n", c);//c=-126
//整型打印,需要再次整型提升
//11111111 11111111 11111111 10000010 - 补码
//11111111 11111111 11111111 10000001 - 反码
//10000000 00000000 00000000 01111110 - 原码 - -126
算术运算才会发生整型提升
算术转换:进行运算时,操作数类型不同,就会进行算术转换,操作数类型向排名高的转换。
排名:
long double
double
float
unsigned long int
long int
unsigned int
int
2、 操作符的属性
操作符的优先级
// 操作符的优先级
int a = 10;
int b = 20;
int c = b + a * 3;
//优先级高的先算
//运算符混合使用时要尽量避免产生歧义
七、指针
7.1 指针是什么?
通过指针,可以找到以指针为地址的内存单元。
一个内存单元的大小是一个字节。
存放在指针的值会被当作地址来处理。
在32位机器上,一个指针变量的大小是4个字节;
在64位机器上,一个指针变量的大小是8个字节.
指针大小都一样,为什么还要区分类型?
指针类型决定了指针进行解引用操作的时候能够访问空间的大小。
int* p:p能够访问4个字节;double p:p能够访问8个字节
指针类型决定了指针走一步走多远,即指针的步长(字节)。
int p:p+1–>4;
char* p:p+1–>1;
double* p:p+1–>8.
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
//int main()
//{
// int a = 10;
// int* p = &a;//指针变量
// //指针就是地址,地址就是指针
//
//
// return 0;
//}
//int main()
//{
// //printf("%d\n", sizeof(char*));//4
// //printf("%d\n", sizeof(int*));//4
// int a = 0x11223344;
// int* pa = &a;
// char* pc = &a;
// printf("%p\n", pa);
// printf("%p\n", pa+1);//整型指针时,+1是加4个字节
// printf("%p\n", pc);
// printf("%p\n", pc+1);//字符型指针,+1是加1个字节
// //char* pc = &a;//会警告
// //printf("%p\n", pa);
// //printf("%p\n", pc);
// return 0;
//}
int main()
{
int arr[10] = { 0 };
int* p = arr;//数组名--首元素地址
int i = 0;
for (i = 0; i<10; i++)
{
*(p + i) = 1;
printf("%d\n", arr[i]);
}
return 0;
}
7.2 野指针
野指针就是指针指向的位置是不可以知道的。
产生野指针的原因:
1、指针未初始化;
//1、指针未初始化;
int a;//局部变量不初始化,默认为随机值
//全局变量默认为0
int* p;//随机地址
*p = 20;//
2、指针越界
//2、指针越界
int arr[10] = { 0 };
int* p = arr;
int i = 0;
for (i = 0; i < 12; i++)
{
p++;//越界
}
3、指针指向的内存空间被释放
//3、指针指向的内存空间被释放
int* p = test();//a被用完后空间就释放了
*p = 20;
如何规避野指针?
- 指针要初始化
- 小心指针越界
- 指针指向空间释放即使置NULL
- 指针使用之前检查其有效性
int* p = NULL;//NULL是用来给指针赋值的
if (p != NULL)
{
//判断其是否为空
}
7.3 指针运算
int main()
{
//指针运算
//1、指针+-整数
int arr[10] = { 1, 2 ,3 ,4 ,5 ,6 ,7 ,8 ,9 ,10};
int i = 0;
for (; i < 10; i++)
{
printf("%d ", *(arr+i));
}
//2、指针-指针
int arr2[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("\n%d\n", &arr2[9] - &arr2[0]);//指针减去指针等于中间元素的个数
//3、指针的关系运算
//标准规定:
//允许指向数组元素的指针于指向数组最后一个元素后面的那个内存位置的指针比较,但不允许于指向第一个元素之前的那个内存位置的指针进行比较
return 0;
}
插播、 strlen的实现
int my_strlen(char* str)
{
char* start = str;
char* end = str;
while (*end != '\0')
{
end++;
}
return end - start;
}
int main()
{
//实现strlen
char arr[] = "bit";
int len = my_strlen(arr);
printf("%d\n", len);
return 0;
}
7.4 指针与数组
部分情况下数组名是首元素的地址。
int main()
{
//数组和指针
//在绝大部分情况下,数组名就是首元素地址
int arr[10] = { 0 };
printf("%p\n", arr);//首元素地址
printf("%p\n", arr+1);
printf("%p\n", &arr[0]);//首元素地址
printf("%p\n", &arr[0]+1);
printf("%p\n", &arr);//整个数组的地址
printf("%p\n", &arr+ 1);
//1、&arr-&a数组名-数组名不是首元素地址-数组名表示整个数组,&数组名取出的整个数组的地址
//2、sizeof(arr)-sizeof(数组名),数组名表示整个数组,计算的是整个数组的大小
return 0;
}
7.5 二级指针
int a = 10;
int* pa = &a;
int* * ppa=&pa;//ppa就是二级指针
//int*** pppa = &ppa;//三级指针
printf("%d\n", **ppa);