目录
废话不多说直接上干货!
一、hello world!程序说明
#include <stdio.h>
int main(int argc, const char *argv[])
{//{}里面包住的部分叫做函数体,就是我们实际要执行的代码
printf("hello world!\n"); //C语言中每条指令结束 都需要加 分号;
//printf 是系统给我们提供的输出的函数
//功能就是将 "" 内的内容打印到终端 \n 表示换行的意思 就是回车
return 0; //函数的返回值
}
1.C语言中以#开头的行 称为预处理行 是在预处理阶段处理的
2. #include 是包含头文件的关键字
3.stdio.h 标准输入输出的头文件 我们的使用的printf就在这个头文件里
4.int 函数的返回值类型
5.main 主函数 是程序的入口 每个程序必须有且仅有一个
6.() 里面是main函数的参数 先不用管 ()里面的内容可以不写 但是 ()必须写。可以写成 int main()
7.()圆括号 []方括号 {}花括号
二、计算机中数据的存储
计算机中数据的存储分为两大部分:
(1)数值型数据 520 1314
(2)非数值型数据 "www.baidu.com" "百度"
2.1数值型数据的存储
2.1.1 十进制
在C语言中,没有特殊说明和前导符时,默认的都是10进制数据
特点:逢10进1 每一位上的数字范围 [0, 9]
例如:520 1314
十进制转二进制
使用除2取余法:用十进制数除以2,保留商和余数,然后再用商继续除以2,保留商和余数,依次类推直到商为 0,将得到的余数 倒序取出 就是对应的二进制数了。
2.1.2 二进制
前导符 0b
特点:逢2进1 每一位上的数字 只能是 0 或者 1
例如:0b1110 0b1100
二进制转十进制:
从右到左算
0b1110 == 0*2^0 + 1*2^1 + 1*2^2 + 1*2^3 == 0 + 2 + 4 + 8 = 14
其他进制转换十进制都可以使用这种方式,只不过将底数的2换成对应进制的数字即可
也可以使用8421的方式转换
1000 --> 8
0100 --> 4
0010 --> 2
0001 --> 1
2.1.3 八进制
前导符 0 (是数字零 不是字母o)
特点:逢8进1 每一位上的数字范围 [0,7]
例如:0456 0666 0765
八进制转二进制:
方法1:八先转十 然后十转二
方法2:从右向左,1位八进制数表示 3位二进制数
0735 -->0b111011101
二进制转八进制:
从右向左,每3位二进制数表示 1位八进制数,高位不够补0
0b010110101 --> 0265
2.1.4 十六进制
前导符 0x
特点:逢16进1 每一位上的数字范围 0~9 a10 b11 c12 d13 e14 f15
例如:0x4F5D 0x1234 0xABCD
十六进制转二进制:
方法1:十六先转十 然后十转二
方法2:从右向左,1位十六进制数表示 4位二进制数
0x45EF -->0b0100010111101111
二进制转十六进制:
从右向左,每4位二进制数表示 1位十六进制数,高位不够补0
0b001010110101 --> 0x2B5
注意:不管几进制的数据,在计算机中都会转换成二进制来处理。
例如:
#include <stdio.h>
int main(int argc, const char *argv[])
{
//int 是数据类型 是用来定义变量的
//value 是我自己起的变量名 变量可以用来存储数据
int value;
value = 520;//用十进制的 520 给变量赋值
printf("十进制 %d\n", value);// %d 是十进制的占位符
printf("八进制 %#o\n", value);// %o 是八进制的占位符 #表示输出前导符
printf("十六进制 %#x\n", value);// %x 是十六进制的占位符 #表示输出前导符
//printf 函数没有按照二进制输出的方式 等后面我们学了位运算 可以自己写一个
printf("-----------------\n");
value = 0b10100011;//用二进制数 给变量赋值
printf("十进制 %d\n", value);
printf("八进制 %#o\n", value);
printf("十六进制 %#x\n", value);
printf("-----------------\n");
value = 0765;//用八进制数 给变量赋值
printf("十进制 %d\n", value);
printf("八进制 %#o\n", value);
printf("十六进制 %#x\n", value);
printf("-----------------\n");
value = 0xAB45;//用十六进制数 给变量赋值
printf("十进制 %d\n", value);
printf("八进制 %#o\n", value);
printf("十六进制 %#x\n", value);
return 0;
}
运行结果:
2.2 非数值型数据的存储
计算机中只能处理二进制的数值型数据,而在编码的过程中,经常也会出现一些非数值型的数据
如:网址、人名、企业名 等.
所以科学家们就发明一种叫 ascii码 的东西,专门用来表示非数值型数据
在C语言中遇到的 所有被单引号 或者 双引号引起来的 都是非数值型数据 'm' 'H' "csdn"
linux系统中可以使用 man ascii 来查看ascii码
常见的字符对应的ascii码:
'\0' --> 0
'\n' --> 10
空格 SPACE --> 32
'0'~'9' --> 48~57
'A'~'Z' --> 65~90
'a'~'z' --> 97~122
转义字符:
在C语言中,任何一个字符都可以使用一个 '\'+八进制数 来表示
C语言中还定义了一个 '\' + 字母 来表示一些无法显示的字符
这些字符就叫做转义字符,因为字母已经不是本身的含义了
注意 : 转义字符虽然有两个符号 (一个反斜杠 和 一个字母),但是转义字符也是一个字符。
三、词法符号
3.1.关键字
所谓的关键字就是编译器已经规定好的一些单词,直接使用即可,不用提前声明或者定义。
一.数据类型关键字:
1 char //声明字符变量
2 double //声明双精度变量
3 float //声明浮点型变量
4 int //声明整型变量
5 short //声明短整型变量
6 long //声明长整型变量
7 unsigned //声明无符号类型变量
8 signed //声明有符号类型变量
9 struct //声明结构体变量
10 union //声明共用体或联合数据类型
11 void //声明函数无返回值或无参数,声明无类型指针
12 enum //声明枚举类型
二.循环语句关键字:
1、循环语句类型关键字(5个)
1 for //遍历循环
2 do //其后紧跟循环体
3 while //条件循环或死循环
4 break //跳出当前循环
5 continue //终止本次循环,开始下次循环
2、条件语句类型关键字(3个)
6 if //条件语句
7 else //条件语句否定分支
8 goto //无条件跳转语句
3、开关语句类型关键字 (3个)
9 switch //用于多条件判断语句
10 case //多条件判断语句分支
11 default //开关语句的其它分支
三、存储类型关键字
1 auto //声明自动变量
2 extern //声明变量是在其他文件定义
3 register //声明寄存器变量
4 static //声明静态变量
四、其它关键字
1 onst //声明只读变量
2 sizeof //计算数据类型长度(字节数)
3 typedef //给数据类型取别名
4 volatile //所修饰的对象不能被编译器优化
3.2 标识符
标识符就是我们自己起的名字,如 变量名 函数名 结构体名 共用体名 等。。。
标识符的命名规范:
1.只能由数字、字母、或者下滑线组成
2.不能以数字开头
3 .不能和关键字冲突
自己起的名字尽量做到望文知意。
四、数据类型
4.1 概念
C语言的本质是操作内存。
内存和硬盘的区别:
内存:数据访问快 掉电丢失
硬盘:数据访问慢 掉电不丢失
C语言内存分配的最小单位是 字节 B
数据类型的作用:
相当于与一个模子,告诉操作系统需要给该类型定义的变量分配多大的内存空间。
不同的数据类型占用的内存空间大小是不一样的,
占 用的内存空间大小不同,决定了能存储的数据范围也不同。
4.2 数据类型的分类
基本类型:
整数类型
实型(小数)
枚举类型
构造类型:
数组
结构体
共用体
指针类型:*
空类型:void
4.3 整数类型
整数类型又细分为 char(字符类型) short(短整型) int(整型) long(长整型) long long(长长整型)
都是整数类型,区别是占用的内存空间大小不同,能存储的数据范围不同
上面的每个类型又分为有符号(signed)的和无符号(unsigned)的
对于有符号数 最高位为符号位 剩下的叫数据位
符号位为0 正数 符号位为1 负数
如果不写有无符号,默认的都是有符号的
4.3.1 char
占用内存空间的大小 1字节 8bit
存储数据的范围
无符号:0 ~ 2^8-1
有符号:-2^7 ~ 2^7-1
有符号数说明:
符号位 数据位
7 6 5 4 3 2 1 0
0 1 1 1 1 1 1 1 --> 2^7-1 最大值
1 0 0 0 0 0 0 0 --> -2^7 最小值
4.3.2 short
占用内存空间的大小 2字节 16bit
存储数据的范围
无符号:0 ~ 2^16-1
有符号:-2^15 ~ 2^15-1
4.3.3 int
占用内存空间的大小 4字节 32bit
存储数据的范围
无符号:0 ~ 2^32-1
有符号:-2^31 ~ 2^31-1
4.3.4 long
写可以写成 long int 输出占位符 %ld (小写的L)
在32位操作系统中 和 int 一样
在64位操作系统中 和 long long 一样
4.3.5 long long
写可以写成 long long int 输出占位符 %lld (小写的L)
占用内存空间的大小 8字节 64bit
存储数据的范围
无符号:0 ~ 2^64-1
有符号:-2^63 ~ 2^63-1
4.4 实型数据的存储
实型数据就是小数,也叫浮点型。 如:3.14
单精度浮点型 float 4字节
双精度浮点型 double 8字节
五、原码、反码、补码的问题
数据在存储的时候设计到原码、反码和补码相互转换的问题。
原码:是给人类看的
反码:用来做原码和补码转换的
补码:是给计算机看的
无符号数:原码、反码、补码都是一样的
有符号的正数:原码、反码、补码都是一样的
有符号的负数:
反码 = 原码的符号位不变,其他位按位取反 0变1 1变0
补码 = 反码+1
注意:以char为例,1000 0000 规定为 -128 的补码
小技巧:存储时看数据(正负) 取出看类型(有无符号)
#include <stdio.h>
int main(int argc, const char *argv[])
{
unsigned char a = 10;//正常
//存储时:
//原码: 0000 1010
//反码: 0000 1010
//补码: 0000 1010
//取出时:
//补码: 0000 1010
//反码: 0000 1010
//原码: 0000 1010 --> 10
printf("a = %d\n", a);//10
signed char b = -10;//正常
//存储时:
//原码: 1000 1010
//反码: 1111 0101
//补码: 1111 0110
//取出时:
//补码: 1111 0110
//反码: 1111 0101
//原码: 1000 1010 --> -10
printf("b = %d\n", b);// -10
signed char c = 129;//异常
//存储时:
//原码: 1000 0001
//反码: 1000 0001
//补码: 1000 0001
//取出时:
//补码: 1000 0001
//反码: 1000 0000
//原码: 1111 1111 --> -127
printf("c = %d\n", c);// -127
unsigned char d = -1;//异常
//存储时:
//原码: 1000 0001
//反码: 1111 1110
//补码: 1111 1111
//取出时:
//补码: 1111 1111
//反码: 1111 1111
//原码: 1111 1111 --> 255
printf("d = %d\n", d);// 255
return 0;
}
六、常量
6.1 概念
在整个程序运行的过程中,值不会发生变化的量。
520 1314 3.14 "csdn"
6.2 常量的分类
整型常量:
前导符 输出占位符 例如
二进制 0b 无 0b1010
八进制 0 %#o 0765
十进制 无 %d 1314
十六进制 0x %x 0xAB34
浮点型常量(实型):
float %f 3.14
double %lf(小写的L)5.28
字符常量 %c 'M' '8' 单引号
字符串常量 %s "www.baidu.com" 双引号
指数常量 %e 3.14e2 ---> 3.14 * 10^2 --> 314
标识常量 宏定义 #define
6.2.1 整型常量
#include <stdio.h>
int main(int argc, const char *argv[])
{
//常量一般是用来给变量赋值的
short v1 = 520;
printf("v1 = %d\n", v1);//520
long int v2 = 1314;
printf("v2 = %ld\n", v2);//1314
long long int v3 = 0xAB34;
printf("v3 = %#llx\n", v3);//0xab34
return 0;
}
6.2.2 浮点型常量
程序中出现的小数,都是十进制小数。
一般形式(带有小数位的):
单进度 float 双精度 double
3.14 5.28
指数形式
格式: [+/-]M.Ne[+/-]T
如: -3.14e-2 --> -3.14*10^(-2) --> -0.0314
#include <stdio.h>
int main(int argc, const char *argv[])
{
float f1 = 3.1415926;
printf("f1 = %f\n", f1);// 3.141593 默认显示6位小数 超过的部分 四舍五入
//也可以使用 %.nf 的方式 来指定显示n位小数
printf("f1 = %.2f\n", f1);// 3.14
//如果是double 占位要用 %lf
double f2 = 3.1415926;
printf("f2 = %lf\n", f2);// 3.141593 默认也是显示6位小数
printf("f2 = %.2lf\n", f2);// 3.14
//指数形式
float f3 = 314;
printf("指数: f3 = %e\n", f3);// 3.140000e+02 可以按指数形式输出
//也可以使用指数常量给变量赋值
float f4 = -5.67e-3;
printf("f4 = %f\n", f4);//-0.005670
return 0;
}
6.2.3 字符常量
C语言中所有的字符常量必须用 单引号 引起来,且单引号中只能引一个字符。
转义字符算一个字符
例如:'m' 'H' '8' '\n' '\0' 注意 '8' 和 8 是不一样的
输出方式:
想输出对应的字符 %c
想输出字符对应的ascii码 %d
#include <stdio.h>
int main(int argc, const char *argv[])
{
//定义一个字符类型的变量 用来保存 字符常量 'H'
char v1 = 'A';
printf("v1 = [%d] [%c]\n", v1, v1);// 65 A
//常量也可以直接输出 但是一般不这样用
printf("[%d] [%c]\n", 'B', 'B');// 66 B
printf("[%d] [%c]\n", '\n', '\n');// 10 换行
printf("[%d] [%c]\n", 10, 10);// 10 换行
//字符常量也可以参与运算 --本质就是 ascii码的运算
char v2 = 'M'+1; //v2中实际存储的是 'N' 的ascii码
printf("v2 = [%d] [%c]\n", v2, v2);// 78 N
char v3 = 67;//使用字符的ascii码给变量赋值也可以
printf("v3 = [%d] [%c]\n", v3, v3);// 67 C
//上述例子中可以得到结论:
//字符就是整形 整形就是字符 具体看我们怎么用
//思考
//如何将 'M' 转换成 'm' ?
char v4 = 'M';
//v4 = v4+32;
v4 = v4+'a'-'A';
printf("v4 = [%d] [%c]\n", v4, v4);// 109 m
//思考
//如何将 '8' 转换成 8 ?
char v5 = '8';
//v5 = v5 - 48;
v5 = v5 - '0';
printf("v5 = %d\n", v5);//8
return 0;
}
6.2.4 字符串常量
字符串常量需要用 双引号 "" 引起来
"www.baidu.com" "1234"
每个字符串常量结尾都会有一个隐藏的字符 '\0' 用来标识字符串结束的
也就是说 "hello" 字符串实际要占用6个字节的内存空间
注意下面几个用法
a 变量a
'a' 字符a
"a" 字符串a
将数据按照字符串方式输出,使用的占位符是 %s
#include <stdio.h>
int main(int argc, const char *argv[])
{
//可以将字符串直接输出
printf("hello\n");
printf("%s\n", "hello");
//也可以将字符串保存在字符数组中
char str[32] = "www.baidu.com";//--数组的用法 后面讲
printf("str = [%s]\n", str);
//也可以定义一个指针直接指向字符串常量
char *p = "beijing"; //--指针的用法 后面讲
printf("p = %s\n", p);
//C语言中对字符串的处理 遇到 '\0' 就结束了
char *p2 = "bei\0jing";
printf("p2 = %s\n", p2);//bei
return 0;
}
6.2 .5标识常量 --宏定义
宏定义就是给常量表达式起一个别名,使用这个名字,就相当于使用这个常量表达式,
如果常量表达式修改了,程序中所有使用这个名字的地方都会发生改变。
这个名字是一个标识符,要符合标识符的命名规范,宏定义的名字一般都是大写的。
格式:
#define 宏名 宏值
例如:
#include <stdio.h>
#define STUDENT 30 //当宏值发生变化时,所有使用宏名的地方都会变化
int main(int argc, const char *argv[])
{
printf("当前班级已有人数 %d\n", STUDENT);
printf("当前班级已有人数 %d\n", STUDENT);
printf("当前班级已有人数 %d\n", STUDENT);
return 0;
}
注意事项:
1.宏定义是在预处理阶段完成替换的;
2.宏定义只是一个简单的替换、是无脑替换;
#include <stdio.h>
#define M 10
#define N 20
#define SUM1 M+N
#define SUM2 (M+N)
int main(int argc, const char *argv[])
{
int ret = SUM1*4;
//int ret = M+N*4;
//int ret = 10+20*4;
printf("%d\n", ret);//90
int ret2 = SUM2*4;
//int ret2 = (M+N)*4;
//int ret2 = (10+20)*4;
printf("%d\n", ret2);//120
return 0;
}
七、变量
7.1 概念
在程序运行的过程中,值允许发生改变的量。
变量的大小:取决于定义变量的类型
变量的名字:是一个标识符,要符合标识符命名规范
定义变量的格式:
存储类型 数据类型 变量名;
存储类型:
const static extern register volatile auto
局部变量不写存储类型默认的都是 auto
7.2 变量的初始化和赋值
#include <stdio.h>
int main(int argc, const char *argv[])
{
//初始化:在定义变量的同时,给变量赋一个初值
int v1 = 1314;
printf("v1 = %d\n", v1);//1314
//如果定义变量没有初始化 里面就都是随机值
int v2;
printf("v2 = %d\n", v2);//不确定的值
//如果定义变量不知道用谁初始化 可以使用0来初始化
//v3 = 520;//错误的 变量需要先定义 才能使用
int v3 = 0;
printf("v3 = %d\n", v3);//0
//变量是可以被重新赋值的
v3 = 520;// = 赋值运算符 将右边的值赋值给左边
printf("v3 = %d\n", v3);//520
v3 = v1;//也可以使用变量给变量赋值
printf("v3 = %d\n", v3);//1314
//变量之间可以做运算
v2 = v1+v3;
printf("v1 = %d v2 = %d v3 = %d\n", v1, v2, v3);//1314 2628 1314
//一行中可以定义多个变量 用逗号分隔
int v4,v5,v6;
//同一个作用域(同一个{})内 不允许定义重名的变量
//int v4; //错误的 重名了
return 0;
}
73 强制类型转换
在某次运算中,通过某种方式将变量的类型转换成其他类型来参与本次运算。
变量本身的类型不会改变,只是在本次运算中使用其他类型参与运算了。
强制类型转换简称 强转
注意:强制类型转换是不安全的,要谨慎使用。
小的类型转大的类型一般没问题
但是大的类型转小的类型就可能会出现数据的截断和丢失
17.3.1 显式强转
格式:
(新的类型)变量名;
#include <stdio.h>
int main(int argc, const char *argv[])
{
int m = 5;
int n = 2;
float ret1 = m/n;//这种写法 因为 m 和 n本身是 int类型 所以运算的结果也是int类型
//m/n 得到的结果就是 2 相当于把2赋值给 ret1
printf("ret1 = %f\n", ret1);//2.000000
//将 m 和 n 的类型转换成 float 来参与本次运算 得到的结果就是 float 类型的
//相当于 把 2.500000 赋值给 ret2
float ret2 = (float)m/(float)n;
printf("ret2 = %f\n", ret2);//2.500000
//m和n的类型不会改变
printf("m = %d n = %d\n", m, n);//5 2
return 0;
}
7.3.2 隐式强转
隐式强转是编译器根据上下文自动推导的,
如果编译器觉得本次转换是安全的,则正常执行
如果编译器觉得是有风险的,一般会报一个警告,具体报不报取决于编译器的严谨程度。
#include <stdio.h>
int main(int argc, const char *argv[])
{
float f = 3.1415;
int m = f;//这种操作是 取整 的操作 会舍弃小数部分
printf("f = %f m = %d\n", f, m);//3.141500 3
return 0;
}
注意:
有符号数和无符号数参与运算时,会将有符号数转换成无符号数参与运算。
#include <stdio.h>
int main(int argc, const char *argv[])
{
signed int a = -20;
unsigned int b = 10;
if(a+b > 0){
printf("yes\n");//输出的是
}else{
printf("no\n");
}
//不要尝试保存 a+b 的结果
//因为如果使用 signed int 保存 相当于把结果隐式强转成 有符号了
//如果使用 unsigned int 保存 虽然结果是无符号的
//但是就没法说明 是在运算的过程中 将有符号数转换成 无符号数了
//实际开发过程中不会出现这种场景
return 0;
}
八、运算符
8.1 概念
运算符就是一个符号,是用来多个值之间进行运算的。
运算符可以连接多个表达式。
所谓的表达式就是由 运算符 运算量 标点符号 等组成的一个有效序列,
是用来说明一个运算过程的。
8.2 运算符的分类
算数运算符: + - * / %(模除,就是取余数的意思)
自增自减运算符:++ --
关系运算符: > < >=
逻辑运算符: && || !
位运算符: & | ^ ~ >
条件运算符(三目运算符): ?:
赋值运算符:= += -= *= ... (复合赋值运算符)
sizeof运算符
逗号运算符: (,,,) ----不常用
8.3 算数运算符
+ - * / %(模除,就是取余数的意思)
注意:模除要求左右操作数必须是整数。
#include <stdio.h>
int main(int argc, const char *argv[])
{
int m = 10;
int n = 3;
printf("m + n = %d\n", m + n);
printf("m - n = %d\n", m - n);
printf("m * n = %d\n", m * n);
printf("m / n = %lf\n", (double)m / (double)n);//整型做除法 需要强转
printf("m %% n = %d\n", m % n);//想输出% 需要敲两个%
double a = 5.28;
double b = 3.14;
printf("a + b = %lf\n", a + b);
printf("a - b = %lf\n", a - b);
printf("a * b = %lf\n", a * b);
printf("a / b = %lf\n", a / b);
//printf("a %% b = %lf\n", a % b);//% 要求左右操作数必须是 整数
return 0;
}
C语言中输入的函数, scanf
#include <stdio.h>
int main(int argc, const char *argv[])
{
int value = 0;
printf("请输入一个整数 : ");
//阻塞等待用户输入
scanf("%d", &value); // & 取地址符
printf("你输入的数据是 : %d\n", value);
return 0;
}
练习:
从终端输入1个三位数 [100,999]
输出 个位+十位+百位 求和的结果,
如:输入:135
则输出:1 + 3 + 5 = 9
#include <stdio.h>
int main(){
int num = 0;
printf("请输入一个三位数:");
scanf("%d", &num);
int g = num % 10;//个位
int s = num % 100 / 10;//十位
int b = num / 100;//百位
printf("%d + %d + %d = %d\n", b, s, g, b+s+g);
return 0;
}
8.4 自增自减运算符
++ --
a++ a = a+1
++a a = a+1
a-- a = a-1
--a a = a-1
注意:以自增运算符为例 前加加和后加加 变量的值都加了1 但是表达式的结果是不一样的。
a = 10;
b = ++a;
上述两行之后,a = 11, b = 11
a = 10;
b = a++;
上述两行之后,a = 11, b = 10
#include <stdio.h>
int main(int argc, const char *argv[])
{
int a = 10;
int b = ++a;//++a表达式的结果 是 加之后的值
printf("a = %d b = %d\n", a, b);//11 11
int m = 10;
int n = m++;//a++表达式的结果 是 加之前的值
printf("m = %d n = %d\n", m, n);//11 10
return 0;
}
8.5 关系运算符
> < >=
关系运算符一般是用于比较大小关系的
关系运算符表达式的结果是一个 bool 类型,bool类型只有两种值 0 假 非0 真
一般多用于控制语句中
注意:
= 和 == 的区别
= 赋值运算符
== 关系运算符 判断是否相等的
//if else 语句说明
if(表达式){
代码块1;
}else{
代码块2;
}
//执行逻辑,如果表达式为真 则执行 代码块1 否则执行代码块2
例如:
#include <stdio.h>
int main(int argc, const char *argv[])
{
int a = 0;
int b = 0;
scanf("%d%d", &a, &b);//scanf可以连续输入多个数 输入时以空格或回车区别
//其他关系运算符 用法同理
if(a > b){
printf("a>b\n");
}else{
printf("a<=b\n");
}
if(a == b){
printf("yes\n");
}else{
printf("no\n");
}
return 0;
}
8.6 逻辑运算符
逻辑运算符是用来连接多个由关系运算符组成的表达式的。
逻辑运算符的结果 也是一个 bool 类型。
&& 逻辑与 表示并且的意思
逻辑与连接的多个表达式,如果都为真,整个表达式的结果才为真
有一个表达式结果为假,整个表达式的结果就为假
|| 逻辑或 表示或者的意思
逻辑或连接的多个表达式,有一个为真,整个表达式的结果就为真
如果所有表达式都为假,整个表达式才为假
! 逻辑非 表示逻辑取反的意思
真变假 假变真
例:
#include <stdio.h>
int main(int argc, const char *argv[])
{
int a = 0;
int b = 0;
scanf("%d%d", &a, &b);
#if 0
//逻辑与
if(a >= 0 && b >= 0){
printf("yes\n");
}else{
printf("no\n");
}
#endif
#if 0
//逻辑或
if(a >= 0 || b >= 0){
printf("yes\n");
}else{
printf("no\n");
}
#endif
//逻辑非
if( !(a>b) ){
printf("yes\n");
}else{
printf("no\n");
}
return 0;
}
注意:
C语言中不允许使用这样的写法 (10<x<20)
需要改写成 (x>10 && x<20)
逻辑运算符的短路原则:
逻辑与的短路原则:
逻辑与连接的多个表达式,如果遇到某个表达式为假了,
足以判断整个表达式为假,后面的表达式就都不执行了
逻辑或的短路原则:
逻辑或连接的多个表达式,如果遇到某个表达式为真了,
足以判断整个表达式为真,后面的表达式就都不执行了
#include <stdio.h>
int main(int argc, const char *argv[])
{
int a = 10;
int b = 20;
int c = 30;
int ret = 0;
ret = (a>0 && b<0 && ++c);//因为b<0为假 所以++c 不执行
printf("ret = %d a = %d b = %d c = %d\n",
ret, a, b, c);//0 10 20 30
a = 10;
b = 20;
c = 30;
ret = 0;
ret = (a<0 || ++b || ++c);//a<0为假 则执行++b ++b为真 所以不执行 ++c
printf("ret = %d a = %d b = %d c = %d\n",
ret, a, b, c);//1 10 21 30
return 0;
}
8.7 位运算符
位运算是针对二进制而言的,位指的是bit位,但凡涉及位运算,
不管几进制的数据计算机中都会转换成二进制参与运算。
一般情况下涉及位运算使用的都是 无符号 数
如果是有符号数 可能需要考虑 原码、反码、补码转换的问题
位运算一般多用于硬件设备的控制或者标志位的控制。
& 按位与 按位运算 全1为1 有0为0
| 按位或 按位运算 有1为1 全0为0
^ 按位异或 按位运算 不同为1 相同为0
~ 按位取反 按位运算 0变1 1变0
>> 按位右移 整个数据向右移动 高位补0 舍弃低位
例:
#include <stdio.h>
int main(int argc, const char *argv[])
{
unsigned char a = 0xa5;
unsigned char b = 0x97;
unsigned char c = 0;
//按位与
c = a & b;
//a: 10100101
//b: 10010111
// &
//c: 10000101 -->0x85
printf("a & b = %#x\n", c);//0x85
//按位或
c = a | b;
//a: 10100101
//b: 10010111
// |
//c: 10110111 -->0xB7
printf("a | b = %#x\n", c);//0xB7
//按位异或
c = a ^ b;
//a: 10100101
//b: 10010111
// ^
//c: 00110010 -->0x32
printf("a ^ b = %#x\n", c);//0x32
//按位取反
c = ~a;
//a: 10100101
// ~
//c: 01011010 -->0x5A
printf("~a = %#x\n", c);//0x5A
//按位左移
c = a<<2;
//a: 10100101
// <<2
//c: 10010100 -->0x94
printf("a<<2 = %#x\n", c);//0x94
//按位右移
c = a>>2;
//a: 10100101
// >>2
//c: 00101001 -->0x29
printf("a>>2 = %#x\n", c);//0x29
return 0;
}
小技巧:
1或任何数 结果都是1
1与任何数 结果都是任何数
0与任何数 结果都是0
0或任何数 结果都是任何数
练习:
使用位运算模拟控制硬件设备。
已知:有8个led灯使用共阴极的接法连接在同一组GPIO上,初始状态未知。
灯的编号 76543210
1.将1357号灯点亮
2.将6号灯点亮
3.将5号灯熄灭
#include <stdio.h>
int main(int argc, const char *argv[])
{
unsigned char led = 0;//初始状态全熄灭
//1.点亮1357号灯
//led = 0x55;//这样操作不好 会影响到其他灯的状态
//如果不想影响其他灯 就需要用位运算了
led = led | 0xAA;
printf("led = %#x\n", led);//0xAA
//2.将6号灯点亮
//led = led | 0x40;
led = led | (1<<6);//这样操作就无需计算数了
printf("led = %#x\n", led);//0xEA
//3.将5号灯熄灭
//led = led & 0xDF;
led = led & ~(1<<5);
printf("led = %#x\n", led);//0xCA
return 0;
}
8.8 条件运算符
?:
也是C语言中唯一一个三目运算符。
格式:
表达式1 ? 表达式2 : 表达式3;
执行逻辑:
先执行表达式1,如果表达式1为真,则执行表达式2,否则执行表达式3.
逻辑和if..else语句特别像。
例:
#include <stdio.h>
int main(int argc, const char *argv[])
{
int a = 10;
int b = 20;
int c = 0;
c = (a<b ? ++a : ++b);
printf("c = %d a = %d b = %d\n", c, a, b);//11 11 20
//上面的语句等价与
if(a<b){
c = ++a;
}else{
c = ++b;
}
return 0;
}
8.9 赋值运算符
=
一般形式:
左 = 右
注意是将右边的值赋值给左边。
注意 = 和 == 的区别。
复合赋值运算符
+= -= *= 等
a+=b a = a+b;
a-=b a = a - b;
其他同理。。
8.10 sizeof运算符
sizeof是用来计算变量或者类型占用的内存空间的大小的。单位 字节
使用格式:sizeof(变量名或者类型名) //会返回计算的结果
注意:sizeof的用法和函数调用很像,但是要切记 sizeof 不是函数调用
例:
#include <stdio.h>
int main(int argc, const char *argv[])
{
char a;
short b;
int c;
long d;
long long e;
float f;
double g;
//64位系统sizeof返回的是一个long类型
//32位系统sizeof返回的是一个int类型
printf("sizeof(char) = %ld sizeof(a) = %ld\n", sizeof(char), sizeof(a));//1
printf("sizeof(short) = %ld sizeof(b) = %ld\n", sizeof(short), sizeof(b));//2
printf("sizeof(int) = %ld sizeof(c) = %ld\n", sizeof(int), sizeof(c));//4
printf("sizeof(long) = %ld sizeof(d) = %ld\n", sizeof(long), sizeof(d));//4 或 8
printf("sizeof(long long) = %ld sizeof(e) = %ld\n", sizeof(long long),
sizeof(e));//8
printf("sizeof(float) = %ld sizeof(f) = %ld\n", sizeof(float), sizeof(f));//4
printf("sizeof(double) = %ld sizeof(g) = %ld\n", sizeof(double), sizeof(g));//8
long ret1 = sizeof(int);//可以保存sizeof计算的结果
printf("ret1 = %ld\n", ret1);
int ret2 = sizeof(int);//这样写也可以 会做隐式强转
printf("ret2 = %d\n", ret2);
return 0;
}
8.11 逗号运算符
(,,,) ----不常用,了解即可
所谓的逗号运算符,就是用逗号连接多个表达式
执行逻辑就是从左到右依次运算,最后一个表达式的结果就是整个逗号运算符表达式的结果。
格式:
(表达式1,表达式2,表达式3,...,表达式n);
例:
#include <stdio.h>
int main(int argc, const char *argv[])
{
int a = 10;
int b = 20;
int c = 30;
int d = 0;
d = (++a, ++b, ++c);//从左到右依次运算 ++c的结果 就是整个表达式的结果
printf("a = %d, b = %d, c = %d, d= %d\n", a, b, c, d);//11 21 31 31
//如果是逗号运算符 一定要用括号扩起来
d = ++a, ++b, ++c;//这就不是逗号运算符了
//只是从左到右依次运算 d 中保存的 ++a 的结果
printf("a = %d, b = %d, c = %d, d= %d\n", a, b, c, d);//12 22 32 12
return 0;
}
3.12 运算符的优先级问题
如果在写复杂的表达式的时候,分不清优先级,可以使用 () 来保证正确
()的优先级是最高的,括号加多了不算错 只要写的对就可以
((5+(6*7))-8)
运算符的优先级参考下图
优先级顺口溜:
单算移关与,异或逻条赋。
思考:
如何交换两个变量的值。
#include <stdio.h>
int main(int argc, const char *argv[])
{
int m = 10;
int n = 20;
#if 0
//方法1:三杯水交换 需要借助临时变量--最常用
int temp = m;
m = n;
n = temp;
#endif
#if 0
//方法2: 通过加减的方式实现
//这样操作数据小的时候 可以实现 但是如果两个数都比较大
//就可能出现数据溢出的情况
m = m+n;
n = m-n;
m = m-n;
#endif
//方法3:三次异或实现两个数的交换 没有溢出的风险,
//且能不是用第三个变量实现两个数的交换
m = m ^ n;
n = m ^ n;
m = m ^ n;
printf("m = %d, n = %d\n", m, n);//20 10
return 0;
}
九、C语言中常用的输入输出函数
输入输出的方式,了解一下即可,会用就行,无需过度纠结他。
原因:
1、实际开发的项目中,基本没有和终端的交互,一般都是和文件交互;
2、笔试面试的过程中,基本上也不会考很多关于输入输出的方式;
我们学习这些方法就是为了方便学习阶段看现象,从而更好的理解知识点的。
9.1 putchar/getchar
9.1.1 putchar()
功能:向终端输出一个字符
头文件:#include
函数原型:int putchar(int c);
参数: c 用来保存要输出字符的ascii码的
#include <stdio.h>
int main(int argc, const char *argv[])
{
//可以传 字符的 ascii码
putchar(65);//A
putchar(10);
//也可以传字符常量
putchar('B');//B
putchar('\n');
//也可传保存ascii码的变量
int num = 67;
putchar(num);//C
putchar('\n');
//也可以传一个表达式
putchar('A'+3);//D
putchar(10);
return 0;
}
9.1.2 getchar
功能:在终端获取一个字符
头文件:#include
函数原型:int getchar(void);
返回值: 输入的字符
#include <stdio.h>
int main(int argc, const char *argv[])
{
//可以直接输出字符串常量
//puts 是自带换行符的
puts("hello");//hello
//也可以输出保存字符串的数组
char str[32] = "beijing";
puts(str);//beijing
//也可以输出指向字符串的指针
char *p = "www.baidu.com";
puts(p);//www.baidu.com
//puts遇到 '\0' 就停止了 后面的字符 不再继续输出
puts("abcd\0efg");//abcd
return 0;
}
9.2.2 gets
功能:在终端获取一个字符串
头文件:#include
函数原型:char *gets(char *s);
参数:s 用来保存输入的字符串的缓冲区的首地址
#include <stdio.h>
int main(int argc, const char *argv[])
{
//注意:使用gets 要保证用来保存字符串的数组足够大 否则会出现越界访问
char s1[32] = {0};//定义了一个数组 并全部初始化成0
gets(s1);
puts(s1);
return 0;
}
9.3 printf/scanf
9.3.1 printf
功能:按照指定的格式,向终端输出一个字符串----格式化输出
头文件:#include
函数原型:int printf(const char *format, ...);
参数:
format :格式
%c 输出字符
%d 输出整型,有符号
%u 输出整型,无符号
%o 输出八进制
%x 输出十六进制
%f 浮点型
%e 指数形式
%% 输出%
附加格式说明:
l (小写的L)输出 long 或者 long long 或者double时用的
# 输出前导符
.n 输出n位小数 n是一个数字
m m是一个数字,可以用来指定输出的数据的位宽
0 如果数据不足指定的位宽 则使用0填充
- 左对齐
+ 如果是正数 输出时显示正号 负数会忽略+
... : 可变参,个数和类型取决于format
#include <stdio.h>
int main(int argc, const char *argv[])
{
int hour = 10;
int min = 9;
int sec = 5;
printf("%d:%d:%d\n", hour, min, sec);// 10:9:5
printf("%2d:%2d:%2d\n", hour, min, sec);// 10: 9: 5
printf("%02d:%02d:%02d\n", hour, min, sec);// 10:09:05
int value = 1234;
printf("%2d\n", value);//1234 如果指定的位宽不够 以实际为准
printf("%10d\n", value);//默认右对齐
printf("%-10d\n", value);//左对齐
printf("%+d\n", value);// +1234
value = -100;
printf("%+d\n", value);// -100 负数会忽略 +
return 0;
}
9.3.2 scanf
功能:按照指定的格式,在终端获取数据----格式化输入
头文件:#include
函数原型:int scanf(const char *format, ...);
参数:
format :格式
和前面printf的用法基本一样。
注意:scanf的格式不能乱写,写成什么样的格式,输入就得按照什么格式输入
... : 可变参,个数和类型取决于format 如果是变量,记得要 &
#include <stdio.h>
int main(int argc, const char *argv[])
{
//--------------------int-----------------
#if 0
int m = 0;
int n = 0;
//scanf 可以连续获取多个数据
//输入时 使用 空格 或者 回车 分隔
scanf("%d%d", &m, &n);
printf("m = %d, n = %d\n", m, n);
scanf("%d:%d", &m, &n);//如果是这样写格式 输入时就必须用 : 分隔
//否则获取不到
printf("m = %d, n = %d\n", m, n);
#endif
//------------------char-----------
#if 0
char m;
scanf("%c", &m);
printf("%c\n", m);
#endif
//-------------float--------------
#if 0
float m;
scanf("%f", &m);
printf("%f\n", m);
#endif
//----------------string--------
char str[32];
//scanf("%s", str);//数组名就是首地址 无需加 &
//printf("%s\n", str);
//scanf不能输入带有空格的字符串
//如果想输入带有空格的字符串 可以使用 gets
//或者使用正则表达式也行 --不常用
scanf("%[^\n]", str);//--正则表达式,了解即可
printf("%s\n", str);
return 0;
}
9.4 垃圾字符的处理
#include <stdio.h>
int main(int argc, const char *argv[])
{
//场景1:多次连续调用 getchar() 时
//处理方式:在每两次调用 getchar之间加一个getchar 用来吃掉垃圾字符 回车
#if 0
//场景2:多次使用scanf连续获取字符时
//处理方式:在每两次调用 scanf之间加一个getchar 用来吃掉垃圾字符 回车
char a,b,c;
scanf("%c", &a);
getchar();
scanf("%c", &b);
getchar();
scanf("%c", &c);
printf("a = [%d] [%c]\n", a, a);
printf("b = [%d] [%c]\n", b, b);
printf("c = [%d] [%c]\n", c, c);
#endif
//场景3:使用scanf 一次获取多个字符时
char a,b,c;
//scanf("%c%c%c", &a, &b, &c);
//处理方式1:使用抑制符来处理 %*c
//抑制符的作用是获取数据 但是 不赋值给后面的变量
//注意:抑制符是一定要吃掉一个字符的 他并不管是不是垃圾字符
//scanf("%c%*c%c%*c%c", &a, &b, &c);
//处理方式2: 可以在格式中使用空格来处理--推荐的处理方式
//空格可以吃掉多个空格 或者 回车
scanf("%c %c %c", &a, &b, &c);
printf("a = [%d] [%c]\n", a, a);
printf("b = [%d] [%c]\n", b, b);
printf("c = [%d] [%c]\n", c, c);
//注意 %d 是不会有垃圾字符的问题的
return 0;
}
十、分支控制语句
C语言的程序结构一共有三种:顺序结构、分支结构(选择结构)、循环结构。
10.1 if..else 语句
10.2.1 格式及执行逻辑
1.简化格式
if(表达式){
代码块;
}
执行逻辑:
先执行表达式,如果表达式为真,
则执行代码块,否则就不执行代码块
if(表达式){
代码块1;
}else{
代码块2;
}
执行逻辑:
先执行表达式,如果表达式为真,
则执行代码块1,否则执行代码块2
2.阶梯形式
if(表达式1){
代码块1;
}else if(表达式2){
代码块2;
}else if(表达式n){
代码块n;
}else{
其他分支;
}
执行逻辑:
先执行表达式1,如果为真,则执行代码块1,
否则执行表达式2,如果为真,则执行代码块2,
依次类推,如果前面的表达式都为假,则执行else分支
3.嵌套格式
if(表达式1){
if(表达式11){
代码块11;
}else if(表达式12){
代码块12;
}else{
其他分支1;
}
}else if(表达式2){
if(表达式21){
代码块21;
}else if(表达式22){
代码块22;
}else{
其他分支2;
}
}else if(表达式n){
代码块n;
}else{
其他分支;
}
执行逻辑:
就是前面阶梯形式的嵌套使用
#include <stdio.h>
int main(int argc, const char *argv[])
{
printf("----start----\n");
int a = 0;
int b = 0;
scanf("%d%d", &a, &b);
#if 0
//简化格式1
if(a > b){
printf("a>b\n");
}
#endif
#if 0
//简化格式2
if(a > b){
printf("a>b\n");
}else{
printf("a<=b\n");
}
#endif
#if 0
//阶梯格式
if(a > 0 && b >0){
printf("a b 都大于 0\n");
}else if(a > 0){
printf("a > 0 b <= 0\n");
}else if(b > 0){
printf("a <= 0 b > 0\n");
}else{
printf("a b 都小于等与 0\n");
}
#endif
//嵌套格式
if(a > 0){
printf("a>0\n");
if(b>0){
printf("b>0\n");
}else{
printf("b<=0\n");
}
}else{
printf("a<=0\n");
if(b>0){
printf("b>0\n");
}else{
printf("b<=0\n");
}
}
printf("----end----\n");
return 0;
}
10.2.2 注意事项
1.如果if..else语句的代码块只有一行,那么 {} 可以不用写;
注意,如果不加{} if..else 只对下面的一行指令生效
2.else必须要有if与之对应,不能单独出现
3.else与同一层次的前面最近的if结合
4.关于if..else语句的表达式:
一般情况下都是有关系运算符和逻辑运算符组成的,用来表示真假
如果是特殊情况,注意下面的用法
if(a = 10) //条件恒为真
if(a = 0) //条件恒为假
//建议:变量和常量判断相等时 将常量放在左边 这样少写等号的时候 编译会报错
if(a=b) //这时表达式的结果取决于 b的值 b为0 假 b为非0 真
if(a) //这时表达式的结果取决于 a的值 a为0 假 a为非0 真 等价于 if(a!=0)
if(!a) //这时表达式的结果取决于 a的值 a为0 真 a为非0 假 等价于 if(a==0)
5.下面这种写法时,两个if..else 语句之间没有任何关系
if(){}
if(){}
6.分支控制语句只会选择一条分支执行
int a = 10;
if(a > 0){
printf("11\n");
a = -10;
}else if(a < 0){
printf("22\n");
}
//上面的代码只输出 11
10.2.3 if..else 语句练习
1.从终端输入一个字符
如果是小写字母 转换成大写输出
如果是大写字母 转换成小写输出
如果是 '0'--'9' 都输出数字0
其他字符 都输出 #
#include <stdio.h>
int main(int argc, const char *argv[])
{
int value = 0;
value = getchar();
if(value >= 65 && value <= 90){//大写
value = value+32;
printf("%c\n", value);
}else if(value >= 'a' && value <= 'z'){//小写
value = value+'A'-'a';
printf("%c\n", value);
}else if(value >= '0' && value <= '9'){//'0'~'9'
value -= '0';
printf("%d\n", value);
}else{//其他字符
printf("#\n");
}
return 0;
}
//注意:分支控制语句的代码 测试时 要把每个分支都测试到
2.在终端输入一个成绩 [0,100]
输出成绩对应的等级
[90, 100] A
[80, 90) B
[70, 80) C
[60, 70) D
[0, 60) 不及格
#include <stdio.h>
int main(){
int s = 0;
scanf("%d", &s);
if(s>=90 && s<=100){
printf("A\n");
}else if(s>=80 && s<90){
printf("B\n");
}else if(s>=70 && s<80){
printf("C\n");
}else if(s>=60 && s<70){
printf("D\n");
}else{
printf("不及格\n");
}
return 0;
}
3.在终端输入三角形的三边长 (整型)
判断能否构成三角形 任意两边之和大于第三边
如果能
输出能构成三角形
再判断是什么类型的三角形
等边 三条边相等
等腰 有两条边相等
直角 勾股定理
普通
如果不能
输出不能构成三角形
#include <stdio.h>
int main(){
int a = 0, b = 0, c = 0;
scanf("%d%d%d", &a, &b, &c);
if(a+b>c && a+c>b && b+c>a){
printf("能构成\n");
if(a==b && b==c && a==c){
printf("等边\n");
}else if(a==b || b==c || a==c){
printf("等腰\n");
}else if(a*a+b*b==c*c || b*b+c*c==a*a || a*a+c*c==b*b ){
printf("直角\n");
}else{
printf("普通\n");
}
}else{
printf("不能构成\n");
}
return 0;
}
4.输入一个年份,输出这一年是平年还是闰年
闰年:能被4整除且不能被100整除 或者 能被400整除
2004 闰年
2000 闰年
1900 平年
#include <stdio.h>
int main(){
int year = 0;
scanf("%d", &year);
if((year%4==0 && year%100 != 0) || year%400==0){
printf("闰年\n");
}else{
printf("平年\n");
}
return 0;
}
5.预测身高的程序
每个做父母的都关心自己孩子成人后的身高,
据有关生理卫生知识与数理统计分析表明,
影响小孩成人后身高的因素有遗传、饮食习惯与坚持体育锻炼等。
小孩成人后身高与其父母身高和自身性别密切相关。
父亲、母亲、孩子 身高都用double
设faHeight为其父身高,moHeight为其母身高,基础身高预测公式为:
男性成人时身高 = (faHeight + moHeight) * 0.54(cm)
女性成人时身高 = (faHeight * 0.923 + moHeight) / 2(cm)
此外,如果喜爱体育锻炼,那么可在基础身高上增加身高 2%
如果有良好的卫生饮食习惯,那么可在基础身高上增加身高1.5%
程序要求:父亲的身高与母亲的身高、小孩的性别、是否喜爱体育锻炼
和是否有良好的卫生饮食习惯也从键盘上输入,最终输出预测的身高。
提示:
小孩性别的输入方式,
可在屏幕给出提示“请输入小孩的性别(男孩输入1,女孩输入0):”,
然后通过if语句来判断从键盘输入的字符是 1 还是 0。
是否喜爱体育锻炼也可以通过类似的方式实现。
测试:
180 160 男 体 习 --->190.026 %.3f
#include <stdio.h>
int main(int argc, const char *argv[])
{
double faHeight = 0;
double moHeight = 0;
double childHeight = 0;
int sex = 0;//性别
int pe = 0;//体育
int food = 0;//饮食
double d_pe = 0;//体育运动的增量
double d_food = 0;//饮食习惯的增量
printf("请输入父亲的身高:");
scanf("%lf", &faHeight);
printf("请输入母亲的身高:");
scanf("%lf", &moHeight);
printf("请输入孩子的性别(0 女 1 男):");
scanf("%d", &sex);
printf("请输入是否喜欢体育运动(0 不喜欢 1 喜欢):");
scanf("%d", &pe);
printf("请输入是否有好的饮食习惯(0 没有 1 有):");
scanf("%d", &food);
//先计算基础身高
if(0 == sex){
childHeight = (faHeight * 0.923 + moHeight) / 2;
}else if(1 == sex){
childHeight = (faHeight + moHeight) * 0.54;
}
//计算体育运动的增量
if(1 == pe){
d_pe = childHeight * 0.02;
}
//计算饮食习惯的增量
if(1 == food){
d_food = childHeight * 0.015;
}
//计算总身高
childHeight = childHeight + d_pe + d_food;
printf("预测孩子的身高位:%.3lf\n", childHeight);
return 0;
}
10.2 switch..case 语句
10.2.1 格式
witch(表达式){
case 常量表达式1:
代码块1;
break;
case 常量表达式2:
代码块2;
break;
case 常量表达式n:
代码块n;
break;
default:
其他分支;
break;
}
执行逻辑:先执行表达式,判断表达式的结果属于哪个case
属于哪个就执行哪个case的代码块
10.2.2 注意事项
1.switch后面括号的例表达式就是我们要判断的值,每个case后面的常量表达式就是
前面表达式所有可能的结果。
2.switch后面的表达式一般是 整型或者字符类型 注意不能是 浮点型
3.break的作用是 执行完某一个分支后,就结束整个switch..case语句
如果没有break,程序会继续执行下面case的代码块,直到遇到break或者
整个switch..case语句结束,----case击穿
4.default表示其他分支,如果前面的所有case都不满足,则执行default分支的代码块
作用相当于if..else语句中的 else 分支,如果不关心其他分支的情况 整个default分支可以不写
例:
#include <stdio.h>
int main(int argc, const char *argv[])
{
int num = 0;
scanf("%d", &num);
switch(num){
case 10:
printf("AAAAAAAAAAA\n");
break;
case 20:
printf("BBBBBBBBBBB\n");
break;
case 30:
printf("CCCCCCCCCCC\n");
break;
default:
printf("other\n");
break;
}
printf("----end----\n");
return 0;
}
练习:
1.使用switch..case语句 实现简易的计算器功能: ( 实现 + - * )功能即可
要求,在终端输入 表达式 如: 5+21 %d%c%d
输出计算的结果 :26
#include <stdio.h>
int main(){
int lvalue = 0;
char operator = 0;
int rvalue = 0;
scanf("%d%c%d", &lvalue, &operator, &rvalue);
switch(operator){
case '+': //注意:如果是字符常量 需要加 单引号
printf("%d\n", lvalue + rvalue);
break;
case '-':
printf("%d\n", lvalue - rvalue);
break;
case '*':
printf("%d\n", lvalue * rvalue);
break;
default:
printf("目前只支持 + - * 运算\n");
break;
}
return 0;
}
if..else 语句和 switch..case 语句都是用来实现分支控制的,
if..else 语句能实现的功能 switch..case 也一定能实现
switch..case 语句能实现的功能 if..else 也一定能实现
一般情况下,表示范围的时候,用 if..else 方便一些
判断某个变量值是 多少的时候用 switch..case 方便一些
2.将前面学生成绩管理的练习,使用switch..case语句实现一次
#include <stdio.h>
int main(int argc, const char *argv[])
{
int num = 0;
scanf("%d", &num);
switch(num/10){
case 10://利用了case击穿的规则
case 9:
printf("A\n");
break;
case 8:
printf("B\n");
break;
case 7:
printf("C\n");
break;
case 6:
printf("D\n");
break;
default:
printf("不及格\n");
break;
}
return 0;
}
十一、循环控制语句
11.1使用goto实现循环
goto 本来是用于代码的跳转的,注意:只能在同一个函数中进行跳转。
注意:goto对代码的可读性和逻辑性有一定的影响,使用goto要谨慎
11.1.1 goto的基本使用
格式:
代码块1;
goto NEXT;
代码块2;
NEXT: //标签
代码块3;
执行逻辑:
先执行代码块1,然后遇到goto 直接跳转到对应的标签向下执行,代码块2被跳过,不执行。
标签名也是一个标识符,需要符合命名规范,一般情况下都采用大写
例:
#include <stdio.h>
int main(int argc, const char *argv[])
{
printf("1111111111\n");
goto NEXT;
printf("2222222222\n");//不执行
printf("3333333333\n");//不执行
printf("4444444444\n");//不执行
NEXT:
printf("5555555555\n");
return 0;
}
11.1.2 使用goto实现循环
如果想实现循环 标签应该在 goto语句的前面
一般情况下 我们使用的循环都是有结束条件的 否则就是死循环了。
#include <stdio.h>
int main(int argc, const char *argv[])
{
printf("----start----\n");
int count = 0;//防止随机值影响 此处必须要用0初始化
LOOP:
printf("hello world\n");
count++;
if(count < 5){//结束条件,否则就是死循环
goto LOOP;
}
printf("----end----\n");
return 0;
}
练习:
使用 goto 计算 1+2+3+...+100 求和的结果。
#include <stdio.h>
int main(){
/*
思路
1.
int sum = 0;//用来保存求和的结果
sum = sum + 1;
sum = sum + 2;
sum = sum + 3;
....
sum = sum + 100;
2.
int sum = 0;
int i = 1;
sum = sum + i;
i++;
sum = sum + i;
i++;
sum = sum + i;
i++;
....
*/
int sum = 0;//用来保存求和的结果
int i = 1; //一般在循环中控制循环执行次数的变量
//我们称之为 循环变量 一般使用 i j k 。。即可
LOOP:
sum = sum + i;
i++;
if(i <= 100){
goto LOOP;
}
printf("sum = %d\n", sum);
return 0;
}
如果练习时,死循环了 可以使用 ctrl + c 终止进程。
11.2 while循环
11.2.1 格式
while(表达式){
代码块;
}
11.2.2 执行逻辑
先执行表达式,如果表达式为真,则执行代码块
然后再执行表达式,如果还为真,则继续执行代码块。。
直到表达式为假,循环立即结束。。
表达式的用法:和 if 语句的表达式用法一样
注意:代码块中一般要有能改变表达式真假的语句,否则就是死循环了。
例:使用while循环打印5行hello world
include <stdio.h>
int main(int argc, const char *argv[])
{
printf("----start----\n");
int i = 0;//循环变量
while(i < 5){
printf("hello world\n");
i++;
}
printf("----end----\n");//没有被while的{}包住的代码不受循环的影响
return 0;
}
练习:
1.使用 while 循环,实现 1~100 求和。
#include <stdio.h>
int main(){
int sum = 0;
int i = 1;
while(i <= 100){
sum += i;
i++;
}
printf("sum = %d\n", sum);
return 0;
}
2.思考:下面的代码会输出什么?
int i = 100;
while(i--);
printf("%d\n", i);//-1 因为i--表达式的结果是减之前的值
//所以 i-- 为0时 i的值已经是 -1 了
int i = 100;
while(--i);
printf("%d\n", i);//0 因为--i表达式的结果是减之后的值
3.猴子吃桃问题
一只猴子第一天摘了若干个桃,
当即就吃了一半数量的桃,没吃过瘾,又多吃了一个
第二天,把剩下的桃又吃了一半数量,没吃过瘾,又多吃了一个
以后的每天都按照这种方式吃桃
到第十天,再想吃桃的时候,发现只剩下一个桃了。
问:猴子第一天摘了多少个桃? 使用while循环解决
第10天 1 个
第9天 4 个 (1+1)*2
第8天 10 个 (4+1)*2
。。。。。
#include <stdio.h>
int main(){
int count = 1;//记录桃子的数量
int i = 0;//控制循环次数
while(i < 9){
count = (count + 1) * 2;
i++;
}
printf("猴子第1天摘了 %d 个桃\n", count);
return 0;
}
再要求:输出每天的桃子数量
第10天 1 个
第9天 4 个
第8天 10 个
...
第1天 1524个
#include <stdio.h>
int main(){
int count = 1;//记录桃子的数量
int i = 0;//控制循环次数
while(i < 9){
printf("猴子第 %-2d 天摘了 %-4d 个桃\n", 10-i, count);
count = (count + 1) * 2;
i++;
}
printf("猴子第 %-2d 天摘了 %-4d 个桃\n", 10-i, count);
return 0;
}
11.3 do..while 循环
11.3.1 格式
do{ 代码块; }while(表达式); //注意:最后要有一个分号
do{
代码块;
}while(表达式); //注意:最后要有一个分号
11.3.2 执行逻辑
先执行代码块,然后执行表达式,如果表达式为真,则继续执行代码块
然后再执行表达式,如果还为真,则继续执行代码块,直到表达式为假,循环立即结束
do..while 和 while的区别
while 先判断 后执行
do..while 先执行 后判断
do..while的代码块无论如何都是要至少执行一次的。
例:
#include <stdio.h>
int main(int argc, const char *argv[])
{
int sum = 0;
int i = 1;
do{
sum += i;
i++;
}while(i<=100);//别落下此处的分号
printf("sum = %d\n", sum);
return 0;
}
11.4 for 循环
11.4.1 格式
for(表达式1; 表达式2; 表达式3){
代码块;
}
11.4.2 执行逻辑
先执行表达式1,然后执行表达式2,如果表达式2为真,则执行代码块
然后执行表达式3,然后再执行表达式2,如果还为真,则继续执行代码块和表达式3
直到表达式2为假,循环立即结束。
关于for循环的三个表达式:
表达式1:只执行一次,一般是用来给循环变量赋初值的
表达式2:和while循环的表达式一样,一般是用来判断真假确定是否继续执行循环的
表达式3:一般是用来改变循环变量的值,从而控制循环执行次数的
如果也许有需求,表达式1和表达式3可以使用逗号分隔 写多个
也就是说,可以给多个变量赋初值,可以修改多个变量的值
for循环的每个表达式 如果不需要,都可以不写,但是两个分号必须写
例:使用for循环打印5行hello world
#include <stdio.h>
int main(int argc, const char *argv[])
{
printf("----start----\n");
int i = 0;
for(i = 0; i < 5; i++){
printf("hello world\n");
}
printf("----end----\n");//不受循环控制
return 0;
}
练习:
1.使用for循环 计算 1~100 求和。
#include <stdio.h>
int main(int argc, const char *argv[])
{
int sum = 0;
int i = 0;
for(i = 1; i <= 100; i++){
sum += i;
}
printf("sum = %d\n", sum);
printf("i = %d\n", i);//这里也可以访问
return 0;
}
关于 for循环的第一个表达式内定义变量
include <stdio.h>
int main(int argc, const char *argv[])
{
int sum = 0;
for(int i = 1; i <= 100; i++){//C99标准的编译器允许for循环的表达式中定义变量
//注意:在for循环的表达式1中定义的变量
//作用域 和 生命周期都是循环内部
sum += i;//循环内部使用没问题
}
printf("sum = %d\n", sum);
printf("i = %d\n", i);//这里就不能访问了
return 0;
}
2.输出 [100,999] 范围内所有的水仙花数。
水仙花数:一个三位数,如果 个*个*个 + 十*十*十 + 百*百*百 == 自身
如: 153 == 1*1*1 + 5*5*5 + 3*3*3 = 1 +125 + 27 == 153
#include <stdio.h>
int main(){
int num = 0;
int g = 0;
int s = 0;
int b = 0;
for(num = 100; num < 1000; num++){
//int g = num%10;
//int s = num/10%10;
//int b = num/100;
//注意:尽量不要在循环里面定义变量
g = num%10;
s = num/10%10;
b = num/100;
if(num == g*g*g + s*s*s + b*b*b){
printf("%d 是水仙花是\n", num);
}
}
return 0;
}
C语言中计算幂和开平方的函数
编译时,需要加编译选项 -lm
#include
pow 功能:计算x的y次幂
double pow(double x, double y);
sqrt 功能:计算x的非负平方根
double sqrt(double x);
#include <stdio.h>
#include <math.h>
int main(int argc, const char *argv[])
{
int a = 5;
int b = 3;
double ret1 = pow(a, b);
printf("ret1 = %lf\n", ret1);//125.000000
double ret2 = sqrt(16);
printf("ret2 = %lf\n", ret2);//4.000000
return 0;
}
3.使用循环输出下面的图案
F
_FE
__FED
___FEDC
____FEDCB
_____FEDCBA
拆分:使用 putchar()
1.
_____
_____
_____
_____
_____
_____
2.
_
__
___
____
_____
3.
FFFFFF
_FFFFFF
__FFFFFF
___FFFFFF
____FFFFFF
_____FFFFFF
4.
F
_FF
__FFF
___FFFF
____FFFFF
_____FFFFFF
5.
F
_FE
__FED
___FEDC
____FEDCB
_____FEDCBA
答案:
#include <stdio.h>
int main(int argc, const char *argv[])
{
int i = 0;
int j = 0;
char value = 0;
//外层循环控制行数
for(j = 0; j < 6; j++){
//这个循环用来输出 下划线
for(i = 0; i < j; i++){
putchar('_');
}
//这个循环用来输出字母
value = 'F';//每次需要重置F的值
for(i = 0; i <= j; i++){
putchar(value--);
}
putchar(10);//换行
}
return 0;
}
11.5 死循环
所谓的死循环就是条件恒为真,循环体一直执行的逻辑。
有些业务场景需要程序一直执行,就需要用到死循环。
格式:
hile(1){ //----常用的用法
循环体;
}
for(;;){//表达式可以不写,但是两个分号必须写
循环体;
}
例
#include <stdio.h>
#include <unistd.h>
int main(int argc, const char *argv[])
{
#if 0
while(1){
printf("hello world\n");
sleep(1);
}
printf("----end----\n");//永远不会被执行
#endif
for(;;){
printf("hello world\n");
sleep(1);
}
printf("----end----\n");//永远不会被执行
return 0;
}
11.6 辅助控制关键字
11.6.1 break
break可以用在switch..case 语句中,表示执行完某个分支就立即结束整个switch..case语句
break也可以用在循环中,表示立即结束本层循环
11.6.2 continue
continue只能用在循环中,表示立即结束本层的本次循环
11.6.3 return
return 如果用在函数中,表示返回函数执行的结果;
如果用在主函数中,表示立即结束整个程序。
例:
#include <stdio.h>
int main(int argc, const char *argv[])
{
int i = 1;
int j = 1;
for(i = 1; i <= 5; i++){
for(j = 1; j <= 5; j++){
if(3 == j){
//break;
//continue;
return 0;
}
printf("%d--%d\n", i, j);
}
}
return 0;
}
编码:
输入一个日期:如 2022/9/30
输出这个日期是这一年的第几天。 ----注意得考虑平年和闰年
#include <stdio.h>
//输入一个日期,输出当前日期是这一年的第几天
#define MONTH_1 31
#define MONTH_2 28
#define MONTH_3 31
#define MONTH_4 30
#define MONTH_5 31
#define MONTH_6 30
#define MONTH_7 31
#define MONTH_8 31
#define MONTH_9 30
#define MONTH_10 31
#define MONTH_11 30
#define MONTH_12 31
int main()
{
int year, month, day;
printf("请输入要查询的日期(年/月/日):");
scanf("%d/%d/%d", &year, &month, &day);
//闰年的增量 也是闰年的标志位 0 平年 1 闰年
int leap = 0;
//printf("%d/%d/%d\n", year, month, day);
//判断输入的日期是否有误
//判断年是闰年还是平年
if(((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0)){
leap = 1;
}
//判断月份输入的是否有误
if(month < 1 || month > 12){
printf("您输入的月份有误,请重新输入\n");
//在main函数里面使用return,结束整个程序
return -1;
}
//判断日是否有误
//月份:1 3 5 7 8 10 12 最多31天
if(month==1 || month==3 || month==5 || month==7 || month==8 || month==10 || month==12){
if(day < 1 || day > 31){
printf("您输入的日期有误,请重新输入\n");
return -1;
}
}
//月份:4 6 9 11 最多30天
if(month==4 || month==6 || month==9 || month==11){
if(day < 1 || day > 30){
printf("您输入的日期有误,请重新输入\n");
return -1;
}
}
//月份:2 闰年最多29天 平年最多28天
if(month == 2){
if(leap == 1){ //闰年
if(day < 1 || day > 29){
printf("您输入的日期有误,请重新输入\n");
return -1;
}
}else if(leap == 0){ //平年
if(day < 1 || day > 28){
printf("您输入的日期有误,请重新输入\n");
return -1;
}
}
}
int days = 0;
switch(month){
case 12:
days += MONTH_11;
case 11:
days += MONTH_10;
case 10:
days += MONTH_9;
case 9:
days += MONTH_8;
case 8:
days += MONTH_7;
case 7:
days += MONTH_6;
case 6:
days += MONTH_5;
case 5:
days += MONTH_4;
case 4:
days += MONTH_3;
case 3:
days += MONTH_2 + leap;
case 2:
days += MONTH_1;
case 1:
days += day;
break;
}
printf("%d年%d月%d日%d年的第%d天\n", year, month, day, year, days);
return 0;
}
十二、控制语句练习
1.输出1~100范围内所有的偶数
#include <stdio.h>
int main(int argc, const char *argv[])
{
int i = 0;
for(i = 1; i <= 100; i++){
if(i%2==0){
printf("%d\n", i);
}
}
return 0;
}
2.输入一个数,输出这个数的所有因子。
如:输入:12
则输出:1 2 3 4 6 12
#include <stdio.h>
int main(){
int num = 0;
printf("请输入一个数:");
scanf("%d", &num);
int i = 0;
for(i = 1; i <= num; i++){
if(num%i == 0){
printf("%d是%d的因子\n", i, num);
}
}
return 0;
}
3.输出 [2,500] 范围内所有的完数。
完数:除了自身之外的所有因子和还等于自身。
6 的 所有真因子 1 2 3
1 + 2 + 3 == 6 所以 6 就是完数
#include <stdio.h>
int main(){
int num = 0;
int sum = 0;//保存因子和的
int i = 0;
for(num = 2; num <= 500; num++){
sum = 0; //此处的sum=0; 是必要的 因为sum中还保存着上一个数的因子和呢
//计算num的因子和
for(i = 1; i < num; i++){
if(num % i == 0){
sum += i;
}
}
if(sum == num){
printf("%d 是完数\n", num);
}
}
return 0;
}
4. 输出 [2,100] 范围内所有的质数。
质数:因子只有 1 和 自身 的数
处理方式1:使用标记位来处理
#include <stdio.h>
int main(int argc, const char *argv[])
{
int num = 0;
//处理方式1:使用标记位来处理
int flag = 0;//质数的标记位 0 是质数 1 不是质数
int i = 0;
for(num = 2; num <= 100; num++){
flag = 0;//使用标记位的方式 需要每次给 flag重置初始值
//遍历 [2,num-1]
//看看是否有一个数能把num整除 有 就说明 num不是质数
for(i = 2; i < num; i++){
if(num % i == 0){
flag = 1;
break;
}
}
if(0 == flag){
printf("%d 是质数\n", num);
}
}
return 0;
}
处理方式2:根据循环结束条件的不同来处理
#include <stdio.h>
int main(int argc, const char *argv[])
{
int num = 0;
int i = 0;
for(num = 2; num <= 100; num++){
//遍历 [2,num-1]
//看看是否有一个数能把num整除 有 就说明 num不是质数
for(i = 2; i < num; i++){
if(num % i == 0){
break;
}
}
//上面的循环有两种结束条件
//1:由于break导致的提前结束
//说明 num%i==0 满足了 说明不是质数
//2:由于表达式2为假导致的正常结束
//说明 num%i==0 从头到为都没有满足过 说明是质数
//如果循环结束时 num和i 相等 就说明 是由于第二种情况导致结束的
if(i == num){
printf("%d 是质数\n", num);
}
}
return 0;
}
十三、数组
13.1 概念
数组是用来保存一组相同数据类型的数据的
数组是一个构造类型
数组中的每个数据叫做数组的元素,或者叫做数组的成员
数组在内存上需要分配一块连续的空间来存储数据,不管几维数组,成员之间都是连续的
13.2 一维数组
13.2.1 概念
所谓的一维数组,就是下标只有一个的数组。
13.2.2 定义一维数组的格式
存储类型 数据类型 数组名[下标];
存储类型:默认就是 auto
数据类型:指的是数组中每个元素的数据类型,
可以是基本类型、也可以是构造类型(数组除外)
数组名: 是一个标识符,要符合标识符的命名规范
下标:在定义数组的时候,下标是个常量,用来表示数组的元素的个数
在其他场景下,下标表示访问数组中的第几个元素,
既可以是变量、也可以是常量、也可以是一个表达式
例如:
int s[5];
//定义了一个一维数组 数组名叫s 数组中每个元素都是一个 int类型的变量
//数组中共有5个元素
18.2.3 一维数组的性质
#include <stdio.h>
int main(int argc, const char *argv[])
{
//定义了一个一维数组 数组名是s 数组中共有5各元素
//每个元素都是一个int类型的变量
int s[5];
//数组访问元素的方式 数组名[下标]
//注意,下标是从0开始的
//当取出数组的元素操作时 和 操作单个的变量就是一样的了
//给数组元素赋值
s[0] = 100;
s[1] = 200;
s[2] = 300;
s[3] = 400;
s[4] = 500;
//读取数组元素的值
printf("s[0] = %d\n", s[0]);//100
printf("s[1] = %d\n", s[1]);//200
printf("s[2] = %d\n", s[2]);//300
printf("s[3] = %d\n", s[3]);//400
printf("s[4] = %d\n", s[4]);//500
//数组名是一个常量
//s = 1314;//错误的
//s++;//错误的 s = s+1;
//数组的元素在内存上是连续的
//printf %p 可以按照16进制打印地址编号
// &变量名 & 取地址符 可以获取变量的地址
printf("&s[0] = %p\n", &s[0]);//连续的
printf("&s[1] = %p\n", &s[1]);
printf("&s[2] = %p\n", &s[2]);
printf("&s[3] = %p\n", &s[3]);
printf("&s[4] = %p\n", &s[4]);
//数组的长度:元素的个数
//数组的大小:占用的内存空间的大小
//一维数组的大小 = 元素的个数 * 单个元素的大小
printf("sizeof(s) = %ld\n", sizeof(s));//20 == 5*sizeof(int)
//数组的元素是变量 允许被重新赋值
s[0] = 520;
printf("s[0] = %d\n", s[0]);//520
//下标可以是一个变量
int m = 1;
s[m] = 1234;
printf("s[m] = %d\n", s[m]);//1234
//下标可以是一个表达式
s[s[0]-519] = 1314;
printf("s[1] = %d\n", s[1]);//1314
//遍历一维数组 方式1
int i = 0;
for(i = 0; i < 5; i++){
printf("%d ", s[i]);
}
printf("\n");
//遍历一维数组 方式
for(i = 0; i < sizeof(s)/sizeof(s[0]); i++){
printf("%d ", s[i]);
}
printf("\n");
return 0;
}
13.2.4 一维数组的初始化
#include <stdio.h>
int main(int argc, const char *argv[])
{
//如果一维数组没有初始化,里面存放的都是随机值
//int s[5];
//一维数组初始化的方式
//完全初始化
//int s[5] = {10, 20, 30, 40, 50};
//不完全初始化
//这时是从左到右依次初始化 没有初始化的元素 会用0初始化
int s[5] = {10, 20};
//全部初始化成 0
//int s[5] = {0}; //--最常用的用法
//省略下标的初始化
//编译器会根据后面初始化的元素的个数 来自动计算数组的长度
//int s[] = {10, 20, 30};
int i = 0;
for(i = 0; i < 5; i++){
printf("%d ", s[i]);
}
printf("\n");
//关于数组访问越界的问题
//注意:数组越界访问的问题 编译器不会检查
//需要程序员自己检查,而且数组越界的问题是很严重的
//越界访问的现象是不可预知的:
//可能正常执行
//可能是段错误
//可能修改了其他变量的数据
//尤其是变量 或者 表达式做为下标使用的使用 要特别注意检查边界
int hqyj[8] = {0};
hqyj[12345] = 1314;//错误的操作
printf("%d\n", hqyj[12345]);//错误的操作
return 0;
}
练习:
1.定义一个长度为10的int类型的一维数组
从终端给数组的元素赋值
找出数组中最大的值,输出最大值。
#include <stdio.h>
int main(int argc, const char *argv[])
{
int s[10] = {0};
int i = 0;
//循环给数组赋值
for(i = 0; i < 10; i++){
scanf("%d", &s[i]);
}
//遍历一维数组
for(i = 0; i < 10; i++){
printf("%d ", s[i]);
}
putchar(10);
#if 0
//找数组中的最大值
//int max = 0;//这些操作方式 如果选了0为比较的基准
//那么都是负数时就没法得到结果了
//要选取数组中的元素做位比较的基准 才合理
int max = s[0];
for(i = 1; i < 10; i++){
if(s[i] > max){
max = s[i];
}
}
//当循环结束的时候 max 中记录的就是最大值
printf("max = %d\n", max);
#endif
//保存最大值的下标
int max_index = 0;
for(i = 1; i < 10; i++){
if(s[max_index] < s[i]){
max_index = i;
}
}
//当循环结束的时候 max_index 中记录的就是最大值的下标
printf("max_value = %d\n", s[max_index]);
return 0;
}
2.使用一维数组保存斐波那契数列的前20位,并输出。
斐波那契数列 1 1 2 3 5 8 13 21 ....
#include <stdio.h>
int main(int argc, const char *argv[])
{
int s[20] = {1, 1};
int i = 0;
#if 0
//循环给数组赋值--注意不要出现越界访问
for(i = 2; i < 20; i++){
s[i] = s[i-1] + s[i-2];
}
#endif
//循环给数组赋值--注意不要出现越界访问
for(i = 0; i < 18; i++){
s[i+2] = s[i+1] + s[i];
}
//遍历一维数组
for(i = 0 ; i < 20; i++){
printf("%d ", s[i]);
}
printf("\n");
return 0;
}
十四、冒泡排序
14.1 基本思想
升序:从小到大 降序:从大到小
相邻的两个数进行比较,按照要求(升序还是降序)进行交换。
冒泡排序的时间复杂度是 O(n^2)
11, 22, 43, 567, 1, 4, 9, 8, 55, 98
14.2 实现思路(以升序为例)
第一趟排序:
第1元素和第2个元素进行比较,将较大的值放在第2个位置上,
然后第2个元素和第3个元素进行比较,将较大的放在第3个位置上,。。
依此类推,直到第一趟排序结束,最大的元素就在整组数据的最后一个位置上了
第二趟排序:
第1元素和第2个元素进行比较,将较大的值放在第2个位置上,
然后第2个元素和第3个元素进行比较,将较大的放在第3个位置上,。。
依此类推,直到第二趟排序结束,第二大的数据就在倒数第二个位置上了
依此类推,直到整个数据有序。
冒泡排序的动图:
14.3 代码实现
#include <stdio.h>
int main(int argc, const char *argv[])
{
int s[10] = {11, 22, 43, 567, 1, 4, 9, 8, 55, 98};
int len = sizeof(s)/sizeof(s[0]);//数组的长度:元素的个数
int temp = 0;
//排序前
int i = 0;
int j = 0;
for(i = 0 ; i < 10; i++){
printf("%d ", s[i]);
}
printf("\n");
#if 0
//一趟排序的过程
for(i = 0; i < len-1; i++){
if(s[i] > s[i+1]){
//交换
temp = s[i];
s[i] = s[i+1];
s[i+1] = temp;
}
}
#endif
//完整的排序
//外层循环控制排序的趟数 此处的 -1 是因为 最后一趟只有一个元素 无需再排序了
for(j = 0; j < len-1; j++){
//内层循环控制一趟排序
for(i = 0; i < len-1-j; i++){
//此处的 -1 是防止越界访问的
//此处的 -j 是因为每趟都可以少比较一个元素
if(s[i] > s[i+1]){//如果是降序 只需要将此处的 > 改成 < 即可
//交换
temp = s[i];
s[i] = s[i+1];
s[i+1] = temp;
}
}
}
//排序后
for(i = 0 ; i < 10; i++){
printf("%d ", s[i]);
}
printf("\n");
return 0;
}
十五、二维数组
15.1 概念
所谓的二维数组,就是下标有两个的数组。
15.2 定义二维数组的格式
存储类型 数据类型 数组名[行数][列数];
注意:
二维数组虽然有行号和列号,但是所有的元素在内存上也是连续的,
列数:决定了按行访问元素时的跨度。
例:
int s[3][4];
//定义了一个二维数组 数组名叫s 数组中有3行4列共计12个元素
//每个元素都是一个 int 类型的变量
15.3 二维数组的性质
#include <stdio.h>
int main(int argc, const char *argv[])
{
//定义了一个二维数组 数组名叫s 数组中共有3行4列共计12个元素
//每个元素都是一个 int 类型的 变量
int s[3][4];
//二维数组访问元素 数组名[行号][列号]
//注意:行号和列号都是从 0 开始的
//二维数组取出元素后的操作 和 操作单个变量也是一样的
s[0][0] = 520;
s[0][1] = 1314;
s[2][2] = 1234;
printf("s[0][0] = %d\n", s[0][0]);//520
printf("s[0][1] = %d\n", s[0][1]);//1314
printf("s[2][2] = %d\n", s[2][2]);//1234
//二维数组的大小 = 行数*列数*单个元素的大小
printf("sizeof(s) = %ld\n", sizeof(s));//48 == 3 * 4 * sizeof(int)
//二维数组的数组名 也是一个常量
//也不能被赋值 不能++
//int s2[3][4];
//s2 = s;//错误的
//s2++;//错误的
//二维数组的元素再内存上也是连续的
printf("%p\n", &s[0][3]);
printf("%p\n", &s[1][0]);//连续的 一次相差一个 int
printf("%p\n", &s[1][3]);
printf("%p\n", &s[2][0]);//连续的 一次相差一个 int
//遍历二维数组
int i = 0;
int j = 0;
//外层循环控制行数
for(i = 0; i < 3; i++){
//内层循环控制列数
for(j = 0; j < 4; j++){
printf("%d ", s[i][j]);
}
printf("\n");
}
return 0;
}
15.4 二维数组的初始化
#include <stdio.h>
int main(int argc, const char *argv[])
{
//如果二维没有初始化 里面也都是随机值
//int s[3][4];
//二维数组的初始化格式
//以行为单位
//完全初始化
//int s[3][4] = {{1,2,3,4},\
{5,6,7,8},\
{9,10,11,12}};
//不完全初始化 没有初始化的位 也是用0初始化
//int s[3][4] = {{1,2},\
{5,6},\
{9}};
//不以行为单位
//完全初始化
//int s[3][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
//不完全初始化 没有初始化的位 也是用0初始化
//int s[3][4] = {1,2,3,4,5,6};
//全部初始化成0 --常用的用法
//int s[3][4] = {0};
//省略下标的初始化
//行数可以省略 但是列数不能省略 因为列数决定按行操作时的跨度
//如果给定的元素不够整行 也按照整行分配空间
//没有给定的位也是用 0 初始化
int s[][4] = {1,2,3,4,5,6,7,8,9};
printf("sizeof(s) = %ld\n", sizeof(s));//48 说明是按照整行分配的空间
//遍历二维数组
int i = 0;
int j = 0;
for(i = 0; i < 3; i++){
for(j = 0; j < 4; j++){
printf("%d ", s[i][j]);
}
putchar(10);
}
//数组一旦定义好了就不能整体赋值了 只能一位一位的赋值
return 0;
}
练习:
1.定义一个3行4列的二维数组,并以行为单位完全初始化
初始化的数据随便指定,找出数组中最大的元素,及最大元素所在的行号、列号,并输出
#include <stdio.h>
int main(){
int s[3][4] = {{12,23,34,45},{11,22,33,44},{456,1,2,3}};
int max_h = 0;
int max_l = 0;
int i = 0;
int j = 0;
for(i = 0; i < 3; i++){
for(j = 0; j < 4; j++){
if(s[i][j] > s[max_h][max_l]){
max_h = i;
max_l = j;
}
}
}
printf("max_value = %d max_hang = %d max_lie = %d\n",
s[max_h][max_l], max_h, max_l);
return 0;
}
2.使用10*10的二维数组保存杨辉三角的数据 并输出。
#include <stdio.h>
#define N 10
int main(){
int s[N][N] = {0};
s[0][0] = 1;
//循环给数组赋值
int i = 0;
int j = 0;
for(i = 1; i < N; i++){
s[i][0] = 1;//给每行的第一列赋值
//其他列的值循环
for(j = 1; j <= i; j++){
s[i][j] = s[i-1][j] + s[i-1][j-1];
}
}
//输出数组的值
for(i = 0; i < N; i++){
for(j = 0; j <= i; j++){
printf("%-4d", s[i][j]);
}
printf("\n");
}
return 0;
}
十六、字符数组和字符串
#include <stdio.h>
int main(int argc, const char *argv[])
{
//字符数组的使用
char s1[5] = {'h', 'e', 'l', 'l', 'o'};
int i = 0;
for(i = 0; i < 5; i++){
printf("%c", s1[i]);
}
printf("\n");
//也可以将字符串保存在字符数组中
//注意:字符串的结尾会有一个隐藏的 '\0' 所以要多分配一个字节来存储
//格式1
char s2[6] = {"world"};
//格式2//--常用
char s3[6] = "12345";
//格式3//--常用
char s4[] = "www.baidu.com";//这种写法 编译器计算长度时会包括 '\0'
printf("sizeof(s4) = %ld\n", sizeof(s4));//14
//如果是字符串 可以使用 %s 处理
printf("s2 = [%s]\n", s2);//world
printf("s3 = [%s]\n", s3);//12345
printf("s4 = [%s]\n", s4);//www.baidu.com
//注意 %s 处理的时候是从给定的首地址开始 一直向后面找
//找到 '\0' 才结束 如果没有 '\0'就一直找 可能会越界访问
char s5[12] = "aabb\0ccdd";
printf("s5 = [%s]\n", s5);//aabb
//ccdd也在数组中 只不过通过 %s 的方式 访问不到了
printf("s5[5] = %c\n", s5[5]);//c
printf("s5[7] = %c\n", s5[7]);//d
//如果不是字符串 就不能使用 %s 处理
//因为不是字符串 就没有 '\0'
//printf("s1 = [%s]\n", s1);//错误的 错误不可预知
//下面的用法是可以的 因为不完全初始化 会用0来初始化那些没有初始化的位
char s6[6] = {'h', 'e', 'l', 'l', 'o'}; // 0 就是 '\0'
printf("s6 = [%s]\n", s6);
return 0;
}
十七、字符串处理函数
strlen strcpy strcat strcmp
17.1 strlen
功能:
计算字符串的长度,不包括'\0'的
头文件:
#include
函数原型:
size_t strlen(const char *s);
参数:
要计算长度的字符串的首地址
返回值:
计算的结果
例:
#include <stdio.h>
#include <string.h>
int main(int argc, const char *argv[])
{
char hqyj[32] = "hello";
//可以直接输出
printf("strlen(hqyj) = %ld\n", strlen(hqyj));//5
//也可以用变量保存计算的结果
int len = strlen(hqyj);
printf("len = %d\n", len);//5
//注意 C语言中对字符串的处理 都是到 '\0' 就结束了
char hqyj2[32] = "hello\0wrold";
printf("strlen(hqyj2) = %ld\n", strlen(hqyj2));//5
//注意:如果不是字符串 就不能使用 strlen 计算长度
//因为 strlen会一直往后找 '\0' 错误是不可预知的
char hqyj3[5] = {'h', 'e', 'l', 'l', 'o'};
printf("strlen(hqyj3) = %ld\n", strlen(hqyj3));//5
//弄清楚 sizeof strlen 区别
// 1.sizeof是关键字 strlen 是函数
// 2.sizeof计算的是占用的内存的字节数 只和定义时有关
// strlen计算的字符串中第一个 '\0' 之前的字符的个数
char s[32] = "abcd";
printf("sizeof(s) = %ld\n", sizeof(s));//32
printf("strlen(s) = %ld\n", strlen(s));//4
return 0;
}
自己实现 strlen 函数的功能
#include <stdio.h>
int main(int argc, const char *argv[])
{
char s1[32] = "hello"; //5
#if 0
int count = 0;
while(s1[count] != '\0'){
count++;
}
#endif
#if 1
int count = 0;
while(s1[count]){
count++;
}
#endif
int count = 0;
while(s1[count++]);
count--;
printf("%d\n", count);
return 0;
}
17.2 strcpy
功能:
字符串的复制
头文件:
#include
函数原型:
char *strcpy(char *dest, const char *src);
参数:
src:源字符串
dest:目标字符串 dest指向的缓冲区要保证足够大 否则会越界
返回值:
就是dest --一般不关心
例:
#include <stdio.h>
#include <string.h>
int main(int argc, const char *argv[])
{
char s1[32] = "beijingnihao";
char s2[32] = "hello";
printf("操作前 s1 = [%s]\n", s1);//beijingnihao
printf("操作前 s2 = [%s]\n", s2);//hello
strcpy(s1, s2);//将s2 拷贝给 s1
printf("操作后 s1 = [%s]\n", s1);//hello
printf("操作后 s2 = [%s]\n", s2);//hello
//将短的字符串 copy 给长的时
//长的字符串后面剩下的部分还在里面
//只不过通过字符串的方式 访问不到了
printf("%c\n", s1[6]);//g
printf("%c\n", s1[7]);//n
printf("%c\n", s1[8]);//i
//注意 要保证 目标字符串足够大 否则越界 错误不可预知
//char s3[3] = {0};
//char s4[32] = "aklhdjkaasdjfh";
//strcpy(s3, s4);
//字符串赋值
char str[32] = {0};
//str = "hello";//错误的
strncpy(str, "hello", 6);//正确的
return 0;
}
练习:
自己实现 strcpy 函数的功能。
#include <stdio.h>
int main(int argc, const char *argv[])
{
char s1[32] = "beijingnihao";
char s2[32] = "hello";
printf("操作前 s1 = [%s]\n", s1);
printf("操作前 s2 = [%s]\n", s2);
//你的操作
int i = 0;
while(s2[i] != '\0'){
s1[i] = s2[i];
i++;
}
//将s2结束的 '\0' 也copy给s1 下面两种写法都可以
//s1[i] = '\0';
s1[i] = s2[i];
printf("操作后 s1 = [%s]\n", s1);
printf("操作后 s2 = [%s]\n", s2);
return 0;
}
17.3 strcat
功能:
字符串的拼接
头文件:
#include
函数原型:
char *strcat(char *dest, const char *src);
参数:
src:源字符串
dest:目标字符串 dest指向的缓冲区要保证足够大 否则会越界
返回值:
就是dest --一般不关心
例:
#include <stdio.h>
#include <string.h>
int main(int argc, const char *argv[])
{
char s1[32] = "beijingnihao";
char s2[32] = "hello";
printf("操作前 s1 = [%s]\n", s1);//beijingnihao
printf("操作前 s2 = [%s]\n", s2);//hello
strcat(s1, s2);//将s2 追加到 s1后面 会覆盖 s1的'\0'
printf("操作后 s1 = [%s]\n", s1);//beijingnihaohello
printf("操作后 s2 = [%s]\n", s2);//hello
//注意 要保证 目标字符串足够大 否则越界 错误不可预知
//char s3[3] = {0};
//char s4[32] = "aklhdjkaasdjfh";
//strcat(s3, s4);
return 0;
}
17.4 strcmp
功能:
字符串的比较
头文件:
#include
函数原型:
int strcmp(const char *s1, const char *s2);
参数:
s1 和 s2 就是要参与比较的两个字符串
比较方式:
两个字符串逐个的比较每个字符的ascii码
直到出现大小关系就立即返回
两个字符串中第一次出现 '\0' 之前的字符都相等 才认为两个字符串相等
注意:比的不是长度
返回值:
>0 s1>s2
==0 s1==s2
例:
#include <stdio.h>
#include <string.h>
int main(int argc, const char *argv[])
{
char s1[32] = {0};
char s2[32] = {0};
gets(s1);
gets(s2);
printf("操作前 s1 = [%s]\n", s1);
printf("操作前 s2 = [%s]\n", s2);
int ret = strcmp(s1, s2);
if(ret > 0){
printf("s1>s2\n");
}else if(ret<0){
printf("s1<s2\n");
}else{
printf("s1==s2\n");
}
//返回值时 s1 和 s2 中第一个不相等的字符 ascii码的差值 是 s1-s2的
//或者是0
printf("ret = %d\n", ret);
//带n的版本 表示只操作前n位
char s3[32] = "hello1234";
char s4[32] = "hello5678";
int ret2 = strncmp(s3, s4, 5);
printf("ret2 = %d\n", ret2);//0
return 0;
}
练习:
1.字符串分割:从buff中解析关键信息。
char buff[256] = "wechat:zhangsan:klrl:1314:2022-10-10:happy new year";
char logo[16];
char sender[16];
char recver[16];
char money[16];
char date[32];
char txt[128];
答案:
#include <stdio.h>
int main(int argc, const char *argv[])
{
char buff[256] = "wechat:zhangsan:klrl:1314:2022-10-10:happy new year";
char logo[16];
char sender[16];
char recver[16];
char money[16];
char date[32];
char txt[128];
int i = 0;
int j = 0;
//解析logo
while(buff[i] != ':'){
logo[j++] = buff[i++];
}
logo[j] = '\0';
//解析 sender
i++;
j = 0;
while(buff[i] != ':'){
sender[j++] = buff[i++];
}
sender[j] = '\0';
//解析 recver
i++;
j = 0;
while(buff[i] != ':'){
recver[j++] = buff[i++];
}
recver[j] = '\0';
//解析 money
i++;
j = 0;
while(buff[i] != ':'){
money[j++] = buff[i++];
}
money[j] = '\0';
//解析 date
i++;
j = 0;
while(buff[i] != ':'){
date[j++] = buff[i++];
}
date[j] = '\0';
//解析 txt
i++;
j = 0;
while(buff[i] != 0){
txt[j++] = buff[i++];
}
txt[j] = '\0';
printf("buff = [%s]\n", buff);
printf("logo = [%s]\n", logo);
printf("sender = [%s]\n", sender);
printf("recver = [%s]\n", recver);
printf("money = [%s]\n", money);
printf("date = [%s]\n", date);
printf("txt = [%s]\n", txt);
return 0;
}
如果字符串解析再涉及bit位的操作 会更复杂些,如:判断某一位是否为1
buff[0]; // 11101010
if( (buff[0] & (1<<5)) != 0){
//说明那一位是1
}
2.自己实现字符串转整型(atoi)
"1314" --> 1314
#include <stdio.h>
#include <stdlib.h>
int main(){
char str[32] = "1314";
int num = 0;
//你的操作
int i = 0;
while(str[i] != '\0'){
num *= 10;
num += (str[i] - '0');
i++;
}
printf("num = %d\n", num);//1314
int num2 = 0;
num2 = atoi(str);
printf("num2 = %d\n", num2);//1314
return 0;
}
十八、指针
18.1 概念
内存中每个字节的空间都有一个编号,这个编号就是指针,也叫作地址。
保存指针的变量叫做 指针变量。
在我们平时交流的时候:
指针:指针变量
地址:内存编号
18.2 指针相关的操作
& 取地址符,获取变量在内存上占用的空间的地址编号
对于多字节的变量取到的是编号最小的那个地址 叫做首地址
* 在定义指针变量的时候,只起到标识的作用,表示定义的是一个指针变量
其他场景下,都表示操作指针保存的那块内存空间
18.3 定义指针变量的格式
存储类型 数据类型 *指针变量名;
18.4 指针和变量的关系图
18.5 指针的基本使用及注意事项
#include <stdio.h>
int main(int argc, const char *argv[])
{
//当程序执行到定义变量的语句时
//会根据变量的类型分配对应大小的内存空间 用来存储数据
int a = 10;
// 通过 & 可以获取变量的地址
// 对于多字节的变量取到的是编号最小的那个地址 叫做首地址
printf("&a = %p\n", &a);
//地址的范围
//32位系统 0x0 ~ 0xFFFFFFFF 4字节
//64位系统 0x0 ~ 0xFFFFFFFFFFFFFFFF 8字节
//可以定义一个指针变量 来保存变量的地址
//指针保存了变量的地址 称之位 指针 p1 指向变量a
int *p1 = &a; //定义了一个 int * 类型的指针变量 p1 保存a的地址
printf("p1 = %p\n", p1);
//指针保存了变量的地址后 就可以通过指针 操作指向的空间的内容了
//读操作
printf("a = %d, *p1 = %d\n", a, *p1);//10 10
//写操作
*p1 = 520;
printf("a = %d, *p1 = %d\n", a, *p1);//520 520
//地址本质也是一个整数
//但是不能用普通的变量来存储地址
//long value = &a;
//printf("value = %p\n", value);//保存是可以保存
//*value = 1314;//但是普通变量不能取 * 操作
//指针类型的作用
//决定了指针存保存的地址开始 一共能访问多少个字节
//int * 指针 访问 4字节
//char * 指针 访问 1字节
//一般情况下 指针的类型要和指向的变量的类型 保持一致
//目的是为了操作空间的大小一致
//指针变量也是变量 值允许改变
//指针的值改变 我们称为改变了指针的指向
int b = 1234;
p1 = &b;
printf("&b = %p, p1 = %p\n", &b, p1);
//指针变量也需要分配内存空间
//一级指针的地址要用二级指针来保存 ----后面讲
printf("&p1 = %p\n", &p1);
//常量没有地址可言
//int *p2 = &100;//错误的
//指针只能保存已经分配了地址:变量的地址 或者 自己malloc的地址
//int *p3 = 0x1234;
//printf("p3 = %p\n", p3);//0x1234 保存没问题
//*p3 = 1314;//但是取*操作 错误是不可预知的
//一行可以定义多个指针 但是要住用法
//这种用法 p4 是指针 p5就是一个普通的int变量
//int *p4,p5;
//p4 = &a;
//p5 = a;
//这种写法才是正确的
int *p4,*p5;
p4 = &a;
p5 = &a;
//如果指针没有初始化 里面存的也是随机值 --就是一个随机的地址
//这种指向不确定的指针 我们称之为 野指针
//野指针是有害的,对野值的操作 错误是不可预知的
//我们程序员要注意 防止野指针
//int *p6;
//*p6 = 1314;
//printf("*p6 = %d\n", *p6);
//如果定义指针时没确定指向 可以先 用 NULL 初始化指针
//这种指针叫做 空指针
int *p7 = NULL;// NULL 本质是 (void *)0
//*p7 = 1314;//对 0 地址操作一定是段错误
return 0;
}
18.6 指针的运算
指针的运算本质就是指针保存的地址作为运算量来参与运算
所以,能进行的运算就是有限的。
注意:只有相同类型的指针变量做运算才有意义。
指针常见的运算:
算数运算:+ - ++ --
关系运算:> < == !=
赋值运算:=
#include <stdio.h>
int main(int argc, const char *argv[])
{
//指针做算数运算时 一般在连续的空间上才有意义
//两个指针做差
//得到的结果是 相差的指针的类型的个数
//和指针指向的变量的类型无关
int s[5] = {10,20,30,40,50};
int *p1 = &s[0];
int *p2 = &s[3];
printf("p2 - p1 = %ld\n", p2 - p1);//3 相差是3个int
//指针的强转 是 安全的 因为指针的大小是固定的
char *q1 = (char *)&s[0];
char *q2 = (char *)&s[3];
printf("p1 = %p q1 = %p\n", p1, q1);//一样的
printf("p2 = %p q2 = %p\n", p2, q2);//一样的
printf("q2 - q1 = %ld\n", q2 - q1);//12 相差是12个char
//一个指针加上一个整数 n
//得到的结果 是 加上 n个数据类型的大小
//int s[5] = {10,20,30,40,50};
int *p3 = &s[0];
int *p4 = NULL;
p4 = p3 + 2;
printf("p3 = %p, p4 = %p\n", p3, p4);//相差8 2个int
//关于++ -- 运算要注意下面的用法
//以++为例
int *p5 = &s[0];
printf("p5 = %p\n", p5);//&s[0]
int value1 = *++p5;
printf("value1 = %d\n", value1);//20
printf("p5 = %p\n", p5);//&s[1]
int *p6 = &s[0];
printf("p6 = %p\n", p6);//&s[0]
int value2 = *p6++;
printf("value2 = %d\n", value2);//10
printf("p6 = %p\n", p6);//&s[1]
//自己写代码的时候 别给自己找麻烦
//*++p --> p++; *p
//*p++ --> *p; p++
//关系运算
//int s[5] = {10,20,30,40,50};
int *p7 = &s[0];
int *p8 = &s[4];
if(p8 > p7){
printf("yes\n");
}else{
printf("no\n");
}
//赋值运算
p8 = p7;
if(p8 == p7){
printf("yes\n");
}else{
printf("no\n");
}
return 0;
}
练习:
1.思考下面的代码会输出什么
int *p = NULL;
printf("%d %d %d\n", p+1, p, (p+1)-p);// 4 0 1
// p 是NULL 也就是 (void *)0 -->0
// p+1 加的一个int 也就是 4号地址 -->4
// 相差的是1个int 所以 (p+1)-p -->1
2.定义一个变量a 类型为int 存储数据8888,
定义一个指针p 类型为 int * , 指向变量a
通过指针p 将 a 的值修改为 0x12345678;
再定义一个指针q 类型为 char *,也指向变量a
分别使用 %#x 输出 *q *(q+1) *(q+2) *(q+3) 的值
#include <stdio.h>
int main(){
int a = 8888;
int *p = &a;
*p = 0x12345678;
char *q = (char *)p;
printf("%#x %#x %#x %#x\n", *q, *(q+1), *(q+2), *(q+3));
//0x78 0x56 0x34 0x12
return 0;
}
大小端存储问题(字节序问题):
CPU和操作系统不同,对多字节数据存储的方式也不同。
小端存储:地址低位存储数据低位,地址高位存储数据高位
大端存储:地址低位存储数据高位,地址高位存储数据低位
请写一个简短的程序,来测试你使用的主机是大端存储还是小端存储?
#include <stdio.h>
int main(){
int a = 0x12345678;
char *p = (char *)&a;
if(0x78 == *p){
printf("小端\n");
}else if(0x12 == *p){
printf("大端\n");
}
return 0;
}
思考:
在小端存储的主机上,下面的代码会输出什么?
int num = 0x41424344;
printf("%s\n", &num); //DCBA+不确定的值
18.7 指针的大小
32位系统中 指针的大小 固定为 4字节
64位系统中 指针的大小 固定为 8字节
#include <stdio.h>
int main(int argc, const char *argv[])
{
char *a;
short *b;
int *c;
long *d;
long long *e;
float *f;
double *g;
printf("sizeof(char *) = %ld, sizeof(a) = %ld\n", sizeof(char *), sizeof(a));//8
printf("sizeof(short *) = %ld, sizeof(b) = %ld\n", sizeof(short *), sizeof(b));//8
printf("sizeof(int *) = %ld, sizeof(c) = %ld\n", sizeof(int *), sizeof(c));//8
printf("sizeof(long *) = %ld, sizeof(d) = %ld\n", sizeof(long *), sizeof(d));//8
printf("sizeof(long long *) = %ld, sizeof(e) = %ld\n", sizeof(long long *),
sizeof(e));//8
printf("sizeof(float *) = %ld, sizeof(f) = %ld\n", sizeof(float *), sizeof(f));//8
printf("sizeof(double *) = %ld, sizeof(g) = %ld\n", sizeof(double *), sizeof(g));//8
return 0;
}
思考:
在32位小端存储的主机上,下面的代码会输出什么?
int s[5] = {1,2,3,4,5};
int *p = (int *)((int)s+1);
printf("%x\n", *p); //2000000
编码:
有一个以空格分隔的字符串
"this is a book"
要求以单词为单位进行翻转
"book a is this"
//思路:
//先整体翻转
// "koob a si siht"
//再以单词为单位 分别翻转
// "book a is this"
#include <stdio.h>
int main(int argc, const char *argv[])
{
char s[32] = {0};
gets(s);
printf("整体操作前:[%s]\n", s);
//整体翻转
int i = 0;
int j = 0;
int k = 0; //用来记录空格的下标
char temp = 0;
//先让j保存最后一个元素的下标
while(s[j] != '\0'){
j++;
}
j--;//因为上面的循环结束时 j 是 '\0' 的下标 所以得 --
//交换前后的字符
while(i < j){
temp = s[i];
s[i] = s[j];
s[j] = temp;
i++;
j--;
}
printf("一步操作后:[%s]\n", s);
//再单独对每个单词翻转
i = 0;
j = 0;
while(s[k] != '\0'){
while(s[j] != ' ' && s[j] != '\0'){
j++;
}
k = j;//先保存一下空格的下标
j--;
while(i < j){
temp = s[i];
s[i] = s[j];
s[j] = temp;
i++;
j--;
}
i = k+1;//重置单词的起始地址
j = k+1;//重置单词的起始地址
}
printf("两步操作后:[%s]\n", s);
return 0;
}
18.8指针和一维数组
#include <stdio.h>
int main(int argc, const char *argv[])
{
int s1[5] = {10, 20, 30, 40, 50};
s1[0] = 1314;
//数组名 就是数组的首地址
//数组名[下标] 访问成员的本质
//就是以数组首地址为基准 偏移 然后取 * 操作
printf("s1[0] = %d, *s1 = %d\n", s1[0], *s1);//1314 1314
//也就是说 数组名这个地址 的操作空间是一个元素
printf("s1[1] = %d, *(s1+1) = %d\n", s1[1], *(s1+1));//20 20
//s1[i] <==> *(s1+i)
//可以定义一个指针来保存数组的首地址
//写法如下
int *p1 = s1; //数组名 就是首地址 这种是最常用的写法
int *p2 = &s1[0];//这种写法也可以
int *p3 = &s1;//注意:这种用法 保存的地址的值是一样的
//但是&s1 相当于给指针升维了 操作空间就变了
//后面讲数组指针时再讲原因 记住 不要对数组名取地址!!!
//下面5个的 值 是一样的
printf("s1 = %p\n", s1);
printf("&s1[0] = %p\n", &s1[0]);
printf("p1 = %p\n", p1);
printf("p2 = %p\n", p2);
printf("p3 = %p\n", p3);
//指针指向数组后 就可以通过指针来操作数组的元素了
*p1 = 520;
*(p1+1) = 1314;
p1[2] = 1234;
//指针指向数组后 有如下的等价关系
// s1[i] <==> *(s1+i) <==> p1[i] <==> *(p1+i)
//一位数组的遍历
int i = 0;
for(i = 0; i < 5; i++){
//printf("%d ", s1[i]);
//printf("%d ", *(s1+i));
//printf("%d ", *(p1+i));
printf("%d ", p1[i]);
}
printf("\n");
//指针指向数组后 p1 和 s1 的区别
//p1 是指针 是变量 可以改变指向
int s2[5] = {10, 20, 30, 40, 50};
p1 = s2;
//s1 是数组名 是地址常量 不能被赋值
//s1 = s2;//错误的
p1++;//正确的 指针的指向向后偏移 一个 int
s1++;//错误的 s1 是常量
return 0;
}
18.9指针和二维数组
#include <stdio.h>
int main(int argc, const char *argv[])
{
int s[3][4] = {{1,2,3,4},
{5,6,7,8},
{9,10,11,12}};
//二维数组的数组名也是一个地址常量 也就是数组的首地址
printf("s = %p\n", s);
//研究二维数组数组名的操作空间
printf("s+1 = %p\n", s+1);//依次相差 16 4 * sizeof(int)
printf("s+2 = %p\n", s+2);
//也就是说 二维数组数组名 的操作空间是一整行元素 +1 加的就是一整行
//我们也称之为 行指针
// * 操作可以给行指针 降维
// 本来操作空间是一行的指针 降维 成操作空间是一个元素的指针
printf("*s = %p\n", *s);//依次相差 4 也就是一个int
printf("*s+1 = %p\n", *s+1);
printf("*s+2 = %p\n", *s+2);
//对降维之后的指针再取 * 操作 才是操作里面的数据
printf("*(*s+1) = %d\n", *(*s+1));//2
printf("*(*(s+0)+1) = %d\n", *(*(s+0)+1));//2
printf("*(*(s+2)+1) = %d\n", *(*(s+2)+1));//10
printf("%d\n", *(s[1]+3));//8 ----是正确的用法 但是不常用
//s[i] <==> *(s+i)
//s[i][j] <==> *(*(s+i)+j) <==> *(s[i]+j)
//不能使用普通的指针来保存二维数组的首地址
int *p = s;
printf("p = %p, s = %p\n", p, s);//两个值一样
//但是不能这样做,原因是:
//二维数组的数组名 是一个 行指针 操作空间 是一整行元素
//已经超过了普通的指针的操作范围了
//普通的指针操作空间都是一个元素
//二维数组的遍历
int i = 0;
int j = 0;
for(i = 0; i < 3; i++){
for(j = 0; j < 4; j++){
//printf("%d ", s[i][j]);
//printf("%d ", *(s[i]+j));
printf("%d ", *(*(s+i)+j));
}
printf("\n");
}
return 0;
}
18.10数组指针
本质是一个指针,指向一个二维数组,也叫作行指针。
数组指针一般多用于函数传参,二维数组作为参数传递时。
格式:
数据类型 (*数组指针名)[列宽];
例:
#include <stdio.h>
int main(int argc, const char *argv[])
{
//可以使用二维数组来保存多个字符串
//行号 表示能存储的字符串的个数
//列号 表示每个字符串的最大长度
char s[4][64] = {"zhangsan",
"lisi",
"xiaoming",
"fulajier.fulajimiluoweiqi.pujing"};
printf("%s\n", &s[0][0]);
printf("%s\n", s[1]);
printf("%s\n", s[2]);
printf("%s\n", s[3]);
//但是这种操作不太好 因为要以最长的字符串为准
//会有空间上的浪费
printf("sizeof(s) = %ld\n", sizeof(s));// 4 * 64 * sizeof(char) = 256
printf("----------------------------------\n");
//也可以使用 指针数组 来处理这种问题
//定义了一个 指针数组 数组名叫 p 数组中共有 4个元素
//每个元素 都是一个 char * 类型的指针
char *p[4] = {NULL};
p[0] = "zhangsan";
p[1] = "lisi";
p[2] = "xiaoming";
p[3] = "fulajier.fulajimiluoweiqi.pujing";
printf("%s\n", p[0]);
printf("%s\n", *(p+1));//这种用法也可以
printf("%s\n", p[2]);
printf("%s\n", p[3]);
//这种情况就只分配多个指针的空间即可
printf("sizeof(p) = %ld\n", sizeof(p));// 4 * sizeof(char *) = 32 (64位系统)
return 0;
}
18.11指针和字符串
虚拟内存的分区:以32位系统为例
#include <stdio.h>
int main(int argc, const char *argv[])
{
//可以使用字符数组保存字符串
//这种操作 数组s1 在栈区 相当于将字符串常量区的 "hello world"
//复制一份儿到栈区保存 通过s1操作 是操作 栈区的数据
char s1[16] = "hello world";
printf("s1 = %s\n", s1);//hello world
//栈区的内存是允许修改的
*s1 = 'H';//正确的
printf("s1 = %s\n", s1);//Hello world
//栈区两个数组的地址 一定是不一样的
char s2[16] = "hello world";
printf("s1 = %p, s2 = %p\n", s1, s2);//不一样的
printf("-----------------------------------\n");
//也可以直接使用指针指向字符串常量
//但是 注意 这时 指针是指向的字符串常量区的 "hello world"
//字符串常量区 是不允许修改的
char *p1 = "hello world";
printf("p1 = %s\n", p1);//hello world 读取是可以的
//*p1 = 'H';//错误的 执行会报段错误
//不管多少个指针 只要指向的是同一个字符串常量 保存的地址就是一样的
char *p2 = "hello world";
printf("p1 = %p, p2 = %p\n", p1, p2);//一样的
return 0;
}
练习:
char a[] = "hello world";
char *p1 = a;
char *p2 = "hello world";
char *p3;
*p1 = 'M'; //正确的
char v1 = *p1++; //正确的
p1++; //正确的 p1的指向向后偏移一个字节
(*p1)++; //正确的 *p1 = *p1+1
*p2 = 'M'; //错误的 常量区不允许修改
char v2 = *p2++; //正确的 char v2 = *p2; p2++;
p2++; //正确的 p1的指向向后偏移一个字节
(*p2)++; //错误的 常量区不允许修改
*p3 = 'M'; //错误的 野指针
18.12二级指针
二级指针是用来保存一级指针的地址的
二级指针多用于函数传参,将一级指针的地址作为参数传递时。
格式:
数据类型 **二级指针名;
int a = 10;
int *p = &a;
int **q = &p;
上面的代码执行完之后,有如下的等价关系
**q *p a //操作的都是 金色的空间
*q p //操作的都是 蓝色的空间
q //操作的是 粉色的空间
变量、一级指针、二级指针的关系:
例:
#include <stdio.h>
int main(int argc, const char *argv[])
{
int a = 10;
int *p = &a;
//不能使用一级指针保存一级指针的地址
//保存值可以
//但是一级指针 没法 取 ** 操作 也就是说 无法访问 p指向的空间
//int *value = &p;
//printf("value = %p, &p = %p\n", value, &p);
//**value = 520;//错误的
//保存一级指针的地址 要使用二级指针
int **q = &p;
printf("**q = %d, *p = %d, a = %d\n", **q, *p, a);//一样的
printf("*q = %p, p = %p, &a = %p\n", *q, p, &a);//一样的
printf("q = %p, &p = %p\n", q, &p);//一样的
//二级指针的操作空间是 一个指针的大小
printf("q = %p, q+1 = %p\n", q, q+1);//相差 8 (64位系统)
return 0;
}
18.13const 关键字
const用来修饰变量,表示是一个只读变量,不可以修改。--readonly
#include <stdio.h>
int main(int argc, const char *argv[])
{
//const 修饰变量
const int value = 100;
//value = 520;//错误的
//const 用来修饰指针
//要注意 const 和 * 的相对位置关系
//如果 const 在 * 的左边 表示 修饰的 是 *p
//表示不能通过该指针修改指向的空间里的内容
//但是指针的指向是可以修改的
//如果 const 在 * 的右边 表示 修饰的 是 p
//表示指针的指向是不能修改的
//但是可以通过指针修改指向的空间里面的内容
//
// const int *p;
// int const *p;
// int * const p;
// const int * const p;
#if 0
//const int *p
int a = 10;
int b = 20;
const int *p = &a;
//*p = 1314;//错误的 不能通过指针修改指向空间的内容
//a = 1234;//正确的 通过变量自身可以修改
p = &b;//正确的 指针的指向 可以修改
#endif
//int const *p ----和上面的例子是一样的 一般使用上面的写法
#if 0
//int * const p
int a = 10;
int b = 20;
int * const p = &a;
//p = &b;//错误的 指针的指向不能修改
*p = 1314;//正确的 可以通过指针修改指向空间的内容
#endif
// const int * const p --都不能修改
int a = 10;
int b = 20;
const int * const p = &a;
//p = &b;//错误的
//*p = 1314;//错误的
return 0;
}
十九、函数
19.1 概念
所谓的函数,就是将能实现某些功能的代码封装成代码块,当想使用这个功能的时候
只要通过代码块的名字就可以调用这个功能,无需再写重复的代码了。
这个代码块就叫做函数,代码块的名字就是函数名。
如 printf scanf strlen strcat atoi 等
19.2 函数的定义和调用
返回值类型 函数名(函数的参数列表){
//函数体 -- 也就是实现功能的代码块
}
例
#include <stdio.h>
//void 是函数的返回值类型 如果函数没有返回值 可以写成 void 但是不能不写
//my_function 是函数名 是一个标识符 要符合标识符命名规范
//()里面是函数的参数列表 如果函数没有参数
//可以空着不写 或者写成void 但是注意括号必须写
//{}里面的是函数体,也就是我们用来实现功能的代码块
//return 是用来返回函数执行的结果的 如果没有返回值 可以不写 或者写成 return;
//如果有返回值 返回的类型要和 函数名前面的类型保持一致
void my_function(void){
int i = 0;
for(i = 0; i < 3; i++){
printf("hello world\n");
}
return;
}
//一个程序是由一个main函数和若干个字函数构成的
void abcd(){
printf("abcd\n");
my_function();
}
int main(int argc, const char *argv[])
{
printf("----start----\n");
//函数的调用
//函数一旦定义好之后 就可以被其他的函数调用了
//调用函数的格式 函数名(参数) 如果没有参数 ()也必须写 且不能写 void
//程序执行到调用函数的语句时 就会跳转到函数中执行 执行完函数后
//回到调用处 继续向下执行
my_function();
//函数如果不被调用 是不会执行的
abcd();
printf("----end----\n");
return 0;
}
执行结果:
19.3 函数的声明
如果把所有的函数都定义在 main 函数的上面,在main函数中调用是没有问题的。
但是函数之间相互调用就可能出现不认识的情况,这时就需要用到函数的声明。
#include <stdio.h>
//函数的声明
void my_print();
void abcd();
int main(int argc, const char *argv[])
{
my_print();
return 0;
}
void my_print(){
printf("hello world\n");
abcd();
}
void abcd(){
printf("abcd\n");
}
19.4 函数的参数
函数为什么要有参数?
在函数中实现功能的过程中,有些值,函数内部没有,
就需要调用者通过参数的方式给函数传递过去。
#include <stdio.h>
//void my_add(int x, int y);//也可以
void my_add(int, int);//有参数的函数声明 可以只写参数的个数和类型 不写参数名
void my_sub(const int x, const int y);//定义中形参有const 声明时也要有
int main(int argc, const char *argv[])
{
//有参数的函数调用
//函数的调用处的()里面的叫做函数的实际参数 --简称 实参
my_add(10, 20);
int a = 100;
int b = 200;
my_add(a, b);
my_sub(b, a);
return 0;
}
//在定义函数的时候 () 里面的叫做函数的形式参数 --简称形参
//在调用函数的时候,操作系统会给形参分配空间 然后用实参来初始化形参
//相当于 在调用函数的过程中 隐藏了这样的代码
//int x = 10;
//int y = 20;
//在函数中使用形参 就相当于 使用了实参的 值
//在函数中对形参的修改 并不会直接影响到实参 因为实参和形参不在同一块内存空间上
//形参的作用域和生命周期都是整个函数内部
//在函数调用结束的时候 操作系统会回收 形参占用的内存空间
void my_add(int x, int y){
printf("%d\n", x+y);
}
//形参加const 的作用 是不允许在函数内部修改形参的值
//保护形参不被修改
//有些场景 加 const会更合理一些
void my_sub(const int x, const int y){
printf("%d\n", x-y);
}
19.5 函数的返回值
函数为什么要有返回值?
有些场景,函数执行的结果,并不是说直接打印到终端就结束了。
因为后面的代码可能会用到函数的执行结果。
如 strlen 函数返回结果给调用者,调用者可以自己决定如果处理这个执行的结果。
如果需要用到函数的返回值就写 如果不需要用,也可以不写。
#include <stdio.h>
int my_add(int x, int y){
printf("my_add\n");
int temp = x+y;
//如果有返回值 返回的可以是变量/常量/表达式
//要注意要和函数名前面的类型 保持一致
return temp;
//函数中遇到return 就立即返回 后面的代码都不执行了
printf("----my_add end----\n");//不会被执行
}
int main(int argc, const char *argv[])
{
int a = 10;
int b = 20;
//函数返回的结果会返回到调用处 调用者可以决定是否保存
//以及如何处理返回值
int ret = my_add(a, b);
printf("ret = %d\n", ret);
//有返回的函数 也可以不接返回值
//不接返回值 函数也会被调用 就是没有执行结果了
my_add(a, b);
return 0;
}
一般情况下,定义函数都是有返回值的,可以利用函数的返回值来判断函数的执行情况。
如下面的伪代码:
//程序初始化的函数
#define ERR_NET -1
#define ERR_DATABASE -2
#define ERR_FILE -3
#define SUCCES 0
int pro_init(){
if(连接网络失败){
return ERR_NET;
}
if(连接数据库失败){
return ERR_DATABASE;
}
if(打开日志文件失败){
return ERR_FILE;
}
return SUCCES;
}
int main(){
//调用初始化的函数
int ret = pro_init();
if(ret != SUCCES){
执行对应的异常处理
}
return 0;
}
练习:
自己封装一个能计算 m~n 求和的函数
其中m和n由参数传递 通过返回值 返回计算的结果
#include <stdio.h>
/*
功能:计算 m~n求和
参数:
m 起点
n 终点
返回值:求和的结果
作者:yangfs
时间:2022/10/12 11:36
备注:xxxx
*/
int my_sum(int m, int n){
if(m > n){
printf("参数不合理 调用失败\n");
return -1;
}
int i = 0;
int sum = 0;
for(i = m; i <= n; i++){
sum += i;
}
return sum;
}
int main(int argc, const char *argv[])
{
int ret = my_sum(1, 100);
printf("ret = %d\n", ret);//5050
return 0;
}
19.6 全局和局部
#include <stdio.h>
//生命周期:占用的空间何时被回收
//作用域:在哪个范围可以访问
//没有被任何{}包住的就叫做全局变量
//----如果初始化了就在data段 没初始化就在bss段
//生命周期:整个程序结束
//作用域:整个文件
int value3 = 300;
int value4;//全局变量不初始化也都是 0
int my_function(){
//printf("value1 = %d\n", value1);//错误的
int value2 = 200;
printf("value2 = %d\n", value2);//正确的 200
printf("func:value3 = %d\n", value3);//正确的 300
}
int main(int argc, const char *argv[])
{
//被{}扩住的变量都叫做局部变量--在栈区
//生命周期:就是最近的{}结束
//作用域:当前最近的{}内部
int value1 = 100;
printf("value1 = %d\n", value1);//正确的 100
//printf("value2 = %d\n", value2);//错误的
printf("main:value3 = %d\n", value3);//正确的 300
printf("main:value4 = %d\n", value4);//正确的 0
my_function();
return 0;
}
19.7 函数的传参方式
19.7.1 全局传参
----了解就行,不常用
#include <stdio.h>
//因为任何一个函数都可以访问全局变量
//函数调用多的时候,可能会造成冲突 --一般不是用这种方式传参
int lvalue;
int rvalue;
int ret;
void my_add(void){
ret = lvalue + rvalue;
}
int main(int argc, const char *argv[])
{
lvalue = 100;
rvalue = 200;
my_add();
printf("ret = %d\n", ret);//300
return 0;
}
19.7.2 复制传参(值传递)
#include <stdio.h>
//功能:将x和y扩大10被,计算的结果赋值给z
//这种传参方式 叫做复制传参(值传递)
//不管在函数内部如何修改形参 实参都不会发生变化
//因为形参和实参不在同一块内存空间上
void my_func(int x, int y, int z){
x = x * 10;
y = y * 10;
z = x+y;
printf("x = %d y = %d z = %d\n", x, y, z);//100 200 300
printf("&x = %p &y = %p &z = %p\n", &x, &y, &z);//和下面不一样
}
int main(int argc, const char *argv[])
{
int m = 10;
int n = 20;
int ret = 0;
my_func(m, n, ret);
printf("m = %d n = %d ret = %d\n", m, n, ret);//10 20 0
printf("&m = %p &n = %p &ret = %p\n", &m, &n, &ret);//和上面不一样
return 0;
}
19.7.3 地址传参(地址传递)
#include <stdio.h>
//函数功能:将x和y扩大10被,计算的结果赋值给 实参ret
//x和y传参方式 叫做复制传参(值传递)
//而 z 的传参方式叫做地址传参
//不管在函数内部如何修改形参 实参都不会发生变化
//因为形参和实参不在同一块内存空间上
void my_func(int x, int y, int *z){//z中保存的是 实参 ret 的地址
x = x * 10;
y = y * 10;
*z = x+y;//对 *z 的操作 就是对 实参 ret 的操作
printf("x = %d y = %d *z = %d\n", x, y, *z);//100 200 300
printf("&x = %p &y = %p z = %p\n", &x, &y, z);//&x &y 和 下面 &m &n 不一样
//但是 z 就是 &ret
}
int main(int argc, const char *argv[])
{
int m = 10;
int n = 20;
int ret = 0;
my_func(m, n, &ret);
printf("m = %d n = %d ret = %d\n", m, n, ret);//10 20 300
printf("&m = %p &n = %p &ret = %p\n", &m, &n, &ret);
return 0;
}
一级指针的值传递和地址传递!
#include <stdio.h>
int m = 10;
int n = 20;
//一级指针的值传递
void my_test1(int *q){
q = &n;
}
//一级指针的地址传递 需要用二级指针做形参
void my_test2(int **q){
*q = &n;
}
int main(int argc, const char *argv[])
{
int *p = &m;
printf("%d\n", *p);// 10
my_test1(p);
printf("%d\n", *p);// 10
my_test2(&p);
printf("%d\n", *p);// 20
return 0;
}
19.8 数组的传参方式
19.8.1 字符串的传参
字符串传参只需要传递首地址即可,因为字符串有 '\0' 做为结束的标志
#include <stdio.h>
int my_strlen(char *p){
*p = 'H';//数组传参 传首地址相当于 地址传递 通过指针是可以修改实参的
//所以这种情况一般都使用 const 修饰形参的指针 防止修改
int count = 0;
while(*p != '\0'){
count++;
p++;
}
return count;
}
int main(int argc, const char *argv[])
{
char s[32] = "hello world";
int ret = my_strlen(s);
printf("ret = %d\n", ret);
printf("s = %s\n", s);//Hello world
return 0;
}
19.8.2 一维整型数组传参
既要传首地址,还要传数组的长度。
#include <stdio.h>
//遍历一维数组的函数
//首地址和长度 都要传
//
//这种写法 p 也是一个指针这种用法叫做代码的自注释
//void print_arr(int p[5], int len){//这种方式传参也可以
//void print_arr(int p[], int len){//这种方式传参也可以
void print_arr(int *p, int len){//--常用的用法
int i = 0;
//for(i = 0; i < sizeof(p); i++){ //这种用法不行 sizeof(p)只是一个指针的大小
for(i = 0; i < len; i++){
printf("%d ", p[i]);
}
printf("\n");
printf("sizeof(p) = %ld\n", sizeof(p));//8
}
int main(int argc, const char *argv[])
{
int s[5] = {10, 20, 30, 40, 50};
print_arr(s, 5);
return 0;
}
19.8.3 二维数组的传参方式
#include <stdio.h>
//遍历二维数组的函数
//
//二维数组传参 需要用到 数组指针
void print_arr(int (*p)[4], int hang, int lie){
int i = 0;
int j = 0;
for(i = 0; i < hang; i++){
for(j = 0; j < lie; j++){
printf("%d ", p[i][j]);
}
putchar(10);
}
}
int main(int argc, const char *argv[])
{
int s[3][4] = {{1,2,3,4},
{5,6,7,8},
{9,10,11,12}};
print_arr(s, 3, 4);
return 0;
}
练习:
封装一个能对整型一维数组的元素求和的函数,返回值返回计算的结果。
#include <stdio.h>
int my_sum(int *p, int len){
int sum = 0;
int i = 0;
for(i = 0; i < len; i++){
sum += p[i];
}
return sum;
}
int main(int argc, const char *argv[])
{
int s[5] = {1,2,3,4,5};
printf("%d\n", my_sum(s, 5));
return 0;
}
19.9 main函数的参数
#include <stdio.h>
//argv 本质是一个指针 指向一个成员都是char * 类型的指针数组
int main(int argc, const char *argv[])
{
//argc 是命令行执行程序时参数的个数 (包括可执行文件名)
printf("argc = %d\n", argc);
//如果执行 命令时 使用的是 ./a.out file1 file2
//printf("argv[0] = %s\n", argv[0]);// "./a.out"
//printf("argv[1] = %s\n", argv[1]);// "file1"
//printf("argv[2] = %s\n", argv[2]);// "file2"
//遍历命令行的所有参数
int i = 0;
for(i = 0; i < argc; i++){
printf("%s\n", argv[i]);
}
return 0;
}
练习:
使用命令行传参的方式实现简易的计算器功能:
例如:输入 ./a.out 10 + 20 输出 30
输入 ./a.out 50 - 30 输出 20
#include <stdio.h>
#include <stdlib.h>
int main(int argc, const char *argv[]){
int lvalue = atoi(argv[1]);
int rvalue = atoi(argv[3]);
//switch(argv[2][0]){//这种写法也行
switch(*argv[2]){
case '+':
printf("%d\n", lvalue + rvalue);
break;
case '-':
printf("%d\n", lvalue - rvalue);
break;
}
return 0;
}
19.10 指针函数
本质是一个函数,返回值是一个指针类型。
注意:指针函数不能返回局部变量的地址
因为函数调用结束后,局部变量占用的内存空间就被操作系统回收了
可以返回:
1.全局变量的地址
2.static关键字修饰的局部变量的地址
3.由参数传递过来的地址
#include <stdio.h>
int temp2 = 0;
int *my_add(int x, int y){
//int temp = x+y;
//return &temp;//错误的 不能返回局部变量的地址
#if 0
//正确的 可以返回全局变量的地址
temp2 = x+y;
return &temp2;
#endif
//static关键字可以将局部变量的生命周期延长至整个程序结束
static int temp3 = 0;
temp3 = x+y;//正确的
return &temp3;
}
char *my_strcpy(char *dest, const char *src){
char *temp = dest;
while(*src){
*temp++ = *src++;
}
*temp = *src;
return dest;//也可以返回由参数传递过来的地址
}
int main(int argc, const char *argv[])
{
int *p = my_add(10, 20);
printf("%d\n", *p);
char s1[32] = "12345678";
char s2[32] = "hello";
char *q = my_strcpy(s1, s2);
printf("%s\n", q);//hello
printf("q = %p s1 = %p\n", q, s1);//相等的
return 0;
}