STD C
C语言补充
0. Linux基础
0.1 常用Linux命令
-
Alt + Ctrl + t 快速打开终端
-
Ctrl+Shift + t 在终端里打开新的窗口
-
alt + 数字键 在不同的窗口间切换
-
ctrl + shift + + 放大字体
-
ctrl + - 字体缩小
-
tab键 快速补全
-
ctrl + l 快速清屏
-
F11 窗口最大化
-
pwd:当前所在的路径
-
ls : 显示目录里的内容
- ls -l :查看文件的详细信息(属性)
- -rwxrw-r-- 1 tarena tarena 70 Jun 25 16:36 qw.c
- 说明:
- ‘-’:表示此文件就是一个普通的文件
- ‘rwx’:表示当前登录linux的用户对此文件可以读(查看文件),可以写(向文件写入信息),可执行
- ‘rw-’:表示当前登录linux的用户所在的组对此文件可以读,可以写,不可执行运行
- ‘r–’:表示其他的linux用户对此文件可以读,不可写,不可执行
- ‘1’:表示硬连接数(不关注)
- ‘tarena’:表示当前登录linux系统的用户名
- ‘tarena’:表示tarena用户所在的组
- ‘70’:表示此文件大小为70个字节
- “Jun 25 16:36”:文件的创建日期
- ‘qw.c’:文件名
- -rwxrw-r-- 1 tarena tarena 70 Jun 25 16:36 qw.c
- ls -a : 显示当前目录下所有的文件,包含隐藏文件
- ls -l :查看文件的详细信息(属性)
-
cd : 切换路径
- / 根目录
- ~ 用户目录
- . 当前目录
- … 返回上一目录
-
mkdir:创建目录
- mkdir 目录名 :只能创建一级目录
- mkdir -p /目录1/目录2/···:递归创建目录(都这样用就行啦)
-
touch: 创建新文件
- touch biji.txt
- touch zc/bj.txt
-
cp: 复制
- cp 要复制的文件 要复制到的目录
- cp xly/day04/bj.txt /home/xly/day05
-
mv: 移动(剪切)
- mv 要移动的文件 要移动到的目标目录
- mv 原名字 新名字
-
rm: 删除
- rm 文件名
- rm -rf 文件名 (force remove)
-
cat 命令:快速查看文件的内容
- cat hello.txt
-
echo命令:向显示器打印输出数据命令
- echo "我是大神"或者echo 我是大神
- 重定向
- > : echo 我是大神 > hello.txt 意思是用新内容将hello.txt文件内容覆盖
- >>:echo 我是小神 >> hello.txt 意思是:将新内容追加到文件的后面
-
find命令:到某个指定的路径下找文件或者目录
格式:find 路径 -name 要找的文件名
例如:find /usr/include -name stdio.h
语义:到/usr/include目录下找一个文件名为stdio.h这个文件 -
grep命令:到某个路径下的某个文件中搜索字符串
例如:grep “我是大神” * -Rn
语义:在当前目录下的所有文件中搜索我是大神字符串
“*”:表示全部文件
“-Rn”:到子目录下也同样搜索,并且打印字符串所在的行号 -
压缩
- tar -jcvf 压缩包.tar.bz2 目录或者文件
- tar -zcvf 压缩包.tar.gz 目录或者文件
- tar -jcvf hello.tar.bz2 hello/ //把hello目录压缩成hello.tar.bz2压缩包
- tar -zcvf hello.tar.gz hello/ ///把hello目录压缩成hello.tar.gz压缩包
-
解压缩
- tar -xvf 压缩包.tar.bz2 / 压缩包.tar.gz
1. Vim基础
- vim三种模式:可视模式,编辑模式,命令行模式
- 可视模式:只能查看内容,不能修改内容
- 编辑模式:即可查看也可修改
- 命令行模式:可以给vim发送控制命令,此模式也是能看不能修改
注意:刚打开文件是,默认为可视模式
- 三种模式的转换
- 可视模式--------按i键或者o键或者a键------>编辑模式
- 编辑模式--------按ESC键-------------------->可视模式
- 如果目前是编辑模式,先按ESC进入可视模式,然后按shift+:进入命令行模式
- 如果目前是可视模式,直接按shift+:进入命令行模式
- 掌握vim命令行模式常用的命令
- w:保存文件
- q:不保存退出
- wq:保存退出
- q!:强制不保存退出
- %s/老信息/新信息/g:字符串,单词替换
1. 例如:%s/我是大神/whoami/g - 直接输入行号:直接跳转到第几行
- vs 文件名:左右分屏
- sp 文件名: 上下分屏
1. 注意:屏幕之间的切换用快捷键:ctrl+ww(前提是先进入可视模式
- 掌握vim常用的可视模式下的快捷键(vim强大的地方)
- h(左移)j(下移)k(上移)l(右移):严重鄙视用上下左右键移动!
- 行选中:shift+v然后方向键选中
- 列选中:ctrl+v然后方向键选中
- 选中某个单词:先按v然后按左右键选中
- 复制:y
- 粘贴:p
- 剪切:x
- 撤销:u
- 撤销后又反悔:ctrl+r
- 跳转到文件的末尾:G
- 跳转到文件的开头:gg
- 单个字母替换:先按R,然后输入新的字母
- 保存并且退出文件:ZZ
- 自动补全:ctrl+n(前提是先进入编辑模式,输入一个单词的开头,然后按ctrl+n自动补全)
- 高亮显示:先输入/然后跟要高亮的单词
- 如果要去掉高亮,在命令行模式输入::nohl
2.
visualstdio里用scanf();要有 *#define _CRT_SECURE_NO_WARNINGS* 宏定义(放在首行)或者 #pragma warning(disable:4996)*(程序的任何位置都可)
1.常量:const 在程序运行过程中,其值不发生改变,也称只读变量
2.sizeof(数据类型/变量名) 计算数据类型在内存中占的字节(BYTE)大小,返回值是无符号整型(%u)
3.数据类型大小
- int:4 byte
- short:2 byte
- long
- windows下:4 byte;
- Linux:
- (32位)4 byte
- (64位)8 byte
- long long:8 byte
- double:8 byte
- char:1 byte
- 指针类型(int*/char*…:)4 byte
4.C语言表示进制数
- %d 十进制 就是正常数字
- %o 八进制 以数字0开头,如0123
- %x/%X十六进制 以0x/0X开头,如0x123/0X123
- c语言不能直接书写二进制数
3
1.字符(串)常量
char a='a';
单个字符单引号- 定义字符串
-
指针定义字符串每个字符串结尾都是\0,\0是占位符 表示输出一个字符串 遇到\0停止.
代码: char* b="Hello world\n"; char* c = "hello\0 world"; //C++里面新学了 char str[] = "Hello world" 输出 Hello world hello
-
数组定义字符串
如果用数组定义一个字符串,一定要留出\0的位置不然就会出现乱码char d[11] = "hello world";
-
2.字符输入输出
- putchar():括号内可以是变量 字符 数字(0-127) 转义字符 输出是单个字符
char ch='a'; putchar(ch); putchar('A'); putchar('\t'); putchar(97); putchar('\n');
- getchar():接收键盘获取字符,可以用来接收回车,也可以用于暂时停留界面
char ch1; ch = getchar(); putchar(ch);
3.运算符
- % 只能对整型操作
int a = 10,b=3,c;
c = a % b;
- ++/–
c = a++;//后自增输出:a=3,c=2先计算表达式再给a赋值
c = ++a;//前自增a=3,c=3先给a赋值,再计算表达式
c = a--;//后自减 a=1,c=2 1
c = --a;//前自减 a=1,c=1
- bool运算符:1表示真; 0表示假
- 逻辑运算符(双目运算符)
- & 按位与
- | 按位或
- ^ 按位异或
- && 与
- || 或
- !非
- 条件运算符(三目运算符)
- 形式:表达式1?表达式2:表达式3
- 判断表达式1:如果表达式为真(1)执行表达式2,否则执行表达式3
- 功能较小,但代码量少
#define MAX(a,b) (a)>(b)?(a):(b)/*定义了个比较大小的函数加括号是为了 防止调用表达式时优先级不同产生歧义*/
4.类型转换
- 隐式转换:遵循一定的规则由编译系统自动完成
- 类型转换原则:占用内存字节数少(值域小)的类型,向占用字节数多的类型转换,以保证精度不降低。
- 大小:char,short < signed int < unsigned int < long < double > float
- 类型转换原则:占用内存字节数少(值域小)的类型,向占用字节数多的类型转换,以保证精度不降低。
- 强制类型转换
- 格式:(数据类型)强制类型转换运算符 不会四舍五入
输入 double p = 3.14; int w = 2; int mul = (int)p * w; printf("%d\n", mul); sum = (double)mul; printf("%lf\n", sum); char ch = (char)mul; printf("%c", ch); 输出 6 6.000000
- 格式:(数据类型)强制类型转换运算符 不会四舍五入
5.语句
- switch语句
switch (整型或者枚举型)
{
case(10):printf("优秀");break;
case(9):printf("优秀");break;
case(8):
case(7):
printf("良好");//满足7/8两个条件,执行此条语句
break;
case(6):
printf("及格");
default://相当与if语句中的else:用于那些没必要全部列举出来的情况,
没有不必要的情况时可以不用default
printf("叫你家长来");
break;
}
- while/do while
- while 略
- do while
do { }while(条件);
- for
- for(1.数据类型 变量赋初值“执行一次”可在语句外赋初值 ; 2.条件判断“如果满足,运行{}” ; 3.范围约束“{}执行完后执行”然后再判断)
- 语句外赋值,1.可空
(1)在循环语句中如果出现 break; 代表在此处跳出所在循环
- 死循环
- for(;😉
- while(1)
- 嵌套循环
- //外层执行一次,内层执行一个周期
例1:打印九九乘法表 for (int i = 1; i < 10; i++) { for (int j = 1; j < i; j++) { printf("%d*%d=%d ", j, i, i * j); } printf("\n"); }
例2:模拟时钟 for (int hour = 0; hour < 23; hour++) { for (int minute = 0; minute < 60; minute++) { for (int sec = 0; sec < 60; sec++) { system("cls");//苍老师hh,清屏功能 printf("%02d:%02d:%02d", hour, minute, sec); Sleep(960);//<Windows.h>函数,参数为毫秒//因为执行循环需要时间,所以不是(1000) } } }
4
4.1跳转语句
-
break语句
- 在switch语句中,终止某个case,并跳出switch语句
- 在循环语句中,作用是退出当前循环,执行后面的代码
- 在嵌套循环语句中,跳出最近的内循环语句,执行后面的代码*/
-
continue语句
- 在循环语句中,立即终止本次循环,并执行下一次循环(还在循环中)
-
goto语句(不推荐使用)
- 格式
goto 标志(如FLAG);//此语句非常自由 标志(FLAG)://标志可以放在任意位置
4.2.1数组的定义和使用
-
概念:数组就是在内存中连续的相同类型的变量空间
-
数组定义:
- 基本格式1:数据类型 数组名[元素个数]={值1,值2,。。。。};
- 基本格式2:
int arr[]; int arr[10]={1,2,3,4}; int arr[10]={1};
- 还可以这样:
//先#define i 10 //然后 int arr[i]={};这样也是可以的, 因为i被定义成了常量
数组不能这样定义
int arr[]; 此时计算机无法开辟内存空间,[]内无数字 int i=10;int arr[i]={.,.,...};[]内必须是 常量 =>数组定义,要么[]里有具体的值,要么[]={};{}里有值
4.注意事项:
-
数组不能动态改变
-
不能通过scanf之类获取数组元素个数,因为获取的数组元素
个数(i)是变量,而(i)应该是常量 -
数组必须预先知道大小
-
数组可以赋值以及运算
-
数组在内存中的存储方式及大小
1.for (int j = 0; j < 10; j++) { printf("%p\n", &arr[j]); 1. &是取地址符,同一个数组中的元素地址是连续的,显示的都是首地址 printf("%p\n", &arr); 2. 数组名是一个地址常量,指向数组收地址 printf("%u\n", sizeof(arr[j])); 3. 数组元素所占大小由数组类型决定,数组在内存中占的大小=数组类型*元素个数 int arr2[] = { 0,1,2,3,4,5 }; for (int k = 0; k < sizeof(arr2)/sizeof(arr[0]); k++) { printf("\t%d\n", k); } 4.这样可以获取数组元素个数 }
- 数组下标越界
int arr[10] = { 9,8,2,5,6,4,7,1,3,0 }; for (int z = 0; z < 20; z++) { printf("%d\n", arr[z]); //10<20所以越界 } printf("%d", arr[10]); //这也是越界 return 0;
数组下标越界,可能会报错,内存允许读就不报错,不允许就报错
4.2.1数组强化训练1
- 十只小猪称体重
#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> int main() { printf("练习:十只小猪称体重\n"); int weight,max=0,NO; int pig[11]; for (int i = 1; i <= 10; i++)//获取十只小猪的体重 { printf("请输入第 %d 只小猪的体重:", i); //垃圾算法 //scanf("%d", &weight); //pig[i] = weight; //将小猪体重存入数组,注意pig[0]未赋值 scanf("%d", &pig[i]); } for (int j = 1; j <= 10; j++) { if (pig[j] > max)//优化:把max换成pig[1],j=2,减少了比对的次数 { max = pig[j]; NO = j; } } printf("最重的小猪是%d号小猪,体重是%d", NO, max); return 0; }
4.2.2数组强化训练2
- 数组逆置
#include<stdio.h>
int main4()
{
printf("数组逆置\n");
int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
int temp;
int i=0;//数组下标正序
int j = sizeof(arr) / sizeof(arr[0]) - 1;//数组下标逆序
while (i < j)
{
temp = arr[i];
arr[i] = arr[j];
arr[j] = temp;
i++;
j--;
}
for (int k = 0; k < sizeof(arr) / sizeof(arr[0]); k++)
{
printf("%d\n", arr[k]);
}
return 0;
}
4.2.3冒泡排序法
#include<stdio.h>
int main5(void)
{
printf("冒泡排序法\n");
//数据与相邻数据比较
//每轮比较次数为:元素个数-1-执行次数, 内层执行次数
//每轮比较都会比较出一个值,n个数执行n-1次, 外层(每轮)执行的次数
int arr[] = {99,66,87,85,86,95,94,59,59,60};
int num = sizeof(arr) / sizeof(arr[0]);
//外层比对行
for (int i = 0; i < num-1; i++)//每轮//num-1是因为最后一个数不用比较就已经确定
{
//内层比对列
for (int j = 0; j < num-1-i; j++)//num-1-i:每轮确定一个数在数组中的位置,需要比较的数字就-1,那么第n轮就-n
{
//比较两个元素 满足条件交换
if (arr[j] > arr[j + 1])//比较j和j+1是因为在内层循环中比较//>/<决定升序还是降序
{
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j+1] = temp;
}
}
}
for (int k = 0; k < num; k++)
{
printf("%d\n", arr[k]);
}
return 0;
}
4.3.0二维数组
-
概念:二维数组可以看成连续的一维数组
-
形式:
数据类型 数组名[行][列] { {值1,值2}, {值3,值4} }; 例、int arr[2][3]= { {1,2,3}, {4,5,6} };
还有这几种写法
1.int arr[2][3]={{1,2,3}{4,5,6}} 2.int arr[2][3]={1,2,3,4,5,6} 3.int arr[][3]={{1,2,3}{4,5,6}} 4.int arr[][3]={1,2,3,4,5,6,7}多出来的第三行是7 0 0 int arr[][] 这种不行 !!!多维数组 只有靠近数组名的[]里的数可以省略
3.遍历二维数组
for (int i = 0; i < 2; i++)
{
for (int j = 0; j < 3; j++)
{
printf("%d 地址:%p ", arr[i][j],&arr[i][j]);
}
printf("\n");
}
==>外层控制行,内层控制列
==>无论几维的数组,在内存中都是连续的空间
-
二维数组的几种运算
- 二维数组大小:sizeof(arr));
- 二维数组一行大小:sizeof(arr[0]));
- 二维数组元素大小:sizeof(arr[0][0]));
- 二维数组行数:sizeof(arr) / sizeof(arr[0]));
- 所有元素除以行数
另:arr[0]=100;//err
- 所有元素除以行数
- 二维数组列数:sizeof(arr[0]) / sizeof(arr[0][0]));
- 行数/单个元素
- 二维数组首地址arr * 数组打印地址时可以不用加&
- 二维数组第1行的首地址:arr[0]);
- 二维数组第2行的首地址:arr[1]);
- 因为是2行3列,所以第一二行首地址差3*4=12字节
- 二维数组第一个元素地址:&arr[0][0]);
- 单个元素打印内存地址需要加&
-
二维数组练习:学生成绩管理
- 描述:定义一个数组,储存五名学生的三门成绩求出每名学生的总成绩和平均成绩及各科的总成绩和平均成绩
int main() { int arr[5][3]; int sum1=0,ave1,sum2=0,ave2,k=1,sum3=0,ave3; //录入学生成绩 for (int i = 0; i < 5/*数组定义没有空间所以不能这样写sizeof(arr[0])*/; i++) { printf("请输入第%d名学生的三科成绩:", k); for (int j = 0; j < 3/*理由同上sizeof(arr[0])/sizeof(arr[0][0])*/; j++) { scanf("%d",&arr[i][j]; } k++; } //横向求和 for (int i = 0;i < 5;i++) { for (int j = 0; j < 3; j++) { sum1 = sum1 + arr[i][j]; } ave1 = sum1 / 3; printf("第%d名学生的总成绩是%d,三科平均成绩是%d\n", i + 1, sum1, ave1); sum1 = 0;//如果不服给他初值就得到所有成绩的和 } //纵向求和 for (int i = 0; i < 3; i++)//表示列 { for (int j = 0; j < 5; j++)//表示行 { sum3 = sum3 + arr[j][i]; } ave3 = sum3 / 5; printf("第%d科的总成绩是%d,平均成绩是%d\n", i + 1, sum3, ave3); sum3 = 0;//如果不服给他初值就得到所有成绩的和 } return 0; }
4.3.1多维数组
1.多维数组的定义
// 层 行 列
int arr[2] [3] [4]=
{
{
{1,2,3,4},
{2,3,4,5},
{3,4,5,6}
},//此处也要用,隔开
{
{4,5,6,7},
{5,6,7,8},
{6,7,8,9}
}
}
//三维数组所以得用3层循环
for (size_t i = 0; i < 2; i++)
{
for (size_t j = 0; j < 3; j++)
{
for (size_t k = 0; k < 4; k++)
{
printf("%d\t", arr[i][j][k]);
}
printf("\n");
}
}
printf("此三维数组的大小:%u\n", sizeof(arr));
4.3.0字符数组与字符串
- 定义
- 字符数组:
char arr[5] = { 'H','e','l','l','o' };
- 字符串:
char str[5] = "Hello";
- 注意区别
char* String="Hello"; char String[]={'H','e','l','l','o','\0'};
- 字符数组:
- 区别
- 字符串是字符数组的一个特例
- 字符串的结束标志位是\0
- 数字0等同于 \0 但是不等同于’ 0 '.
- 具体区别
char arr1[5] = { 'H','e','l','l','o' }; char arr2[6] = { 'H','e','l','l','o' };//有效字符5个,结尾自动补0 char arr3[] = {'H','e','l','l','o','\0'};
- 用%s获取字符串
- 总结
- c语言中没有字符串这种数据类型,通过char的数组来替代
- 字符串一定是一个char的数组(多一个‘\0’但不等于‘\0’),
但char的数组未必是字符串 - char数组遇到数字0(或者‘\0’)就是字符串,但如果char数组没有以数字0结尾,就不是一个字符串,只是一个普通的字符数组,所以字符串是一种特殊的char数组*/
4.3.1字符串拼接例
int main()
{
char ch1[] = "Hello";
char ch2[] = "world";
char ch3[20];
//for循环实现
for (size_t i = 0; i < 5; i++)
{
ch3[i] = ch1[i];
}
for (size_t j = 0; j < 5; j++)
{
ch3[5+j] = ch2[j];
}
ch3[10] = '\0';
printf("%s", ch3);
//while循环实现
int i=0,j=0;
while (ch1[i]!='\0')
{
ch3[i] = ch1[i];
i++;
}
while (ch2[j] != '\0')
{
ch3[i + j] = ch2[j];
j++;
}
ch3[i+j] = '\0';
printf("%s", ch3);
return 0;
}
4.3.2字符串获取与打印
1.字符串获取
char arr[11];//定义字符数组时还要留个空给“\0”
scanf("%10s",arr);//对输入进行限定,输入过程中不能带空格,
//scanf("%[^\n]",ch);//使用了正则表达式,表示接收非\n的所有内容,这样就能接收空格了
printf("%s",arr);//还可以输入汉字,一个汉字占两个字节,
2.字符串获取的几个函数
/*1.gets(数组名);通过键盘获取一个字符串它接收字符串可以带空格,scanf则要用到正则表达式
char ch[100];
gets(ch);
printf("%s", ch);
getchar();//注意:与scanf相同,无法知道字符串s的大小,必须遇到换行符或读到文件结尾为止,
因此容易导致字符数组越界(缓冲区溢出)的情况,所以给大一点的空间
/*2.fgets(数组名,最大读取字符串长度(int),stdin);*/
char ch2[10];
fgets(ch2, sizeof(ch2), stdin);
printf("%s", ch2);
//注意:fgets获取字符串少于元素个数会有\n,大于等于没有\n所以是一种比较安全的输入方式,不会溢出fgets可以接收空格
- 字符串输出
printf("%s",);
puts(数组名);
输出完成后自动换行 \nchar ch[]="Hello world!"; puts(ch);
fputs(数组名,stdout)
输出后不加换行,后面文件会详细说char ch[]="Hello world!"; fputs(ch,stdout);
- 字符串长度
- 函数:strlen(数组名); 计算指定字符串长度,不包含\0,返回unsigned int
char ch[100] = "hello world!"; char ch1[]= "helloworld!"; printf("数组大小:%u\n",sizeof(ch)); printf("字符串长度:%u\n",strlen(ch)); printf("数组大小:%u\n",sizeof(ch1)); printf("字符串长度:%u\n", strlen(ch1));
- 函数:strlen(数组名); 计算指定字符串长度,不包含\0,返回unsigned int
5
5.1随机数
-
步骤
- 引入头文件
#include<stdlib.h> //用来设置rand();产生随机数时的随机种子 #include<time.h> //获取当前系统时间
- 添加随机数种子
srand((unsigned int)time(NULL));
- 创建随机数
例:printf("%d\n", rand() % 51+50);//获取50-100的随机数
-
随机数使用的举例
int main() { //随机数例子:双色球 //6个红球 1-32 每个球的数不能重复 1个蓝球 1-16 srand((size_t)time(NULL));//创建随机数种子 int arr[6] = {0};//此处要赋初值0,不然也会自己随机赋值一个值,到后面不好判断 int value = 0; int flag = 0;//确定有没有重复的值 int j; int num = 0; printf("您想抽奖几次?"); scanf("%d", &num); for (int k = 0; k < num; k++) { flag = 0;//不加flag每次都是累加 for (int i = 0; i < 6; i++) { value = rand() % 32 + 1;//为什么这样写呢:i%2=0,1,,,所以i%32=0,1,2,3,4,5,6,7,8,9,.....,31; //为什么加一呢:为什么不写%32呢,因为是从0 开始的+1就没有了0 //去重 for (j = 0; j < flag; j++)//遍历数组判断新产生的数是否与已经存在的值相同 { if (value == arr[j])//如果相同就跳出 去重 再去拿一个随机数进来比较 { i--; break; } } if (j == flag)//判断是否遍历完了数组的所有元素 { arr[flag] = value; flag++; } } for (int i = 0; i < 6; i++) { printf("%d ", arr[i]); } printf(" + %d", rand() % 16 + 1); printf("\n"); } return 0; }
5.2函数
5.2.0函数的定义和使用
- 函数定义的格式
返回值类型 函数名(参数1类型 参数1,参数2类型 参数2·····)//返回值可以是void;函数名要避免与关键字相同 { 函数体 return 返回值类型;//要对应上 }
- 注意事项
- 默认的返回值类型是 整型
- 在不同函数中函数的变量名可以相同,因为他们的作用域不同;且存储的地址也不同
- 用void的好处是能够任意将返回值转化成想要的类型
- 返回值如果是数组则可以一次性返回多个值,但一定需要是相同类型
- 如果return的返回类型与定义时的不同则以定义时的为准:::函数的返回类型决定返回值类型
- 举例:
double add(double x, double y)//X,Y都为形式参数(定义时记得加上类型),无具体值
//在函数调用过程中的参数称为实际参数,有具体的值
//有()代表他是一个函数而不是一个普通的变量名
{
double sum = x + y;
return sum;//不能返回多个值,做成数组可以返回多个值,但一定是相同类型
//如果return的返回类型与定义时的不同则以定义时的为准:::函数的返回类型决定返回值类型
}
使用时
int a = 10.5, b=11.5;
printf("%lf",double(a,b));
5.2.1函数的训练题
#include<stdio.h>
int my_strcmp(char ch1[], char ch2[])
{
int i = 0;
while (ch1[i] == ch2[i])
{
//是否到字符串结尾
if (ch1[i] == '\0')
{
return 0;
}
i++;
}
return ch1[i] > ch2[i] ? 1:-1;
}
int main0201()
{
printf("比较两个字符串,相同返回0,不同返回1或-1\n");
char ch1[] = "Hello";
char ch2[] = "hello";
char ch3[] = "Hello";
if (my_strcmp(ch1, ch3) == 0)
{
printf("两个数组相同");
}
else printf("两个数组不同");
return 0;
}
5.3函数样式
- 有参函数
//冒泡排序函数版
void BubbleSort(int arr[], int len)//void类型不能直接定义数据;但可以作为函数的返回值类型,表示没有返回值
//数组被函数调用就退化成了指针,所以使用了两个参数,指针的那个地方详细说
{
for (size_t i = 0; i < len-1; i++)
{
for (size_t j = 0; j < len-i-1; j++)
{
if (arr[j] < arr[j + 1])
{
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}
}
}
}
- 无参函数
void ask()
{
printf("你瞅啥");
}
- Demo
int main()
{
ask();
int arr[] = { 9,5,6,3,2,1,4,7,8 };
BubbleSort(arr, sizeof(arr) / sizeof(arr[0]));
for (size_t i = 0; i < 9; i++)
{
printf("%d ", arr[i]);
}
}
5.4函数的声明
- 使用函数的过程
- 函数定义
- 函数调用
- 函数声明 :函数定义在主函数之后需要在主函数中声明;
- 举例
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
/*函数声明*/
extern int add01(int i, int j);
/*
函数的声明可以简化
1.extern可以省略
2.()内可以只写上参数的类型
最终简化版本:int add01(int,int);
*/
/*
声明和定义的区别
1.声明变量不需要建立存储空间 如extern int a;
2.定义变量需要建立存储空间
3.从广义的角度来说声明中包含着定义,即定义是声明的一个特例,所以并非所有的声明都是定义
如 int a 它既是声明同时又是定义
而 extern b 它只是声明不是定义
一般来讲,把建立存储空间的声明称之为:定义,而把不需要建立存储空间的定义称之为:声明
*/
int main4()
{
/*函数调用*/
printf("%d",add01(90, 60));
return 0;
}
/*函数定义*/
int add01(int i, int j)
{
return i + j;
}
5.5 main()函数和exit()函数
#include<stdio.h>
void fun();
int main(void)
{
fun();
printf("Hello");
printf("Hello");
return 0;
}
void fun()
{
printf("Hello");
printf("Hello");
printf("Hello");
exit(0); //终止程序执行,出现报错的时候
printf("Hello");
printf("Hello");
}
5.6多文件编程
- 文件种类
- .h (header)类型的,变量的定义,结构体定义,宏定义,函数声明,include包含等内容
"ioput.h"
#include <stdio.h>//读取用户输入的年份
int input_year(void);//读取用户输入的月份
int input_month(void);//显示日历
void output_days(int year, int month, int week, int is_leap_year);
"calc.h"
#include "ioput.h"//蔡勒公式计算星期,只适合于1582年10月15日之后的日期
int calc_week(int year, int month, int day);//计算闰年
int calc_leap_year(int year);//日历核心函数
void calc_core(void);
- .c (source)编写实际功能的实现
main.c必须
#include "calc.h"
int main(int argc, char *argv[])
{
calc_core();
return 0;
}
其他的对应好.h
ioput.c
#include "ioput.h"//引入头文件便可以省略定义与声明
//读取用户输入的年份
int input_year(void)
{
int year;
printf("Enter the year:");
scanf("%d", &year);
return year;
}
//读取用户输入的月份
int input_month(void)
{
int month;
printf("Enter the month:");
scanf("%d", &month);
return month;
}
//显示日历
void output_days(int year, int month, int week, int is_leap_year)
{
//月份与星期的名称及每个月的天数
char* month_name[12] = { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" };
char* week_name[7] = { "Su", "Mo", "Tu", "We", "Th", "Fr", "Sa" };
int days[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
//闰年二月29天
if (is_leap_year)
{
days[1] = 29;
}
printf("\n");
//显示年、月和星期
printf(" %s %d\n", month_name[month], year);
for (int j = 0; j < 7; j++)
{
printf("%2s ", week_name[j]);
}
printf("\n");
//显示每月1日前的空白
for (int i = 0; i < week % 7; i++)
{
printf(" ");
}
//循环显示日期
for (int i = 1; i <= days[month]; i++)
{
printf("%2d ", i);
//显示7个数后换行
if ((i + week) % 7 == 0)
{
printf("\n");
}
}
printf("\n\n");
}
calc.c
#include "calc.h"
//蔡勒公式计算星期,只适合于1582年10月15日之后的日期
int calc_week(int year, int month, int day)
{
if (month <= 2)
{
month += 12;
year--;
}
int century = year / 100;
year %= 100;
int days = (year + year / 4 + century / 4 - 2 * century + 26 * (month + 1) / 10 + day - 1) % 7;
while (days < 0)
{
days += 7;
}
return days;
}
//计算闰年
int calc_leap_year(int year)
{
if ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0))
{
return 1;
}
return 0;
}
//日历核心函数
void calc_core(void)
{
do
{
int year = input_year();
if (year <= 1582)
{
break;
}
int month = input_month();
if (month <= 0 || month >= 13)
{
break;
}
int is_leap_year = calc_leap_year(year);
int week = calc_week(year, month, 1);
month--;
output_days(year, month, week, is_leap_year);
}
while (1);
}
- 文件结构
- 头文件
- ioput.h
- calc.h
- 源文件
- main.c
- ioput.c
- calc.c
- 头文件
- 作用:方便代码的阅读和维护
- 注意:
-
导入库函数用<>导入自己的函数用"",<>这个从环境变量中找库,“”这个从当前目录中找库
-
防止头文件重复包含的两种方式
1.#pragma once 全局变量的定义 函数的声明
* 只能用于Windows * .h文件的第一行
#ifndef __FUN_H__//大写的头文件名行业标准 #define __FUN_H__ 全局变量的定义 函数的声明 #endif
- 适用于c++
-
6
6.1 指针的定义和使用
- 不要怕:指针只是一个变量,其值为另一个变量的地址(无符号16进制整型数),不同数据类型的指针之间唯一的不同是,指针所指向的变量或常量的数据类型不同
- 概念:定义指针变量存储变量地址,指针也是一种数据类型
- 格式:数据类型* 变量名
- 注意:指针类型存什么变量类型就用什么类型的指针如char类型用char*类型,double用double *类型,定义指针类型一定要和变量的类型对应上
- 运算符:
- &:取地址运算符,升维度(普通变量变为一级指针)
- *:取值运算符,降维度(一级指针变为普通变量)
- 大小
- 所有的指针类型存储的都是内存地址(无符号16进制整型数)
- 在32位操作系统下所有指针类型是4字节大小:X86
- 在64位操作系统下所有指针类型是8字节大小:X64
- 扩展:
- 内存可以抽象成很大的一维数组
- 编码就是对内存的每个字节分配一个32或64位的编号(由处理器决定)这个内存编号我们称之为内存地址,内存中每一个数据都会分配相应的地址
- char:占一个字节分配一个地址,int占四个字节分配四个地址float struct 函数 数组等
- code
#include<stdio.h>
int main()
{
printf("int a=10;通过变量a改变值\n");//通过变量a改变值
int a=10;//printf("%p\n", &a);//windows电脑在做数据存储采用小端对齐
int* p;//指针类型存什么变量类型就用什么类型的指针如char类型用char*类型,double用double*类型
p = &a;//p=a的地址 &是取地址符
printf("通过变量赋值a的值及内存地址");
printf("打印整型变量a的值:%d\n", a);
printf("利用“*”取值 运算符打印指针变量p(a的地址)处存储的值%d\n", *p);//*也叫做取值运算符,是降维度的
printf("利用取地址符&获取的a的地址并打印:%p\n", &a);//&叫做取地址运算符,升生维度的
printf("利用指针变量p(值为变量a的地址)打印a的地址:%p\n", p);//p是指针变量,代表了变量a的地址
printf("\n");
printf("\n");
printf("\n");
//通过指针间接改变变量的值
printf("*p = 1000000;通过指针间接改变变量的值\n");
*p = 1000000; //*p(取值运算符+指针变量=存储在“指针变量存储的地址”处的值)在此处即*p=a;
printf("打印整型变量a的值:%d\n", a);
printf("利用“*”取值 运算符打印指针变量p(a的地址)处存储的值%d\n", *p);//*也叫做取值运算符,是降维度的
printf("利用取地址符&获取的a的地址并打印:%p\n", &a);//&叫做取地址运算符,升生维度的
printf("利用指针变量p(值为变量a的地址)打印a的地址:%p\n", p);//p是指针变量,代表了变量a的地址
printf("\n");
printf("两种方式都不改变内存地址");
printf("\n");
printf("\n");
printf("\n");
printf("各种类型指针变量的大小:\n");
printf("int*:%u\n", sizeof(int*));
printf("char*:%u\n", sizeof(char*));//所有的指针类型存储的都是内存地址(无符号16进制整型数)
printf("double*:%u\n", sizeof(double*));//在32位操作系统下所有指针类型是4字节大小:X86
printf("long*:%u\n", sizeof(long*)); //在64位操作系统下所有指针类型是8字节大小:X64
printf("long long*:%u\n", sizeof(long long*));//
printf("\n");
printf("\n");
printf("\n");
int b = 10;
int p2=&b;//此时整型变量p2的值是整型变量b的地址
// *p2=100;这样写是错误的,因为p2是整型变量而不是指针变量
*(int*)p2 = 100; /*这样写是正确的:先将整型变量强制类型转换成指针
变量再对其用取值运算符然后对b所在的内存地址赋新值*/
printf("将整型变量强制类型转换后再取值%d:", b);
printf("\n");
printf("\n");
printf("\n");
/*使用指针变量时,与一般的变量类型要对应相同,因为如int*代表的内存地址是对应的int变量的4个字节
而char*代表的内存地址是对应的char变量的1个字节*/
char ch = 97;
int* p3 = &ch;
printf("%d\n", ch);
printf("%d", *p3);
return 0;
}
6.2野指针和空指针
- 野指针:
- 指针变量指向一个未知的空间(空间:内存地址已知,内存内容未知)。
- 不建议将一个值(内存地址)直接赋给指针变量
如:int* p=100;
- 操作一个野指针对应的内存空间可能报错(因为不知到那个内存地址是否被占用)
- 程序中允许存在野指针
- 操作系统将0-255内存地址作为系统占用不允许访问操作,所2.的
int*p=100;
的赋值就会报错
- 空指针
- 操作空指针对应的空间一定会报错
如: int* p1 = NULL;//NULL是内存地址编号为0的空间=((void*)0) printf("%d", *p);
- 空指针可以用作条件判断
如: if (p == NULL) { }
6.3万能指针
- 万能指针能够接收任意类型的变量地址
- 在通过万能指针修改变量的值时,需要找到变量对应的指针类型
- 万能指针void*:可以指向任意变量的内存空间
- 例
int main523()
{
int a = 10;
//万能指针能够接收任意类型的变量地址
printf("改变前a的值是:%d\n", a);
void* p = &a;
printf("改变前a的地址是:%p\n", p);
//在通过万能指针修改变量的值时,需要找到变量对应的指针类型
//*p = 100;//这是非法的间接寻址,因为不清楚void在内存中占几个字节
*(int*)p = 100;//这样是可以的
printf("改变后a的值是:%d\n", *(int*)p);//*(int*)p==a
printf("改变前后的地址是:%p\n", p);
printf("万能指针在内存中占的内存大小:%d\n", sizeof(void*));
printf("万能指针void*:可以指向任意变量的内存空间\n");
return 0;
}
- 使用万能指针要要知道你需要的代替的变量的类型,然后将万能指针进行该类型的强制转换
指针都是四字节的指针类型,所以类型转换并没有影响
6.4 const修饰指针变量
- 首先用const定义变量是不安全的!!!,利用指针寻址是可以修改!!!,用#define定义就不可以修改,因为不可寻址
例: #include<stdio.h> #define NUM 97 //这样不可以修改,因为不可寻址。 int main() { printf("%p\n", NUM); printf("part1\n"); const int a = 100;//这里叫做栈区,可以利用寻址修改 int* p = &a; *p = 1000;//指针间接修改常量的值 //err int* p2 = &NUM;不可修改 printf("%p\n", p); printf("%d\n", a); }
- const修饰指针的三种类型
0. 口诀:const离谁近就不能改谁- 用const修饰指针类型
- 可以修改指针变量的值
- 不可以修改指针指向内存空间的值,叫做只读指针
- 例:
int b = 10, c = 20; const int* p2 = &b;//p2储存的是b的地址 printf("%p\n", p2); printf("%d\n", *p2); p2 = &c; //对应1,此功能跟普通的指针变量相同 printf("%p\n", p2); printf("%d\n", *p2);
- 用const修饰指针变量
- 不可以修改指针变量的值
- 可以修改指针指向内存空间的值
- 例
int d = 50; int e = 60; int* const p3 = &d;//p3储存的是d的地址 printf("%p\n", p3); printf("%d\n", *p3); //p3 = &e;err //对应1 *p3 = e; //对应2 printf("%p\n", p3); printf("%d\n", *p3)
- 同时用const修饰指针变量和指针类型
- 例:
int f = 80; int g = 90; const int* const p4 = &f; //p4 = &g; 都不能这样修改,可以用二级指针修改,降维度。 //p4 = 500; int** pp = &p4; //*pp是一级指针的值 printf("%p\n", p4); //**pp是储存的变量的值 printf("%d\n", *p4); *pp = 1000; printf("%p\n", pp); printf("%d\n", *pp);
- 比他高一级的指针可以修改:原称之为降维打击
- 用const修饰指针类型
7
7.1 指针和数组
- 数组名是一个常量,其值等于数组的首地址,不允许赋值,所以数组名可以看作指针,指针也可以看作是数组
例:
int arr[] = { 1,2,3,4,5,6,7,8,9,'a','b' };
//err arr =100;arr是一个常量(其值等于数组的首地址),不允许赋值。
int* p;
p = arr;//p叫做指向数组的指针
printf("p表示的arr的地址:%p\n", p);//都表示地址所以不用取地址符了
printf("arr的地址:%p\n", arr);
//结果是两个地址相同
- 数组可以看成一串连续的内存地址(指针),所以可以用用首地址依次向下偏移来代替数组
例:
int arr[] = { 1,2,3,4,5,6,7,8,9,'a','b' };
int* p;
p = arr;
for (size_t i = 0; i < 11; i++)
{
//用指针思想打印数组的几种方式
//printf("%d\n", arr[i]);|
//printf("%d\n", p[i]); |->数组和指针都可以这样,都代表着数组的首地址 //用首地址依次向下偏移来代替数组
//printf("%d\n", *(arr + i));//指针类型变量+1等同于内存地址+sizeof(数组类型)
//printf("%p\n", p++);//与数组同理,指针+1都等于+类型所占的内存为单位(sizeof(int))
printf("%d\n", *p++);//注意!!改变p后,p就不等同于原来的数组了,因为地址改变了,而arr依旧不变。这也是数组和指针的区别
}
printf("%d\n", *arr);//注意!!改变p后,p就不等同于原来的数组了,因为地址改变了,而arr依旧不变。这也是数组和指针的区别
printf("%d\n", *(p-1));//故意-1个,这样才能看出区别
重点
- 指针类型变量+1等同于内存地址+sizeof(数组类型),数组也一样
- 改变p后,p就不等同于原来的数组了,因为地址改变了,而arr依旧不变。这也是数组和指针的区别
- 指针p是变量,数组名arr是常量
- p是一个指针,4个字节大小,arr是数组,n*4字节大小
- 指针相减
- 两个指针相减得到的是两个指针的偏移量(步长)
- 注意相减的两个指针类型要对应上,所有指针类型相减,结果都是int类型
- 指针版冒泡排序法
void BubbleSort(int* arr,int length)
{
// int length = sizeof(arr) / sizeof(arr[0]);所以不能用这种方法求得数组长度
for (size_t i = 0; i < length-1; i++)
{
for (size_t j = 0; j < length-1-i; j++)
{
/*if (arr[j] > arr[j + 1])//常规版本
{
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
}*/
if (*(arr + j) > * (arr + j + 1))//指针写法(不推荐)
{
int temp = *(arr + j);
*(arr + j) = *(arr + j + 1);
*(arr + j + 1) = temp;
}
}
}
for (size_t k = 0; k < length; k++)
{
printf("%d ", arr[k]);
}
}
- 为什么是两个参数的详细解释:
数组作为函数参数会退化为指针,丢失了数组的精度(数组元素的个数)int arr[]变成了int* arr;,因为数组名不就跟指针一样么,所以要规定第二个变量,来给数组指路。
7.2指针运算
- 运算规则
- 指针的加减运算和指针的类型有关,如果是int+1:内存地址变4个;如果是char:内存地址变1个;即变化sizeof(int/char/double)等大小
- 例子:
- 例1
printf("指针的偏移\n"); int arr[] = { 1,2,3,4,5,6,7,8,9,10 }; int* p = arr; //arr[-1];对于数组来说是数组下标越界 p = &arr[3]; printf("%p\n", p); printf("%p\n", arr); p--; p--; p--; printf("%p\n", p);//指针的加减运算和指针的类型有关,如果是int:内存地址变4个;如果是char:内存地址变1个; p++; p++; p++; int step = p - arr; printf("%d\n", step);//内存地址相差是:12/sizeof(int)=偏移量; printf("%d\n", p[-2]);//*(p-2)指针操作数组时,下标允许是负数。
- 例2
printf("指针和运算符的操作\n"); int arr[] = { 1,2,3,4,5,6,7,8,9,10 }; int* p = arr; p = &arr[3]; if (p > arr) { printf("%d > %d\n", *p, arr[1]); printf("%p>%p\n", p, arr); //printf("真\n"); }
7.3指针数组
-
前言:数组可以看成一级指针,指针数组也可以看成是一个特殊的二维数组模型,定义数组:数据类型 数组名[元素个数] ={值,·,·,·,····};
-
指针数组存储的就是地址,需要注意的是指针的数组存储的地址的类型要对应上,数组类型占四个字节,指针数组的大小=4*元素个数。
int a = 10; printf("整型变量a的地址 %p\n", &a); int b = 20; printf("整型变量b的地址 %p\n", &b); int c = 30; printf("整型变量c的地址 %p\n", &c); int* arr[3] = {&a,&b,&c}; //char* arr[3] = { &a,&b,&c }; printf("%d\n", *arr[0]); printf("指针数组元素存储的地址:%p %p %p\n", arr[0],arr[1],arr[2]); printf("指针数组元素的地址:%p %p %p\n", &arr[0],&arr[1],&arr[2]); printf("指针数组大小:%d\n", sizeof(arr)); printf("指针元素大小:%d\n", sizeof(arr[0]));//无论是什么类型的指针数组大小 = 4*元素个数 return 0;
-
存储数组指针的指针数组
int a[] = { 1,2,3 }; int b[] = { 3,4,5 }; int c[] = { 5,6,7 }; //指针数组是一个特殊是二维数组模型 int* arr[] = { a,b,c }; printf("%p\n",arr);//指针数组的首地址 printf("%p\n",&arr[0]);//同上 printf("%X\n", *arr); printf("%p\n",a);//a数组的地址,不是指针数组里的a元素的地址 //arr是指针地址的首地址,存储的是arr[0]即a[]数组的首地址。 printf("%d\n",arr[0][1]);//找到数组a[]中的第二个元素 for (int i = 0; i < 3; i++) { printf("这样打印的是整型数组%c首元素的值和地址:%d %p\n",i+97 ,*arr[i],arr[i]); printf("%c数组的元素{ ", i + 97); //因为是一个特殊的二维数组模型,所以用两层循环可以遍历// for (size_t j = 0; j < 3; j++) { //printf("%d ", arr[i][j]);//数组的遍历方式 //printf("%d ", *(arr[i] + j));//利用指针偏移量的方式 printf("%d ",*(*(arr+i)+j));/*利用指针首地址+偏移量(*arr+i)是arr[i]存储的值(a的地址) *(*(arr+i)+j) = *(a[j])取到的是a数组的值 */ printf(""); } printf("}\n"); } 输出: 0117FC74 0117FC74 117FCB0 0117FCB0 2 这样打印的是整型数组a首元素的值和地址:1 0117FCB0 a数组的元素{ 1 2 3 } 这样打印的是整型数组b首元素的值和地址:3 0117FC9C b数组的元素{ 3 4 5 } 这样打印的是整型数组c首元素的值和地址:5 0117FC88 c数组的元素{ 5 6 7 }
7.4多级指针
- 前言:
- C语言允许有多级指针的存在,在实际程序中一级指针最常用,其次是二级指针
- 二级指针就是指向一个一级指针变量的地址。
- 三级指针基本用不着
- 指针数组可以看成一个特殊的的二维数组。
- 示例1
int main() int a[] = { 1,2,3 }; int b[] = { 4,5,6 }; int c[] = { 7,8,9 }; int* arr[] = { a,b,c }; int** p = arr;//指向的是数组所以也不需要加上& printf("%d\n", **p);//==arr[0][0] //二级指针加偏移量相当于跳过了一个一维数组的大小。 printf("%p\n", *(p + 1));//二级指针+1 = 行+1 //printf("%d\n", ); printf("%d\n", **(p + 1)); //一级指针加偏移量相当于跳过了一个元素 printf("%d\n", *(p + 1));//此时是第2个数组的第1个元素的地址,二级指针加一个*相当于一级指针 printf("%d\n", *(*p + 1));//==arr[0][1] printf("%d\n", *(*(p + 1)+ 1));//==arr[1][1] for (size_t i = 0; i < 3; i++)//表示行 { for (size_t j = 0; j < 3; j++)//表示列 { printf("%d", p[i][j]); printf("%d", *(p[i]+j)); printf("%d", *(*(p+i)+j)); } puts(""); }
- 示例2
int a = 9;
int* p = &a;
int** pp = &p;//二级指针指向一级指针的地址
printf("变量a的地址:%p\n", &a);
printf("p存储的是变量a的地址%p\n", p);
printf("a的值%d\n", a);
printf("对p指针做取值%d\n", *p);
printf("一级指针p的地址%p\n", &p);
printf("二级指针pp的地址%p\n", &pp);
printf("二级指针存储的是一级指针地址:%p\n", pp);
//二级指针加*==一级指针
printf("对二级指针pp取值运算一次得到的是一级指针p存储的a的地址%p\n", *pp);//存储的是指针类型,所以用%p打印
//加**==值
printf("对二级指针pp取值两次得到的是一级指针存储地址的值:%d\n", **pp);
7.5值传递和地址传递
- 区别:
- 值传递,在函数结束后函数内存销毁,原来内存及存储的值不变;值传递的特点是单向传递,即主调函数调用时给形参分配存储单元,把实参的值传递给形参,在调用结束后,形参的存储单元被释放,而形参值的任何变化都不会影响到实参的值,实参的存储单元仍保留并维持数值不变。
- 地址传递,形参可以改变实参的值;地址传递的特点是形参并不存在存储空间,编译系统不为形参数组分配内存。数组名或指针就是一组连续空间的首地址。因此在数组名或指针作函数参数时所进行的传送只是地址传送,形参在取得该首地址之后,与实参共同拥有一段内存空间,形参的变化也就是实参的变化。
- 我感觉值传递和地址传递只是用于函数,对于在main函数里的交换,是直接通过赋值来转换的,原来的变量的值不见了
- 值传递
void swap(int a,int b)
{
int temp = a;
a = b;
b = temp;
printf("%d %d", a, b);
}
- 地址传递
void pswap(int* a, int* b)
{
int temp = *a;
*a = *b;
*b = temp;
printf("%d %d", *a, *b);
}
- 主函数
int a = 10, b = 20;
//值传递 形参不影响实参的值
printf("值传递,在函数结束后函数内存销毁,原来内存及存储的值不变\n");
swap(a, b);
printf("\n%d %d\n", a,b);
printf("地址传递,形参可以改变实参的值\n");
pswap(&a, &b);
printf("\n%d %d\n", a,b);
输出:
值传递,在函数结束后函数内存销毁,原来内存及存储的值不变
20 10 //值的交换,在函数里面可以实现,等函数运行完就会销毁,所以这里储存的值
//更像是复制版
10 20 //原来内存中储存的值还是不变
地址传递,形参可以改变实参的值
20 10 //地址传递,交换的是原来内存中的值,直接对内存操作,所以交换后不会不改变
20 10
7.6数组名作为函数参数
- 数组名作为函数参数,会退化成指针,第二个参数可以用来给指针指路
- 例1数组版
void my_strcatarr(char* ch1, char* ch2)
{
/*******相当于strlen(ch1);*******/
int i = 0;
while (ch1[i] != '\0')
{
i++;
}
printf("%d\n", i);
printf("%d\n", strlen(ch1));
/********************************/
/****两个字符串链接****/
int j = 0;
while (ch2[j] != '\0')
{
//前面的i用来确定合成的数组元素的前面的大小
ch1[i + j] = ch2[j];
j++;
}
ch1[i + j] = '\0';
}
- 例2指针版
void my_strcpypoint(char*ch1,char*ch2)
{
int i = 0;
while (*(ch1 + i) != '\0')
{
i++;
}
int j = 0;
while (*(ch2 + j) != '\0')
{
*(ch1 + i + j) = *(ch2 + j);
j++;
}
}
- 主函数
char ch1[100] = "Hello";
char ch2[] = "world";
my_strcatarr(ch1, ch2);
//my_strcpypoint(ch1, ch2);
printf("%s\n", ch1);
return 0;
7.7练习:字符串去空格
- 数组版
void remove_spacearr(char* ch)//数组方法:占用额外的存储空间,不可取。
{
char str[100] = { 0 };
char* temp = str;
int i = 0;
int j = 0;
while (ch[i] != '\0')
{
if (ch[i] != ' ')//将空格排除了
{
str[j] = ch[i];
j++;
}
i++;
}
//printf("%s\n", str);
while (*ch++ = *temp++);
//printf("%s\n", ch);
}
- 指针版
void remove_spacepoint(char* ch)
{
char* ftemp = ch;//用来遍历字符串
char* rtemp = ch;//记录非空格字符串
while (*ftemp!='\0')
{
if (*ftemp != ' ')
{
*rtemp = *ftemp;
rtemp++;//指针的移动
}
ftemp++;//源指针移动
}
*rtemp = 0;
}
- 主函数
void remove_space(char* ch);
char ch[100] = " h e llo w o r l d";
//remove_spacearr(ch);
remove_spacepoint(ch);
printf("%s\n", ch);
return 0;
7.8指针作为函数返回值
- 练习,查找字符在字符串中第一次出现的位置
- 数组方式
char* my_chf(char* str, char ch)
int i = 0;
while (str[i])
{
if (str[i] == ch)
{
return &str[i];
}
i++;
}
return NULL;//NULL是空指针
}
- 指针方式
char* my_chf(char* str, char ch)
{
while (*str)
{
if (*str == ch)
{
return str;
}
str++;
}
return NULL;
}
- 主函数
int main771()//字符串中查找字符
{
printf("查找字符第一次在字符串中出现的位置\n");
char ch[] = "Hello world";
char* p = my_chf(ch, 'l');
if (p == NULL)
{
printf("没找到");
}
printf("%p\n", p);
return 0;
}
7.9字符串查找字符串
char* my_strstr(char* src, char* dest)//src代表元字符串,dest代表目标字符串
{
char* fsrc = src;//遍历源字符串指针findsrc
char* rsrc = src;//记录与目标字符串相同的首地址remembersrc
char* tdest = dest;
while (*fsrc)//遍历数组地址
{
rsrc = fsrc;//每次循环记录每次循环开始字符的地址,用来记得每次遍历的字符的地址
while (*fsrc == *tdest && *fsrc != '\0')//&&防止元字符串到头了还继续比对
{//碰到相同的字符,元字符串和目标字符串就向下进一个,再进行比对。
fsrc++;
tdest++;
}
//如果目标字符串在元字符串中出现,那么目标字符串一定遍历完了,到达了‘\0’
if (*tdest == '\0')
{
return rsrc;
}
fsrc = rsrc;//如果比对不匹配那么回到元字符串起始位置
tdest = dest;//目标字符串也回到起始位置
fsrc++;//找到下一个字符继续循环比对
}
return NULL;
}
int main78()
{
char src[] = "Hello world!";
char dest[] = "orl";
char* P = my_strstr(src, dest);
printf("%s\n", P);
return 0;
}
8
8.1指针和字符串
- 数组可以位更改,指针不能位更改;原因:字符数组存储在栈区,指针数组存储在常量区(只读)
char ch[] = "Hello world!"; char* p = "Hello world!"; //源数据 printf("%s", ch); printf("%s\n", p); //更改后 ch[2] = 'y';//字符数组存储在栈区,指针数组存储在常量区(只读) printf("%s", ch); //p[2] = 'y';//err //*(p + 2) = 'y';//err //两种方式都不可以更改
- 字符串数组
char ch1[] = "Hello"; char ch2[] = "world"; char ch3[] = "hahaha"; char* ch4[] = { ch1,ch2,ch3 }; //or ch4[0] = "sunwenjing"; ch4[1] = "lvchunyan"; ch4[2] = "dengzhangjia"; char* ch5[] = { "Hello","world","大宝贝" }; ch5[2] = "dabaobei"; //两种字符串数组的区别是ch4[]可以修改 ch5[]不可以修改 printf("%c\n", ch4[1][0]); printf("%c\n", ch5[1][0]); printf("%p\n", ch4); printf("%p\n", ch5); for (size_t i = 0; i < 3; i++) { printf("%s\n", ch4[i]); printf("%s\n", ch5[i]); } 输出: l //输出l的原因是ch4[1] = "lvchunyan";改变了内存中的值 w 010FFE14 010FFE00 //这是更改之后的ch4数组中的内容,与未更改成功的ch5中的内容 sunwenjing Hello lvchunyan world dengzhangjia dabaobei
- 应用实例:电话本名字排序
for (size_t i = 0; i < 3-1; i++) { for (size_t j = 0; j < 3-1-i; j++) { if (ch4[j][0] > ch4[j + 1][0]) { char* temp = ch4[j]; ch4[j] = ch4[j + 1]; ch4[j + 1] = temp; } if (ch5[j][0] > ch5[j + 1][0]) { char* temp = ch5[j]; ch5[j] = ch5[j + 1]; ch5[j + 1] = temp; } } } for (int i = 0; i < 3; i++) { printf("%s\n", ch4[i]); printf("%s\n", ch5[i]); } return 0;
8.2字符指针作为函数参数
//数组版字符串长度
int my_strlenarr(char* str)
{
int i = 0;
while (*(str + i) != '\0')//初学者可以暂时认为指针等于数组
//在这里我把数组写成了指针的形式,
//while (str[i] != 0)//另外字符串末尾可以是'\0'或者直接是0,不能是'0'
//while((str+i) != 0)//这样写是错误的,这里直接对地址进行了操作,而不是数组元素
{
i++;
}
return i;
}
```
```c
//指针版字符串长度
int my_strlenpoint(const char* str)//对指针元素进行约束
{
char* temp = str;//此时temp记录的是字符串str的首地址
while (*temp != 0) temp++;
return temp - str;
}
8.3const修饰指针变量
- 通过一级指针间接修改常量的值
const int a = 10;
//a = 100;err
int* p = &a;
*p = 100;
printf("%d\n", a);
- const 对指针定义过程中各个位置的限制(但都可以通过更高一级别的指针降维打击)
- 指向常量的指针:可以修改指针变量的值,不可以修改指针变量指向内存空间的值。
char ch1[] = "Hello";//vs快捷键,ctrl+d快速复制当前行到下一行 char ch2[] = "world"; const char* p2 = ch1;//const离谁近谁就不能改(此处const修饰带*的带*的*p就不能改) //*p2 = 'M';err *p不能改 //p2[2] = 'm';err等同于 *(p2+2)='m';不能改 p2 = ch2;//p值能改
- 常量指针:不可以修改指针变量的值,可以修改指针变量指向内存空间的值。
char ch3[] = "Hello"; char ch4[] = "Clanguage"; char* const p3 = ch3; //此处const修饰不带* 的不带* 的p就不能改 *p3 = 'W';//因为仅指向了数组首元素的地址,所以只能改一个。可以改 //p3 = ch4;err printf("%s", ch3); printf("%s", p3);//指针指向数组不用取值运算,指针指向单个字符用取值运算符取出值
- 只读指针:都不可以修改
char ch6[] = "Python"; const char* const p4 = ch6;
- 高一级指针降维打击
char** p7 = &p4;//此处注意&&&&&&&&&&&&&& *(*p7 + 1) = 'z';//*p7是一级指针指向数组首元素地址,*p7+1移动到第二个元素的地址, //然后*(*p+1)对数组第二个元素取值运算 printf("%s", p4); printf("%s", *p7);
8.4主函数的形参
-
主函数的形式 int main(int argc, char argv[])*
- 解释
- int argc表示传递参数的个数 》》》如:gcc -o hello hello.c(4个参数)
- char* argv[]={“gcc”,"-o",“hello”,“hello.c”};
int main84(int argc, char* argv[])//字符串数组(指针数组) { //如果参数少于命令数,如只输入一个gcc if (argc<3) { printf("缺少参数"); return -1; } for (size_t i = 0; i < argc; i++) { printf("%s\n", argv[i]); } return 0; } //操作方法 /* 1.cmd >gcc -o 文件路径(或者直接拖进,.c改为.exe) 文件路径(直接拖进去)回车 > 就会编译出一个exe文件在路径的文件夹中 2.exe文件拖进cmd中 输入参数 首先输入的是参数的个数(int),然后输入具体的值,个数与最先输入的相同 */
8.5通过实现<string.h>中的部分函数内容练习指针的用法
- 字符首次出现的位置
char* my_strstr(char* src, char* dest) { char* fsrc = src;//遍历字符串 char* rsrc = src;//记录比对时的位置 char* rdest = dest; while (*fsrc) { rsrc = fsrc; while (*fsrc == *rdest && *fsrc != 0) { fsrc++; rdest++; } if (*rdest == 0) { return fsrc; } fsrc = rsrc; rdest = dest; fsrc++; } return NULL; }
- 字符串出现的次数
0. 三种形式(两种结构)- 函数形式(while结构)
int my_strstrcount(char* src, char* dest) { int n = 0; char* fsrc = src;//遍历字符串 char* rsrc = src;//记录比对时的位置 char* rdest = dest; while (*fsrc) { rsrc = fsrc; while (*fsrc == *rdest && *fsrc != 0) { fsrc++; rdest++; if (*rdest == 0) { rdest = dest;//从头再来 n++; } } fsrc = rsrc; rdest = dest; fsrc++; } return n; }
- 利用strstr的while 结构
int count = 0; char* p = my_strstr(ch,dest); while (p != NULL) { count++; p += strlen(dest);//找到的话返回目标字符串首地址,所以要跳过目标字符串的长度 p = my_strstr(p, dest); } printf("%s在数组ch中出现的次数:%d\n", dest, count);
- 利用strstr()的do-while结构
count = 0; p = my_strstr(ch, dest); do { if (p) { count++; p += strlen(dest);//找到的话返回目标字符串首地址,所以要跳过目标字符串的长度 p = my_strstr(p, dest); } } while (p); printf("%s在数组ch中出现的次数:%d\n", dest, count); return 0;
- 字符串去空格以及统计字符个数
- 数组版本
int get_countarr(char*ch) { int i = 0; int count = 0; while (ch[i]) { if (ch[i] != ' ') { count++; } i++; } return count; }
- 指针版本
int get_countpoint(char* str)//指针版本 { int count = 0; while (*str) { if (*str != ' ') { count++; } str++; } return count; }
- 数组版本
- 统计字符个数
int main6_2() { char ch2[] = "dshfkhdgkddkgafhdjkghfdbngmzbdxvbea"; int arr[26] = {0}; for (size_t i = 0; i < strlen(ch2); i++) { arr[ch2[i] - 'a']++;//a-b对应着数组下标0-25,对应数组元素存放对应字母的个数 } for (size_t j = 0; j < 26; j++) { if(arr[j]) printf("字母 %c 出现次数:%d \n", j + 'a', arr[j]);//只有这一句会连未出现的字母一起打印 /*优化:加上if语句*/ } }
- 字符串逆置
- 数组版
void inversearr(char* str) { int i = 0; int j = strlen(str) - 1;//找到数组下标 while (i < j) { char temp = str[i]; str[i] = str[j]; str[j] = temp; i++; j--; } }
- 指针版
void inversepoint(char* str) { char* ftemp = str; char* btemp = str + strlen(str)-1;//指向最后一个字符地址, //-1是因为:假如首地址是2,加上字符串长度10,地址是12,多出了一个首字母的空间 while (ftemp < btemp) { char temp = *ftemp; *ftemp = *btemp; *btemp = temp; ftemp++; btemp--; } }
- 判断是否是回文字符串
int symm(char* str) { char* ftemp = str; char* ltemp = str + strlen(str) - 1; while (ftemp < ltemp) { if (*ftemp != *ltemp) { return 1; } ftemp++; ltemp--; } return 0; }
- 字符串拷贝
- 字符串完全拷贝
void my_strcpy(char* dest, const char* src) { while (*dest++ = *src++); }
- 字符串有限拷贝
void my_strncpy(char* dest, const char* src, size_t n) { while ((*dest++ = *src++) && n--);//这里应该是--n /*数组形式 int i = 0; while (i < n) { *(dest + i) = *(src + i); i++; } */ /*指针形式 while (n--&&*src) { *dest = *src; dest++; *src++; } */ }
**注意:**拷贝的子数组要初始化{0};因为不一定包含’\0’,则会出现乱码。且空间要足够大
7. 字符串拼接
1. 全局字符串拼接
void my_strcat(char* dest, const char* src)
{
//找到dest字符串中\0的位置
while (*dest) dest++;
while (*dest++ = *src++);
}
```
2. 局部字符串拼接
void my_strncat(char* dest, const char* src, size_t n)
{
while (*dest) dest++;
while ((*dest++ = *src++)&&--n);//加括号
}
- 字符串比较
- 全局比较
int my_strcmp(char* s1, char* s2)
{
while (*s1 == *s2)
{
if (*s1 == '\0') return 0;
s1++;
s2++;
}
return *s1 > * s2 ? 1 : -1;
}
2. 局部比较
int my_strncmp(const char* s1, const char* s2,size_t n)
{
for (size_t i = 0; i < n && s1[i] && s2[i]; i++)//考虑了字符数组不够长的情况
{
if (s1[i] != s2[i])
{
return s1[i] > s2[i] ? 1 : -1;
}
}
return 0;
}
8.6字符串处理函数
- gets()
-
头文件:#include <stdio.h>
-
char *gets(char *s);
-
功能:从标准输入读入字符,并保存到s指定的内存空间,直到出现换行符或读到文件结尾为止。
参数:
s:字符串首地址
返回值:
成功:读入的字符串
失败:NULL -
gets(str)与scanf(“%s”,str)的区别:
gets(str)允许输入的字符串含有空格
scanf(“%s”,str)不允许含有空格- 注意:由于scanf()和gets()无法知道字符串s大小,必须遇到换行符或读到文件结尾为止才接收输入,因此容易导致字符数组越界(缓冲区溢出)的情况。
char str[100]; printf("请输入str: "); gets(str); printf("str = %s\n", str);
- fgets()
#include <stdio.h>
char *fgets(char *s, int size, FILE *stream);
功能:从stream指定的文件内读入字符,保存到s所指定的内存空间,直到出现换行字符、读到文件结尾或是已读了size - 1个字符为止,最后会自动加上字符 ‘\0’ 作为字符串结束。
参数:
s:字符串
size:指定最大读取字符串的长度(size - 1)
stream:文件指针,如果读键盘输入的字符串,固定写为stdin
返回值:
成功:成功读取的字符串
读到文件尾或出错: NULL
- fgets()在读取一个用户通过键盘输入的字符串的时候,同时把用户输入的回车也做为字符串的一部分。通过scanf和gets输入一个字符串的时候,不包含结尾的“\n”,但通过fgets结尾多了“\n”。fgets()函数是安全的,不存在缓冲区溢出的问题。
char str[100]; printf("请输入str: "); fgets(str, sizeof(str), stdin); printf("str = \"%s\"\n", str);
- puts()
#include <stdio.h>
int puts(const char *s);
功能:标准设备输出s字符串,在输出完成后自动输出一个’\n’。
参数:
s:字符串首地址
返回值:
成功:非负数
失败:-1
#include <stdio.h>
int main()
{
printf("hello world");
puts("hello world");
return 0;
}
- fputs()
#include <stdio.h>
int fputs(const char * str, FILE * stream);
功能:将str所指定的字符串写入到stream指定的文件中, 字符串结束符 ‘\0’ 不写入文件。
参数:
str:字符串
stream:文件指针,如果把字符串输出到屏幕,固定写为stdout
返回值:
成功:0
失败:-1
- fputs()是puts()的文件操作版本,但fputs()不会自动输出一个’\n’。
printf("hello world");
puts("hello world");
fputs("hello world", stdout);
- strlen()
#include <string.h>
size_t strlen(const char *s);
功能:计算指定指定字符串s的长度,不包含字符串结束符‘\0’
参数:
s:字符串首地址
返回值:字符串s的长度,size_t为unsigned int类型
char str[] = "abcdefg";
int n = strlen(str);
printf("n = %d\n", n);
- strcpy()
#include <string.h>
char *strcpy(char *dest, const char *src);
功能:把src所指向的字符串复制到dest所指向的空间中,’\0’也会拷贝过去
参数:
dest:目的字符串首地址
src:源字符首地址
返回值:
成功:返回dest字符串的首地址
失败:NULL
注意:如果参数dest所指的内存空间不够大,可能会造成缓冲溢出的错误情况。
char dest[20] = "123456789";
char src[] = "hello world";
strcpy(dest, src);
printf("%s\n", dest);
- strncpy()
#include <string.h>
char *strncpy(char *dest, const char *src, size_t n);
功能:把src指向字符串的前n个字符复制到dest所指向的空间中,是否拷贝结束符看指定的长度是否包含’\0’。
参数:
dest:目的字符串首地址
src:源字符首地址
n:指定需要拷贝字符串个数
返回值:
成功:返回dest字符串的首地址
失败:NULL
char dest[20] ;
char src[] = "hello world";
strncpy(dest, src, 5);
printf("%s\n", dest);
dest[5] = '\0';
printf("%s\n", dest);
- strcat()
#include <string.h>
char *strcat(char *dest, const char *src);
功能:将src字符串连接到dest的尾部,‘\0’也会追加过去
参数:
dest:目的字符串首地址
src:源字符首地址
返回值:
成功:返回dest字符串的首地址
失败:NULL
char str[20] = "123";
char *src = "hello world";
printf("%s\n", strcat(str, src));
- strncat()
#include <string.h>
char *strncat(char *dest, const char *src, size_t n);
功能:将src字符串前n个字符连接到dest的尾部,‘\0’也会追加过去
参数:
dest:目的字符串首地址
src:源字符首地址
n:指定需要追加字符串个数
返回值:
成功:返回dest字符串的首地址
失败:NULL
char str[20] = "123";
char *src = "hello world";
printf("%s\n", strncat(str, src, 5));
- strcmp()
#include <string.h>
int strcmp(const char *s1, const char *s2);
功能:比较 s1 和 s2 的大小,比较的是字符ASCII码大小。
参数:
s1:字符串1首地址
s2:字符串2首地址
返回值:
相等:0
大于:>0
小于:<0
char *str1 = "hello world";
char *str2 = "hello mike";
if (strcmp(str1, str2) == 0)
{
printf("str1==str2\n");
}
else if (strcmp(str1, str2) > 0)
{
printf("str1>str2\n");
}
else
{
printf("str1<str2\n");
}
- strncmp()
#include <string.h>
int strncmp(const char *s1, const char *s2, size_t n);
功能:比较 s1 和 s2 前n个字符的大小,比较的是字符ASCII码大小。
参数:
s1:字符串1首地址
s2:字符串2首地址
n:指定比较字符串的数量
返回值:
相等:0
大于: > 0
小于: < 0
char *str1 = "hello world";
char *str2 = "hello mike";
if (strncmp(str1, str2, 5) == 0)
{
printf("str1==str2\n");
}
else if (strcmp(str1, "hello world") > 0)
{
printf("str1>str2\n");
}
else
{
printf("str1<str2\n");
}
- sprintf()
#include <stdio.h>
int sprintf(char *str, const char *format, …);
功能:根据参数format字符串来转换并格式化数据,然后将结果输出到str指定的空间中,直到出现字符串结束符 ‘\0’ 为止。
参数:
str:字符串首地址
format:字符串格式,用法和printf()一样
返回值:
成功:实际格式化的字符个数
失败: - 1
char dst[100] = { 0 };
int a = 10;
char src[] = “hello world”;
printf(“a = %d, src = %s”, a, src);
printf("\n");
int len = sprintf(dst, “a = %d, src = %s”, a, src);
printf(“dst = " %s”\n", dst);
printf(“len = %d\n”, len);
- sscanf()
#include <stdio.h>
int sscanf(const char *str, const char *format, …);
功能:从str指定的字符串读取数据,并根据参数format字符串来转换并格式化数据。
参数:
str:指定的字符串首地址
format:字符串格式,用法和scanf()一样
返回值:
成功:参数数目,成功转换的值的个数
失败: - 1
char src[] = "a=10, b=20";
int a;
int b;
sscanf(src, "a=%d, b=%d", &a, &b);
printf("a:%d, b:%d\n", a, b);
- strchr()
#include <string.h>
char *strchr(const char *s, int c);
功能:在字符串s中查找字母c出现的位置
参数:
s:字符串首地址
c:匹配字母(字符)
返回值:
成功:返回第一次出现的c地址
失败:NULL
char src[] = "ddda123abcd";
char *p = strchr(src, 'a');
printf("p = %s\n", p);
- strstr()
#include <string.h>
char *strstr(const char *haystack, const char *needle);
功能:在字符串haystack中查找字符串needle出现的位置
参数:
haystack:源字符串首地址
needle:匹配字符串首地址
返回值:
成功:返回第一次出现的needle地址
失败:NULL
char src[] = "ddddabcd123abcd333abcd";
char *p = strstr(src, "abcd");
printf("p = %s\n", p);
- strtok()
#include <string.h>
char *strtok(char *str, const char *delim);
功能:来将字符串分割成一个个片段。当strtok()在参数s的字符串中发现参数delim中包含的分割字符时, 则会将该字符改为\0 字符,当连续出现多个时只替换第一个为\0。
参数:
str:指向欲分割的字符串
delim:为分割字符串中包含的所有字符
返回值:
成功:分割后字符串首地址
失败:NULL
在第一次调用时:strtok()必需给予参数s字符串
往后的调用则将参数s设置成NULL,每次调用成功则返回指向被分割出片段的指针
char a[100] = "adc*fvcv*ebcy*hghbdfg*casdert";
char *s = strtok(a, "*");//将"*"分割的子串取出
while (s != NULL)
{
printf("%s\n", s);
s = strtok(NULL, "*");
}
- atoi()
#include <stdlib.h>
int atoi(const char *nptr);
功能:atoi()会扫描nptr字符串,跳过前面的空格字符,直到遇到数字或正负号才开始做转换,而遇到非数字或字符串结束符(’\0’)才结束转换,并将结果返回返回值。
参数:
nptr:待转换的字符串
返回值:成功转换后整数
类似的函数有:
atof():把一个小数形式的字符串转化为一个浮点数。
atol():将一个字符串转化为long类型
char str1[] = "-10";
int num1 = atoi(str1);
printf("num1 = %d\n", num1);
char str2[] = "0.123";
double num2 = atof(str2);
printf("num2 = %lf\n", num2);