大纲
Linux命令
基础部分:进制转换 词法符号 变量常量 输入输出的方式 分支语句 循环语句 循环控制语句
数组: 一维数组 二维数组 字符数组 排序
指针: 一级指针 二级指针 指针数组 数组指针 指针和数组
函数:函数的基本使用 string函数族群 开辟堆区空间 递归函数
结构体:结构体变量 结构体数组 结构体指针
共用体
枚举
一、linux命令
1.打开终端的方式
-
- 可以直接用鼠标点击
- Ctrl Alt t 新建一个终端窗口,默认路径是家目录
- Ctrl Shift n 路径是当前路径
2.关闭终端方式
-
- 直接点击叉号
- Ctrl d
- 输入exit
3.终端常用命令
hq | @ | Ubuntn | : | ~(家目录) | $(普通用户) |
用户名 | 间隔符 | 主机名 | 间隔符 | 当前路径 | 用户类型 |
查看用户名:whoami
查看主机名:hostname
查看当前完整路径:pwd
查看当前路径下的文件:ls
查看当前路径下的所以文件包含隐藏文件:ls -a
当前路径下的文件的详细信息:ls -l
查看当前路径下的所有文件包含隐藏文件的详细信息:ls -al
d rwxrwxr-x 4 hq hq 4096 7月 21 20:21 23072
d:文件类型 文件夹 -:普通文件
rwx rwx r-x
前三个:当前用户的权限 可读 可写 可执行
中间三个:同组用户的权限 可读 可写 可执行
后三个:其他用户的权限 可读 不可写 可执行
r 读
w 写
x 执行
- 不可以
4 链接数 当前文件夹下有多少个文件夹
hq: 用户名
hq: 组名
4096: 文件的大小
7月 21 20:21 : 文件的最后修改的时间
23072: 文件名
路径切换:cd 确保当前文件夹有要求的文件夹
直接cd 返回家目录
cd 文件夹名字: 进入某一个文件夹
. :当前路径
.. :上一级路径
cd . :在当前路径下
cd .. :返回上一级文件夹
cd 路径:进入指定的路径
cd ~:返回家目录
cd -:返回上一级操作的路径
新建文件夹:mkdir 文件夹名字
新建文件: touch 文件名字
删除文件夹:rmdir 文件夹名字
rm 文件夹名字 -r
删除文件: rm 文件名字
复制文件:cp 文件名字 新建文件的名字 当前路径下
cp 文件名字 目标路径
复制文件夹:cp -r
剪切(移动):mv 文件名字 目标路径
4.常用快捷键
放大终端:Ctrl Shift +
缩小Ctrl -
历史命令:上下键
清屏:Ctrl l
自动补全:Tab
5.vi编辑器![](https://i-blog.csdnimg.cn/blog_migrate/96ea9e81b37fcbe5cf15b7fb04d8f5d4.png)
vi 文件名:使用vi编辑器打开某一个文件,没有文件会自动创建
在命令行模式
复制:yy 复制光标所在行 Nyy 复制多行
粘贴:p
删除:dd Ndd 删除多行
撤销:u
反撤销:Ctrl r
光标回到行首:gg
光标回到末尾:G
行首:0
行尾:$
整理代码:gg=G
底行模式
保存:w
退出:q
保存并退出:wq
强制退出不保存:q!
分屏:vsp
查找:/str
替换:s/str/str2 将光标所在行第一个符合查找的内容进行替换
全部替换:%s/str/str2/g
把 / 替换成 \ %s/\//\\/g \ 表示转义
取消高亮:nohl
显示行号:set nu
取消行号:set nonu
二、gcc编译器
4个步骤:1.预处理 2.编译 3.汇编 4.链接
- 预处理
命令:gcc -E hello.c -o hello.i
删除文件中的注释 展开头文件 替换宏定义
- 编译
命令:gcc -S hello.i -o hello.s
检查语法错误 有错报错 没有错误生成汇编语言
- 汇编
命令:gcc -c hello.s -o hello.o
将汇编语言转化成二进制的文件
- 连接
命令:gcc hello.o -o hello
链接库文件,生成可执行文件
执行文件:gcc -c hello.c 默认生成一个a.out文件 ./a.out
三、进制之间的转换
二进制 八进制 十进制 十六进制
二进制转十进制
101100——》0*20+0*21+1*22+1*23+0*24+1*25=4+8+32=44
十进制转二进制
短除法:对2进行倒取余
66——》1000010
拆分发:
64——》26 + 21
1000010
计算器演算
二进制转八进制
一个八进制的数可以用3位二进制的数表示
7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
111 | 110 | 101 | 100 | 011 | 010 | 001 | 000 |
110 101——》065
010 101——》025
1 010 101 011——01253
八进制转二进制
075——111 101
031——11 001
二进制转十六进制
十六进制:满十六进一 最大数是15 0-9 a-f
以0x开头
一个十六进制的数可以用4位二进制表示
15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 |
1111 | 1110 | 1101 | 1100 | 1011 | 1010 | 1001 | 1000 | 111 | 110 | 101 | 100 | 011 | 010 | 001 |
10 0010 1010——0x22a
11 0010 1010——0x32a
十六进制转二进制
0x45——0100 0101
0xf65——1111 0110 0101
二:110 0110 1010
八:03152
十:2+8+32+64+512+1024=1642
十六:0x66a
二:1111 0110
八:0366
十:2+4+16+32+64+128=246
十六:0xf6
非数值型的数据
ASCII码值:
在终端输入man ascii查看ASCII表,按q键退出
'\0' 字符串结束的标志 ASCII值是0
'\n' 换行 ASCII值是10
'0'——'9' ASCII值是48——57 字符型数字转数字 -48
A——Z ASCII值是65——90 大写转小写 +32
a——z ASCII值是97——122 小写转大写 -32
man手册:第一章shell命令 第三章库函数
四、词法符号
分类:关键字 标识符 运算符 分隔符 标点符
1.关键字(32)
分类:
存储类型:auto自动 static静态 extern引用外部 register寄存器
数据类型:char int long short float double signed(有符号) unsigned(无符号)
- 构造类型:struct union enum
选择类型:if else switch case default
循环类型:for while goto do break continue
其它 :void typedef const sizeof return volatile
2.标识符
定义:用于标识变量 宏定义 函数名
命名规范:
由数字,字母,下划线组成
不能以数字开头
不能和关键字重名
见名知义
变量
概念:在程序运行当中会发生变化的量
定义格式:存储类型 数据类型 变量名
例: (auto) int num;
3.运算符
算数运算符
+
-
*
/ 整数进行相除 向下取整只取整数 int a = 10/3 a=3
float b = 10/3——》3.000000
float c = 10.0/3——》3.333333
% 取余,不能对float,double取余
++ 在自身进行加1操作 b = a++ ++在后,先赋值,后运算
b = ++a ++在前,先运算,后赋值
独立成句 a++ = ++a
-- 在自身进行减1操作和++同理
逻辑运算符
&& 逻辑与 全真为真,一假则假 真为1,假为0
|| 逻辑或 全假为假,一真则真
! 逻辑非 取反,非真即假,非假即真
单目运算 只需要一个操作数如a++
双目运算 需要两个操作数 + - && || 等
三目运算 需要三个操作数
截断法制
在逻辑与运算中,前面为假后面不进行运算
在逻辑或中,前面为真后面半截袖运算
#include <stdio.h>
int main()
{
int a=5,b=6,c=7,d=8,m=2,n=2;
(m=a>b)&&(n=c>d);
printf("%d %d",m,n);//m=0 n=2
}
#include <stdio.h>
int main()
{
int a=5,b=6,c=7,d=8,m=2,n=2;
(m=a<b)||(n=c>d);
printf("%d %d",m,n);//m=1 n= 2
}
位运算符
& 位与 对二进制的补码进行运算,全1则1,有0则0
int a=5
int b=-2
int c=a&b
printf("%d",c)//4
a= 5 原码 反码 补码:
0000 0000 0000 0000 0000 0000 0000 0101
b=-2 原码 反码 补码:
原码:1000 0000 0000 0000 0000 0000 0000 0010
反码:1111 1111 1111 1111 1111 1111 1111 1101
补码:1111 1111 1111 1111 1111 1111 1111 1110
结果补码:
0000 0000 0000 0000 0000 0000 0000 0100
结果:4
| 位或 有1则1,全0则0
5|-2:
5的原码 反码 补码:
0000 0000 0000 0000 0000 0000 0000 0101
-2补码:
1111 1111 1111 1111 1111 1111 1111 1110
结果的补码:
1111 1111 1111 1111 1111 1111 1111 1111
反码:1111 1111 1111 1111 1111 1111 1111 1110
原码:1000 0000 0000 0000 0000 0000 0000 0001 = -1
~ 按位取反 1变0,0变1
~5:
5的原码 反码 补码:
0000 0000 0000 0000 0000 0000 0000 0101
~: 1111 1111 1111 1111 1111 1111 1111 1010
反码:1111 1111 1111 1111 1111 1111 1111 1001
原码:1000 0000 0000 0000 0000 0000 0000 0110 = -6
^ 异或 左右两边相同为0,不同为1
5^-2
5的原码 反码 补码:
0000 0000 0000 0000 0000 0000 0000 0101
-2补码:
1111 1111 1111 1111 1111 1111 1111 1110
结果补码:
1111 1111 1111 1111 1111 1111 1111 1011
反码:1111 1111 1111 1111 1111 1111 1111 1010
原码:1000 0000 0000 0000 0000 0000 0000 0101 = -5
<< 左移
操作的是补码 符号位不变,左边丢弃0,右边补0
5<<3---->40
0000 0000 0000 0000 0000 0000 0000 0101
0000 0000 0000 0000 0000 0000 0010 1000 = 32+8=40
-5<<3
-5: 1000 0000 0000 0000 0000 0000 0000 0101
反码:1111 1111 1111 1111 1111 1111 1111 1010
补码:1111 1111 1111 1111 1111 1111 1111 1011
1111 1111 1111 1111 1111 1111 1 1011 000
反码:1111 1111 1111 1111 1111 1111 1 1010 111
原码:1000 0000 0000 0000 0000 0000 0 0101 000 = -40
5*2^3=40
>> 右移
操作的是补码 右边丢弃,左边补0
5>>2
5的原码 反码 补码:
0000 0000 0000 0000 0000 0000 0000 0101
0000 0000 0000 0000 0000 0000 0000 0001 = 1
5/2^2---->1
负数移动,右边丢弃,左边补1
-5>>2:
-5: 1000 0000 0000 0000 0000 0000 0000 0101
反码:1111 1111 1111 1111 1111 1111 1111 1010
补码:1111 1111 1111 1111 1111 1111 1111 1011
>>2
补码:1111 1111 1111 1111 1111 1111 1111 1110
反码:1111 1111 1111 1111 1111 1111 1111 1101
原码:1000 0000 0000 0000 0000 0000 0000 0010 = -2
置1公式:a|(1<<n)
1:0000 0000 0000 0000 0000 0000 0000 0001 根据格式得 1|(1<<1) 1<<1:0000 0000 0000 0000 0000 0000 0000 0010 1| 0000 0000 0000 0000 0000 0000 0000 0001 结果: 0000 0000 0000 0000 0000 0000 0000 0011 |
置0公式:a&(~(1<<n)) n要置0或1的位数 a原本的数
对于整数来说有原码 反码 补码
int 4个字节 1个字节是8位
原码 | 反码 | 补码 | |
正数 | 二进制的本身 | 二进制的本身 | 二进制的本身 |
负数 | 二进制的本身 | 在原码的基础上取反,除符号位 | 在反码的基础上加1 |
1: 0000 0000 | 0000 0000 | 0000 0000 | 0000 0001
-1: 1000 0000 | 0000 0000 | 0000 0000 | 0000 0001
-1的反码: 1111 1111 | 1111 1111 | 1111 1111 | 1111 1110
补码:1111 1111 | 1111 1111 | 1111 1111 | 1111 1111
最高位是符号位
关系运算符
>
<
>=
<=
==
!=
提醒:1<x<=10 ---------> 1 < x && x <= 10
a != b------>a=1,b=2 ---->1真
a != b------>a=1,b=1 ---->0假
赋值运算符
=
+= a += 10--->a=a+10
-=
*= a *= 10--->a=a*10
/=
三目运算符(条件运算符)
?:
格式:表达式1?表达式2:表达式3
int a=3, b=5;
int c= a>b ? a++ : ++b;
c?----->6
运算规则:先判断表达式1,成立运算2,1不成立运算3
运算符的优先级
单目运算:! ~ ++ --
算术运算:* / % + -
移位运算:<< >>
关系运算:> < >= <= == !=
位与运算:&
异或运算:^ |
逻辑运算:&& ||
条件运算:?:
赋值运算:= += -= *=....
口诀:单算移关与 异或逻条赋
单目运算,从右向左运算
4.标点符号
,;( ) { } [ ]
5.分隔符
换行 空格 tab
五、变量
概念:在程序运行当中会发生变化的量
定义格式:存储类型 数据类型 变量名
赋值方式:
1在定义的同时进行赋值
int a = 10;
2定义之后进行赋值
int a;
a = 10;
1.变量的分类:
全局变量:在函数外声明的变量,变量可以在全局使用
局部变量:在函数内声明的变量,只能在函数内部使用
2.全局变量和局部变量的区别?
局部变量 | 全局变量 | |
定义位置 | 函数内部 | 函数外部 |
作用域 | 在声明函数内可以使用 | 全局可用 |
初始值(未赋值) | 随机值 | 值为0 |
存储位置 | 栈区 | 全局区 |
生命周期 | 和定义函数共存亡 | 和整个程序共存亡 |
- 数据类型
cahr | 字符型 | 1个字节 |
int | 整型 | 4个字节 |
short | 短整型 | 2个字节 |
long | 长整型 | 4个字节 |
float | 单精度浮点型 | 4个字节 |
double | 双精度浮点型 | 8个字节 |
六、常量
概念:在程序运行当中不会发生变化的量
分类:
1.字符型常量
用单引号( ' ' )引起来的数据就是字符型的数据,只能包裹单个字符
'a'---------->字符a
a------------>变量
' '---------->空格字符
'\n'--------->换行符
'A' '\x41'十六进制 '\101'八进制--------->字符A
2.字符串常量
多个字符拼接在一起,使用双引号( " " )引起来的
"hello" 占6个字节 \0:字符串结束的标志
对于一个字符串来说字符串的个数是实际个数+1
sizeof():查看数据所占内存大小 返回值是所占内存的字节数
3.整型常量
1 2 3 4
一般使用int定义储存
4.浮点型常量
0.9 1.23 2.56
小数都是浮点型常量
一般定义使用float double
5.指数常量
3*10^3 3e3
注意:
e的前面可以是整数也可以是小数,但是e的后面只能是整数不可以是小数
6.标识常量
宏定义:起表示作用(宏替换 宏代替)
定义格式:#define 宏名 常量或表达式
宏名遵循标识的命名规则
特点:只能单纯的替换,不可以进行运算
七、输入输出
1.按照字符输入输出
输入的是字符,输出的也是字符,getchar putchar都是函数实现输入输出
函数三要素:功能 参数 返回值
(1)getchar
int getchar(void)
功能:从终端获取(输入)一个字符
参数:无
返回值:一个int类型的数据,输入字符的ASCII值
(2)putchar
int putchar(int c)
功能:向终端输出一个字符
参数:要输出字符的ASCII码值 可以是字符
返回值:要输出字符的ASCII码值
2.按照格式输入输出
(1)printf
int printf(const char *format, ...);
功能:按照格式向终端输出
参数:format:字符串 按照字符串中的格式输出
返回值:输出字符的个数
格式:
"%d" int类型
"%f" float类型
"%lf" double类型
"%.n" 保留小数点后几位
"%c" cahr类型
"%s" 字符串类型
"%#x" 十六进制的数
"%#o" 八进制的数
"%p" 地址
"%e" 指数
"%m" 列宽 结合scanf
(2)scanf
int scanf(const char *format, ...);
功能:按照格式从终端输入内容
参数:format:字符串 按照字符串中的格式输入
返回值:正确输入的个数
(3)回收垃圾字符
空格 可以回收一个或多个字符(空格 回车 tab键)
%*c 只能回收一个字符
getchar( ) 一般用在循环里面
while(1)
{
char a;
scanf("%c",&a);
printf("%c\n",a);
getchar();
}
八、强制转换
int a = 5;
float b = (float)a / 2;
printf("%f\n",b);
隐式强制转换
char a = 's';
printf("%f\n",(float)a);
九、分支语句
if else
只执行一条
基本结构:
if(表达式)
{
语句块1;
}
else
{
语句块2;
}
int age;
scanf("%d",&age);
if(age >= 18)
{
printf("可以进入网吧学习\n");
}
else
{
printf("圆润的离开\n");
}
执行顺序:
首先走if语句中的表达式,如果表达式成真(即满足条件)则走语句块1的位置,表达式为假(即不满足条件)走else后面的语句块2。
分层结构:
if(表达式1)
{
语句块1;
}
else if(表达式2)
{
语句块2;
}
else if(表达式3)
{
语句块4;
}
……
else
{
语句块N;
}
switch case
基本结构:
switch(表达式或常量)
{
case 常量1:
语句块1;
break;
case 常量2:
语句块2;
break;
……
defeult:
语句块N;
break;
}
case后面只能跟一个固定值
首先根据switch语句的表达式进入,如果是常量1则走语句块1,遇到break跳出;是常量2走语句块2,遇到break跳出;……都不符合走defeult,执行语句块N,遇到break跳出
十、循环语句
for循环
基本结构:
定义一个循环变量
for(表达式1;表达式2;表达式3)
{
语句块;(循环体)
}
表达式1:给循环变量赋初始值
表达式2:循环条件也是结束循环的条件
表达式3:递增或递减
定义一个循环变量,根据表达式1给循环变量赋初始值,根据表达式2结束循环,根据表达式3自增或自减,执行语句块
变形1:
int i = 0;
for(;i < 10;i++)
{
语句块;
}
变形2
int i = 0;
for(;i < 10;)
{
语句块;
表达式3;
}
变形3:
int i;
for(;;)//死循环
{
if(表达式2)
{
语句块(循环体);
表达式3;
}
else
{
break;
}
}
int i = 0;
for ( ; ; )
{
if(i<10)
{
printf("*");
i++;
}
else
{
break;
}
}
printf("\n");
嵌套结构
基本结构:
定义两个循环变量
for(表达式1;表达式2;表达式3)
{
for(表达式4;表达式5;表达式6)
{
语句块;(循环体)
}
}
int i,j;
for(i = 0;i < 5;i++)//行数
{
for (j = 0; j < 5; j++)//列数
{
printf("*");
}
printf("\n");
}
特点:
1.双重for循环 外层循环执行一次,内层循环就要走完全部
- 外层循环控制行数 ,内层循环控制列数
while循环
格式:
定义一个循环变量
while(判断条件)
{
语句块;
递增或递减;
}
定义一个循环变量,根据while语句的判断条件,为真进入循环,为假退出循环
int i = 1,sum = 0;
while (i <= 100)
{
sum += i;
}
printf("%d\n",sum);
do while
结构:
do
{
语句块;
}
while(判断条件)
先执行do语句里的语句块,再根据while语句的判断条件,为真继续循环,为假退出循环
例如:计算1-100的和
int i = 0, sum = 0;
do
{
sum += i;
i++;
} while (i <= 100);
printf("%d\n", sum);
循环控制语句
break continue
break:直接跳出整个循环(程序)
continue:跳出循环,只跳出当前循环
使用场景:在循环语句中使用,在判断语句中使用需要判断
死循环
while(1)
{
}
for(;;)
{
}
while(1) ;
十一、数组
概念:
具有一定顺序的若干变量的集合
定义数组:
存储类型 数据类型 数组名[元素的个数]
int arr[5] //定义了一个数组名为arr的数组并且数组中可以存放五个数,开辟了五个int类型的空间存放
int arr[5] = {1, 2, 3, 4, 5}
0, 1, 2, 3, 4
数组名:代表数组的首地址
地址常量:不可以被重新赋值,不能为左值,不可以进行++ --的操作
数组中的每一项都是变量,称之为元素
数组的更新:
访问元素:数组名[元素的下标],下标从零开始
arr[0]----------->在arr数组中访问第一个元素的值
访问第一个元素arr[0]
访问第N个元素arr[N-1]
如果一个数组中存放了5个元素,对应下标到5-1的位置,
注意数组越界的问题,会有修改别的变量的风险,原变量的值会
修改元素的内容:数组名[元素的下标] = 新值
特点:
定义数组的数据类型和后面要存储的数据相同
内存地址是连续的,
定义数组名时遵循标识符的命名规则
数组下标从零开始,N个元素,下标到N-1
在同一个函数中,数组名和变量名不可以重名
如:int a[5];
int a=10;//报错
分类:
一维数组 二维数组
一维数组:
格式:存储类型 数据类型 数组名[元素的个数]
访问元素:数组名[下标],下标从零开始
数组名:数组首地址,不能为左值
初始化:全部初始化;
int a[3] = {1,2,3}
注意数组越界的问题 int a[3] = {1,2,3,4}
部分初始化:
int a[3];
a[0]=1;
a[1]=2;
//1,2随机值
int a[3]={1,2}//1,2,0
未初始化:
int a[3];
定义空数组:
int a[3] = {0,0,0};
int a[3] = {0};
int a[3] = { };
引用:
先定义后引用
每一次引用只能引用一个元素a[N],想要引用多个就只能循环遍历
引用时要注意数组越界,有的编译器不会报错
打印数组的元素的地址,使用%p格式
引用时数组的内存地址是连续的
数组遍历
int a[3] = {1,2.3};
a[0],a[1],a[2]
for中的循环变量i i=0, i<3, ---->0,1,2
数组的输入输出:
char a[32];
int i,j;
for (j = 0; j < 6; j++)
{
scanf("%c ",&a[j]);
}
printf("-----------------------\n");
for ( i = 0; i < 6; i++)
{
printf("%c ",a[i]);
}
printf("\n");
gets
char *gets(char *s);
功能:从终端输入一个字符串
参数:s:字符数组的首地址
返回值:目标字符串的首地址
注意:使用gets的时候要注意关心数组越界的问题,使用时会报警告不要在意,不影响运行
puts
int puts(const char *s);
功能:向终端输出一个字符串
参数:要输出的字符数组的首地址
返回值:输出字符的个数
数组的大小
元素的个数*数据类型的大小
sizeof()
int arr[5];
printf("%d\n",sizeof(arr));
数组的元素的个数
数组的大小/数据类型的大小
sizeof(数组名)/sizeof(数据类型)
清空函数
bzero
#include <strings.h>
void bzero(void *s, size_t n);
功能:
将内存空间设置为0
参数:
s:需要清空空间的首地址
n:需要清空的空间的字节大小
返回值:无
int a[5] = {1,2,3,4,5};
printf("清空之前:\n");
for (int i = 0; i < 5; i++)
{
printf("%d ",a[i]);
}
printf("\n");
bzero(a,sizeof(a));
printf("清空之后:\n");
for (int i = 0; i < 5; i++)
{
printf("%d ",a[i]);
}
printf("\n");
memset
#include <string.h>
void *memset(void *s, int c, size_t n);
功能:将内存空间设置为0
参数:
s:需要清空空间的首地址
c:将空间设置成的大小,默认是写0,设置成其它的不准确
n:需要清空的空间的字节大小
返回值:
清空空间后的首地址
int a[5] = {1,2,3,4,5};
printf("清空之前:\n");
for (int i = 0; i < 5; i++)
{
printf("%d ",a[i]);
}
printf("\n");
memset(a,0,sizeof(a));
printf("清空之后:\n");
for (int i = 0; i < 5; i++)
{
printf("%d ",a[i]);
}
printf("\n");
字符数组
概念:用来存放若干字符的集合(存放字符串),数据类型是char类型
定义:
char arr[] = {"hello"};//字符串以\0结尾,元素个数是6,字节数也是6
------------>char arr[6] = {'h','e','l','l','o','\0'};
char b[] = "hello";//字符串以\0结尾,元素个数是6,字节数也是6
char c[32] = "hello";//实际元素个数是6,字节数是32
注意:在定义字符数组时,经常省略元素的个数,需要注意数组越界
字符数组的输入输出:
for循环,打印格式用%c
可以使用%[^\n]直到遇到\n,才理解为结束
char a[32];
int i,j;
scanf("%[^\n]",a);
printf("%s\n",a);
练习:hello 输出成 olleh
char a[] = "hello";
int i,j;
for (i = 0; a[i] != '\0'; i++);//存储字符的真实个数
// char last = a[i-1];//int last = i-1
// char index = 0;
for (j = 0; j < i / 2; j++)
{
char temp;//三杯水交换
temp = a[j];
a[j] = a[i - 1 - j];
a[i - 1 - j] = temp;
}
printf("%s\n",a);
char s[] = {"hello"};
char a[6] = {};
int i,j = 4;
for (i = 0; i < 5; i++)
{
a[j] = s[i];
j--;
}
printf("%s\n",a);
字符数组计算字符个数(字符数组的实际长度)
for循环,将判断条件设置为不等于\0进行计数加1
sizeof(数组名)/sizeof(数据类型)
sizeof(数组名)-1
strlen
#include <string.h>
size_t strlen(const char *s);
功能:计算字符串实际长度
参数:s :字符串的首地址
返回值:字符串的实际长度
char a[16] = {"hello"};
int ret = strlen(a);
printf("长度是%d\n",ret);
sizeof和strlen的区别
sizeof是一个关键字,strlen是一个函数
sizeof作用用来计算数据所占空间的大小,strlen就是用来计算字符串的实际长度
sizeof计算时包含\0但是strlen不包含,sizeof比strlen多一个(在定义字符数组忽略掉元素个数的情况下)
排序
冒泡排序
两两进行比较,相邻的两个数进行比较
特点:第一轮比较完成之后最大值在最后面
int a[5] = {5,4,3,2,1,};
第一轮:i=0;----外层循环控制轮数(元素个数-1),内层循环控制取到的元素(比较次数,数组元素个数-1-i)
5和4进行比较---》交换位置---》45321
5和3进行比较---》交换位置---》43521
5和2进行比较---》交换位置---》43251
5和1进行比较---》交换位置---》43215
比较了4(数组元素的个数-1)次,j=0-3 j<4-0,
第二轮:i=1
4和3进行比较---》交换位置----》34215
4和2进行比较---》交换位置----》32415
4和1进行比较---》交换位置----》32145
比较了三次,j=0-2,j<4-1
第三轮:i=2
3和2进行比较---》交换位置----》23145
3和1进行比较---》交换位置----》21345
比较了二次,j=0-1,j<4-2
第四轮:i=3
2和1进行比较---》交换位置----》12345
比较了一次,j=0,j<4-3
int a[5] = {5,4,3,2,1};
int i,j,temp;
for (i = 0; i < 5-1; i++)
{
for (j = 0; j < 5-1-i; j++)
{
if(a[j] > a[j+1])
{
temp = a[j];
a[j] = a[j + 1];
a[j + 1] = temp;
}
}
}
for (i = 0; i < 5; i++)
{
printf("%d",a[i]);
}
选择排序
在N个数中找最小值,标记下标
特点:第一轮结束之后最小值在最前面
双重循环 外层控制轮数i,内层循环比较次数ji = 0,默认标记的最小值a[0],k = 0,j = 0+1 a[j] a[k]
i = 1,默认标记的最小值a[1],
i = 2,默认标记的最小值a[2],
……
如果数组有N个元素,轮数需要比较n-1轮,第一轮比较时循环变量j=1
int a[5] = {5,4,3,2,1,};
第一轮:i=0,k=i;----外层循环控制轮数i,内层循环比较次数j
a[0]={5}和a[1]={4}进行比较---》a[1]={4}小---》k=1
a[1]={4}和a[2]={3}进行比较---》a[2]={3}小---》k=2
a[2]={3}和a[3]={2}进行比较---》a[3]={2}小---》k=3
a[3]={2}和a[4]={1}进行比较---》a[4]={1}小---》k=4
比较了4(数组元素的个数-1)次
第二轮:i=1,k=i;
a[1]={4}和a[2]={3}进行比较---》a[2]={3}小---》k=2
a[2]={3}和a[3]={2}进行比较---》a[3]={2}小---》k=3
a[3]={2}和a[4]={1}进行比较---》a[4]={1}小---》k=4
比较了三次
第三轮:i=2,k=i;
a[2]={3}和a[3]={2}进行比较---》a[3]={2}小---》k=3
a[3]={2}和a[4]={1}进行比较---》a[4]={1}小---》k=4
比较了二次
第四轮:i=3,k=i;
a[3]={2}和a[4]={1}进行比较---》a[4]={1}小---》k=4
比较了一次
int a[5] = {5,4,3,2,1};
int i,j,k,temp;//i,j循环变量 k标记最小值 temp用于交换值
for (i = 0; i < 5-1; i++)//轮数
{
k = i;//默认最小值是第i+1轮的第一个数
for (j = i+1; j < 5; j++)//比较次数
{
if (a[k] > a[j])
{
k = j;
}
}
if (k != i)
{
temp = a[i];
a[i] = a[k];
a[k] = temp;
}
}
for (i = 0; i < 5; i++)
{
printf("%d ",a[i]);
}
printf("\n");
二维数组
格式:存储类型 数据类型 数组名[行数][列数]
int arr[2][3];//定义了一个两行三列的二维数组
int arr[2][3] = {1,2,3,4,5,6};
行下标 列下标 | 0 | 1 | 2 |
0 | 1 a[0][0] | 2 a[0][1] | 3 a[0][2] |
1 | 4 a[1][0] | 5 a[1][1] | 6 a[1][2] |
访问元素:数组名[行下标][列下标]
行下标和列下标都不会超过行数和列数,注意不能数组越界
int arr [][3];可以省略行数,但是不能省略列数
元素的个数:行数*列数
数组大小:行数*列数*数据类型的大小
sizeof(数组名)
一维数组:int a[3]={1,2,3} ----->a就是第一个元素的地址 a+1:第二个元素的地址
二维数组:int arr[2][3]={1,2,3,4,5,6};----->arr表示第一行的首地址, arr+1:第二行的首地址
arr[0]----->表示第一行第一列的地址 arr[0]+1:第一行第二列的地址
对地址进行加法操作的是数据单位,而不是一个字节数
a+1:向后移动一个数据单位而不是一个字节,数据单位的移动大小取决于数组的数据类型,int----一个数据单位就是4个字节,char----一个数据单位就是一个字节
初始化
全部初始化
int arr[2][3] = {1,2,3,4,5,6};按照顺序赋值
int arr[2][3] = {1,2,3},{4,5,6};按照行进行赋值
部分初始化
int arr[2][3] = {1,2,3,4};按照顺序赋值1,2,3,4,0,0
int arr[2][3] = {1,2},{4};按照行进行赋值1,2,0,4,0,0
未初始化
只能一个元素一个元素的赋值,未赋值的是随机数
int arr[2][3];
a[0][0] = 1;
内存存储
二维数组的遍历
for的嵌套,外层循环控制行数,内层循环控制列数
int a[m][n]={};
int i.j;
for(i=0;i<m;i++)
{
for(j=0;j<n;j++)
{
scanf()/printf()
}
}
输入输出
字符串可以直接按行输出
char arr[2][6] = {i,hello,china};按照顺序赋值
printf("%s %s %s",a[2],a[1],a[0]);
指针
地址:内存当中的每个字节都是一个单元格,每个单元格都有一个编号
指针:就是地址,内存地址
指针变量:用来存储指针的变量
定义格式:存储类型 数据类型 *指针变量名
int *p;定义了一个int类型的指针p
int a = 10;
p = &a;
第二种:
int a = 10;
int *p = &a;
注意:定义指针的数据类型和将要存储的数据的数据类型保持一致
指针的操作符
&:取地址符,取变量的地址
*:取内容,对地址进行取内容
*&a:------>先取地址再取内容a:10
&*a:这样写&*变量不行,错误
&*p:这样写&*指针可以
指针和指针所指的变量之间的关系
int a = 20;
int *p = &a;
printf("%d %d\n",a,*p);
printf("%p %p\n",&a,p);
printf("%p\n",&p);
指针的初始化
将普通变量的地址赋值给指针
int a = 20;
int *p = &a;在定义的同时进行赋值
int a=20;
int *p = NULL;//int *p;是野指针,会乱指
p = &a;先定义后赋值
a=30;---->*p=30
将数组的首地址赋值给指针
char s[10] = "hello";
char *p = s;//存储数组中第一个元素的地址,指向h字母的位置
将指针变量的内容赋值给另一个指针
float a = 1.99;
float *p = &a;
float *q = p;
指针的运算
算术运算+ - ++ --
char a[] = "hello";
char *p = a;//0x100
p + 1;//0x101:可以使地址向后移动(向高地址位置进行移动)移动1个字节
int a[] = {1,2,3}
int *p = a;//0x100
p + 1;//0x104:可以使地址向后移动(向高地址位置进行移动)移动4个字节
指针在进行计算的时候移动字节大小数据类型决定的
p+1:向高地址位置移动一个数据单位,但是指针p的指向并不会发生时改变
p-1:向低地址位置移动一个数据单位,但是指针p的指向并不会发生时改变
printf("%p %p\n",p,++p);p和++p不能放在同一个printf中,运算不是从左往右,根据编译器的不同,可能会先运算++p;
++p:向高地址位置移动一个数据单位,同时改变指针p的指向
--p:向低地址位置移动一个数据单位,同时改变指针p的指向
p++:向高地址位置移动一个数据单位,先运算(输出 或者计算)后移动指向,改变指针p的指向
p--:向低地址位置移动一个数据单位,先运算(输出 或者计算)后移动指向
练习:
int arr[5] = {1, 2, 3, 4, 5};
int *p1 = arr,a=0;
printf("%d\n", *p1);//1
printf("%p\n", p1);//第一个元素的地址
p1++;
printf("%d\n", *p1);//2
printf("%p\n", p1);//第二个元素的地址
p1--;
printf("%d\n", *p1);//1
printf("%p\n", p1);//第一个元素的地址
a=*(p1+3)
printf("%d %d %d\n", *(p1 + 3), a,*p1);//4 4 1
两个地址之间的差 = 两个地址之间间隔的元素的个数
int a[] = {1,2,3};
int *p = a;
printf("%d",((p+2)-p));
关系运算> < == !=
指针之间比较大小始终指向高地址的指针大,
int a[] = {5,4,3};
int *p = a;
int *q = p+2;
if
注意:指针之间的比较时必须是同一区域的比较(同一个数组之间进行比较),非同一区域的比较是没有意义的
练习:
char s[32] = "hello";
char *p = s;
p++;
char y = (*--p)++;
printf("%c",y);//
练习:
练习:
编写一个程序实现功能:将字符串“Computer Science”赋值给一个字符数组,然后从第一个字母开始间隔的输出该字符串,用指针完成。结果:Cmue cec
例:
int x[5] = {10, 20, 30};
int *px = x;
printf("%d,", ++*px);//11--->*px=*px+1
printf("%d\n", *px);//11
px=x
printf("%d,", (*px)++); // 11
printf("%d\n", *px);//12
px=x+1
printf("%d,", *px++); // 20
printf("%d\n", *px);// 30
px=x+1
printf("%d,", *++px); // 30
printf("%d\n", *px);// 30
指针的大小
指针的大小取决于操作系统,
64位的操作系统,指针占8个字节
linux是一个32位操作系统 一个字节是8位,指针占4个字节
总结:
- 指针的大小取决于操作系统,32位的操作系统就是4个字节
- 内存地址是固定的,但是变量的地址是不固定的,栈区的地址是随机分配的
- 指针的数据类型是他指向的空间的数据类型决定
段错误
Segmentation fault (core dumped)
段错误 (核心已转储)
- 野指针
定义了一个指针没有初始化,会产生野指针
int *p;
scanf("%d",p);
printf("%d",*p);
解决:给指针进行赋值
int *p = NULL;
scanf("%d",p);
printf("%d",*p);
指针p 释放了(free),没有赋值为NULL
解决:p = NULL;
- 内存泄漏
对非法空间进行操作
char *p = "hello";//hello存放在常量区,不可被修改,可以被访问
scanf("%s",p);
printf("%s",*p);
解决:
char s[10] = {"hello"};
char *p = s;
scanf("%s",p);
printf("%s",*p);
练习1.将字符串转换成整型数字输出。用指针实现
要求:字符串为0-9组成,输出数据为一个整形数
例:char s[]="123" ------> num=123
#include <stdio.h>
#include <string.h>
int main(int argc, char const *argv[])
{
char s[10] = {"12345"};
char *p = s;
int num = 0,i;
for (i = 0; i < strlen(s); i++)
{
num = (num*10)+ *p - 48;
p++;
}
printf("%d\n",num);
return 0;
}
#include <stdio.h>
int main(int argc, char const *argv[])
{
char s[10] = {"12345"};
char *p = s;
int num = 0;
while(*p)//*p!='\0'
{
num =(num*10) + *p - 48;
p++;
}
printf("%d",num);
return 0;
}
指针修饰
const常量化
只要被const修饰的内容就是一个只读,不可以被修改
- .修饰变量
int = 10;
a = 20;
const int a = 10;//a就不能被修改
或者
int const a = 10;
a不可以修改,但是可以通过间接修改的方式修改掉a的值,通过指针修改
- 修饰指针
2.1修饰指针*p
const int *p;//修饰p不可以被修改
2.2修饰指针p
int *const p = &a;指针的指向不能被修改
const int * const p = &a;//修饰指针p和*p
2.1void不可以修饰变量
2.2可以修饰指针
void *p;//指针p被void修饰,p是一个任意类型的指针
使用场景:函数的参数 函数的返回值
void修饰过的指针可以被赋值,但是不可以取内容,需要对内容进行强制转换,强制转换时只能为右值不能为左值
大小端
大端:在低地址存储高字节数据,在高地址存储低字节数据
小端:在低地址存储低字节数据,在高地址存储高字节数据
0x12345678(左为高字节,右为低字节)
int a=0x12345678 起始地址:0x100
0x100 | 0x101 | 0x102 | 0x103 | |
大端存储 | 0x12 | 0x34 | 0x56 | 0x78 |
小端存储 | 0x78 | 0x56 | 0x34 | 0x12 |
低地址: | 0x12 |
0x34 | |
0x56 | |
高地址: | 0x78 |
大端 |
高地址: | 0x78 |
0x56 | |
0x34 | |
低地址: | 0x12 |
小端 |
#include <stdio.h>
int main(int argc, char const *argv[])
{
int a = 0x12345678;
char b;
b = (char)a;
if (b==0x12)
{
printf("大端存储\n");
}
else if(b==0x78)
{
printf("小端存储\n");
}
return 0;
}
二级指针
概念:二级指针是用来存储一级指针的地址
格式:存储类型 数据类型 **指针变量;
int a = 10;
int *p = &a;
int **q = &p;
访问a的值:a *p **q
访问a的地址:&a p *q
指针和数组
直接访问:按照变量的地址去访问变量的值(通过数组名去访问)
间接访问:通过存放变量的地址的变量去访问变量(通过指针去访问)
指针和一维数组
int a[5] = {1,2,3,4,5};
int *p = a;
访问数组的元素a[i]的值:
直接访问:a[i] *(a+i)
间接访问:p[i] *(p+i)
访问数组的元素a[i]的地址:
直接访问:&a[i] a+i
间接访问:&p[i] p+i
注意:
a和p本质是不同的,a是数组名代表数组的首地址,是地址常量,不可以被修改
指针p是一个变量,可以被修改
如:p++//可以
a++//不可以
练习:
设数组a[5]={10,20,30,40,50};已知指针p指向a[1];则表达式*++p的值是___30___
指针和二维数组
int a[2][3] = {1,2,3,4,5,6};
//a:数组名 第一行首地址 a+1:第二行的首地址
//a[0]:第一行第一列的地址(列地址) a[0]+1:第一行第二列的地址
直接访问:
a:数组名 第一行首地址
*a:代表第一行第一列的地址(*在这代表降级)
*a+1:代表第一行第二列的地址
*(a+1):代表第二行第一列的地址
*(a+1):代表第二行第二列的地址
访问二维数组a[i][j]的值:
直接访问:a[i][j] *(*(a+i)+j) *(a[i]+j)
访问二维数组a[i][j]的地址:
直接访问:&a[i][j] *(a+i)+j a[i]+j
间接访问:
十三、数组指针:
定义:本质还是指针,指向数组(行指针)
地址是行地址
格式:存储类型 数据类型 (*指针变量名)[列数]
int a[2][3] = {1,2,3,4,5,6};
int (*p)[3] = a;
printf("%p %p\n",p,a);
数组指针移动时:一行一行进行移动,移动的字节数取决于列数是多少(列数*数据类型的大小)
访问数组中a[i][j]
值:p[i][j] *(*(p+i)+j) *(p[i]+j)
地址:*(p+i)+j p[i]+j &p[i][j]
数组指针的大小
sizeof(数组指针名)= 4字节 取决于操作系统
练习:
Int a[2][3]={1,2,3,4,5,6};用数组指针遍历二维数组。
若有以下说明和语句,int c[4][5],(*p)[5];p=c;能正确引用c数组元素的是_____.
A) p+1 B) *(p+3) C) *(p+1)+3 D) *(p[0]+2)
答案:D
若有定义:int a[2][3],则对a数组的第i行j列元素地址的正确引用为____.
a)*(a[i]+j) b)(a+i) c)*(a+j) d)a[i]+j
答案:D
有以下定义
char a[10],*b=a;
不能给数组a输入字符串的语句是()
A)gets(a) B)gets(a[0]) C)gets(&a[0]); D)gets(b);
答案:B
有以下程序
main()
{
char a[]="programming",b[]="language";
char *p1,*p2;
int i;
p1=a;p2=b;
for(i=0;i<7;i++)
if(*(p1+i)==*(p2+i))
printf("%c",*(p1+i));
}
输出结果是
A)gm B)rg C)or D)ga
答案:D
下面程序段的运行结果是 :
char a[ ]="interesting" , *p ;
p=a ;
while (*p!='s') { printf(“%c”,*p-32); p++ ; }
A)INTERESTING B)INTERESter C)INTEREster D)INTERE
答案:D
十四、指针数组
定义:本质是一个数组,存放指针
格式:存储类型 数据类型 *指针数组名[元素的个数]
int *a[3];
有小括号的是数组指针,没有小括号的是指针数组
使用场景
- 存放普通变量的地址
- 可以存放二维数组的地址
- 存放字符串
存放普通变量的地址
int a=10,b=20,c=30;
int *p[3] = {&a,&b,&c};
//访问b的值:*p[1] *(*(p+1))
//访问c的地址:p[2] *(p+2)
存放二维数组的地址
存放二维数组的每一行的首地址
int a[2][3] = {1,2,3,4,5,6};
int *p[2] = {a[0],a[1]};
//访问a[1][2]的值:*(p[1]+2) *(*(p+1)+2)
//访问a[1][2]的地址:p[1]+2 *(p+1)+2
存放字符串
char *p[3] = {"hello","world","hqyj"};
//访问world字符串:%s格式 p[1] *(p+1)
//访问e字符:%c格式 p[0][1] *(p[0]+1)
命令行参数
int main(int argc, char const *argv[])
{
printf("argc=%d\n",argc);
printf("argv[0]=%s argv[1]=%s argv[2]=%s\n",argv[0],argv[1],argv[2]);
return 0;
}
argc:argv数组中元素的个数
argv:指针数组,用来接收命令行中所有参数
练习:
25. int main(int argc, char *argv[])
{
int i, n = 0;
for (i=1; i<argc; i++) { n = 10*n + *argv[i] – ‘0’; }
printf(“%d\n”, n);
}
./aout 12 345 678
输出结果为_________。
A、123 B、136 C、678 D、58
答案:B
*argv[i]是指针数组,相当于二维数组,所以*argv[i]表示二维数组各行的的首地址
十五、函数
概念:一个可以完成某种特定功能的代码模块,可以复用
三要素:
功能:参数:返回值:
格式:
存储类型 数据类型 函数名(参数列表)
{
函数体;//实现功能的代码模块
}
void fun()
{
int a = 10;
}
int main(int argc, char const *argv[])
{
int a = 10;
return 0;
}
定义时有以下几种情况:
- 有返回值,那么返回值的类型要和定义函数时的数据类型相同
- 没有返回值,那么定义函数时需要将数据类型定义成void类型,函数内部可以不写return
- 有参数,需要将参数以定义变量的方式写在函数名的小括号内,多个参数之间以逗号间隔
- 没有参数,参数列表可以直接省略不写
- 定义子函数时,如果将子函数写在主函数的下方,需要在使用之前声明子函数
- 函数必须调用才能执行
声明函数:
存储类型 数据类型 函数名(参数列表);
void fun(char a,int b);
int main(int argc, char const *argv[])
{
fun('A',10);
return 0;
}
void fun(char a,int b)
{
printf("%c %d\n",a,b);
}
声明时在调用前声明即可,可以在主函数里面也可以在主函数外面,定义函数时同理
函数调用
函数名(参数列表);
在函数调用时:如果有返回值可以选择接收返回值(定义一个和返回值相同类型的变量来接受),没有返回值,直接调用函数即可
函数的参数:
实参:就是在函数调用时写在函数名后的小括号内的内容,用来给形参传递数据
形参:就是在定义函数时写在函数名后的小括号内的内容,用来接收实参传递过来的数据
注意:实参和形参的数量是一一对应的,形参定义了几个实参就需要传递几个,并且位置也要对应,数据类型也要相同
void fun(char a,int b)
{
printf("%c %d\n",a,b);
}
int main(int argc, char const *argv[])
{
fun('A',10);
return 0;
}
关于return
- 返回右边的表达式或者变量返回出去
- 阻断代码的执行(当前函数的执行)
练习1:写一个函数实现计算两个数据的和随机两个数据的和(使用返回值的方式)
#include <stdio.h>
int fun(int a,int b)
{
return a + b;
}
int main(int argc, char const *argv[])
{
int c,d;
fun(c,d);
scanf("%d %d",&c,&d);
printf("%d\n",fun(c,d));
return 0;
}
练习2:编写一个函数,函数的2个参数,第一个是一个字符,第二个是一个char *,
返回字符串中该字符的个数。
n(char a, char *p)
{
int i = 0;
while(*p)
{
if (*p == a)
{
i++;
}
p++;
}
return i;
}
int main(int argc, char const *argv[])
{
char s[] = "hello";
printf("%d\n",fun('l',s));
return 0;
}
练习3:返回字符串中某一字符在此字符串中首次出现的位置下标
int fun(char a, char *p)
{
int i = 0;
while(*p != a)
{
p++;
i++;
}
return i;
}
int main(int argc, char const *argv[])
{
char s[] = "hello";
printf("%d\n",fun('o',s));
return 0;
}
函数的传参
- 值传递:
实参向形参传递数据(实参-------->形参),形参的改变不会影响实参的改变,实参的改变不会影响形参的改变
int fun(int a)
{
a++;
printf"%d\n",a)
return 0;
}
int main(int argc, char const *argv[])
{
int b = 10;
fun(b);
b = b + 10;
printf("%d\n",b);
return 0;
}
2.地址传递:
双向的传递,当子函数中形参发生改变,主函数中的实参也会发生改变
利用指针接收地址,通过地址交换里面的内容从而交换a,b的值
正确:
int fun(int *a, int *b)
{
int temp;
temp = *a;
*a = *b;
*b = temp;
printf("fun: a=%d,b=%d\n",*a,*b);
return 0;
}
int main(int argc, char const *argv[])
{
int a = 10;
int b = 20;
fun(&a, &b);
printf("main: a=%d b=%d\n", a, b);
return 0;
}
只交换了指针的指向,没有改变地址里面的内容,所以a,b的值没有发生交换
错误:虽然是地址传递但是本质还是值传递
int fun(int *a, int *b)
{
int *temp = a;
a = b;
b = temp;
printf("fun: a=%d,b=%d\n",*a,*b);
return 0;
}
int main(int argc, char const *argv[])
{
int a = 10;
int b = 20;
fun(&a, &b);
printf("main: a=%d b=%d\n", a, b);
return 0;
}
3.数组传递
和地址传递一样,传递的是数组名
void fun(int *s,int n)
{
int i;
*(s+2) = 100;
printf("fun:");
for (i = 0; i < 3; i++)
{
printf("%d ",s[i]);
}
}
int main(int argc, char const *argv[])
{
int i;
int arr[3] = {1,2,3};
fun(arr,3);
for (i = 0; i < 3; i++)
{
printf("%d ",arr[i]);
}
return 0;
}
栈和堆的区别:
- 栈空间的开辟和释放都是由系统自动处理,而堆空间需要人为的开辟和释放
- 栈空间比较小,堆空间相对大一些
- 栈空间处理数据的速度会比较快,堆空间相对慢一些
- 栈空间开辟内存地址是连续的,但是堆空间的地址分配是随机的
堆空间的开辟
malloc
#include <stdlib.h>
void *malloc(size_t size);
功能:在堆空间开辟内存
参数:size:开辟空间大小的字节数
返回值:任意类型的指针,开辟成功后空间的首地址
成功:开辟成功后空间的首地址
失败:NULL
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
int *p = (int *)malloc(sizeof(int) * 5);
if(p == NULL)
{
printf("开辟失败\n");
}
else
{
printf("开辟成功 %p\n",p);
}
return 0;
}
free
#include <stdlib.h>
void free(void *ptr);
功能:释放堆空间
参数:ptr:需要释放的空间的首地址
返回值:无
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char const *argv[])
{
int *p = (int *)malloc(sizeof(int) * 5);
if(p == NULL)
{
printf("开辟失败\n");
}
else
{
printf("开辟成功 %p\n",p);
}
free(p);
p = NULL;
return 0;
}
防止指针为野指针
注意:
- malloc和free要搭配使用,防止内存泄漏的问题
- 开辟堆空间后不可以对指向堆空间的指针重新赋值
- 使用完堆空间要及时释放
练习:
void fun(char *p)
{
p = (char *)malloc(32);
strcpy(p, "hello");
}
int main()
{
char *m = NULL;
fun(m);
printf("%s\n", m);
return 0;
}
执行结果:段错误
产生原因:因为指针p是一个局部变量,fun执行结束指针p就会被销毁,堆空间丢失,导致内存泄漏
想象的效果:在子函数中开辟空间,在主函数中使用
1.使用返回值的方式
char * fun()
{
char *p = NULL;
p = (char *)malloc(32);
strcpy(p, "hello");
return p;
}
int main()
{
char *m = NULL;
m = fun();
printf("%s\n", m);
return 0;
}
2.传参方式
void fun(char **p)
{
*p = (char *)malloc(32);
strcpy(*p, "hello");
}
int main()
{
char *m = NULL;
fun(&m);
printf("%s\n", m);
return 0;
}
string函数族
strcpy
#include <string.h>
char *strcpy(char *dest, const char *src);
功能:实现字符串的复制
参数:dest:目标字符串的首地址(复制到的地址)
src:源字符串的地址(要复制的字符串的地址)
返回值:完成复制后目标字符串的首地址
是一个字符一个字符的复制,
char dest[] = {"hello"};
char src[] = {"world"};
strcpy(dest,src);
printf("%s",dest);
int main(int argc, char const *argv[])
{
char dest[] = {"hellohqyj"};
char src[] = {"world"};
int i;
strcpy(dest,src);
for (i = 0; i < 9; i++)
{
printf("%d ",dest[i]);
}
return 0;
}
注意:strcpy在使用时,复制会包含\0