C语言基础

大纲

Linux命令

基础部分:进制转换 词法符号 变量常量 输入输出的方式 分支语句 循环语句 循环控制语句

数组: 一维数组 二维数组 字符数组 排序

指针: 一级指针 二级指针 指针数组 数组指针 指针和数组

函数:函数的基本使用 string函数族群 开辟堆区空间 递归函数

结构体:结构体变量 结构体数组 结构体指针

共用体

枚举

一、linux命令

1.打开终端的方式

    1. 可以直接用鼠标点击
    2. Ctrl Alt t 新建一个终端窗口,默认路径是家目录
    3. Ctrl Shift n 路径是当前路径

2.关闭终端方式

    1. 直接点击叉号
    2. Ctrl d
    3. 输入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编辑器

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.链接

  1. 预处理

命令:gcc -E hello.c -o hello.i

删除文件中的注释 展开头文件 替换宏定义

  1. 编译

命令:gcc -S hello.i -o hello.s

检查语法错误 有错报错 没有错误生成汇编语言

  1. 汇编 

命令:gcc -c hello.s -o hello.o

将汇编语言转化成二进制的文件

  1. 连接

命令: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——》2+ 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

存储位置

栈区

全局区

生命周期

和定义函数共存亡

和整个程序共存亡

  1. 数据类型

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)/ 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循环 外层循环执行一次,内层循环就要走完全部

  1. 外层循环控制行数 ,内层循环控制列数

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)
54进行比较---》交换位置---45321
53进行比较---》交换位置---43521
52进行比较---》交换位置---43251
51进行比较---》交换位置---43215
比较了4(数组元素的个数-1)次,j=0-3  j<4-0,
第二轮:i=1
43进行比较---》交换位置----34215
42进行比较---》交换位置----32415
41进行比较---》交换位置----32145
比较了三次,j=0-2,j<4-1
第三轮:i=2
32进行比较---》交换位置----23145
31进行比较---》交换位置----21345
比较了二次,j=0-1,j<4-2
第四轮:i=3
21进行比较---》交换位置----12345
比较了一次,j=0,j<4-3  


int a[5] = {5,4,3,2,1};
int i,j,temp;
for (= 0; i < 5-1; i++)
{
   for (= 0; j < 5-1-i; j++)
   {
       if(a[j] > a[j+1])
       {
            temp = a[j];
            a[j] = a[+ 1];
            a[+ 1] = temp;
       }
   }
       
}
for (= 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 (= 0; i < 5-1; i++)//轮数
{
    k = i;//默认最小值是第i+1轮的第一个数
    for (= i+1; j < 5; j++)//比较次数
    {
        if (a[k] > a[j])
        {
            k = j;
        }
    }
    if (!= i)
    {
        temp = a[i];
        a[i] = a[k];
        a[k] = temp;
    }
}
for (= 0; i < 5; i++)
{
    printf("%d ",a[i]);
}
printf("\n");

二维数组

格式:存储类型 数据类型  数组名[行数][列数]

int arr[2][3];//定义了一个两行三列的二维数组
int arr[2][3] = {123456};

行下标        列下标

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] = {123}{456};按照行进行赋值

 部分初始化
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 *= &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 *= &a;
float *= p;

指针的运算

算术运算+ - ++ --

char a[] = "hello";
char *= a;//0x100
+ 1;//0x101:可以使地址向后移动(向高地址位置进行移动)移动1个字节
int  a[] = {1,2,3}
int *= a;//0x100
+ 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 *= a;
printf("%d",((p+2)-p));

关系运算> < == !=

指针之间比较大小始终指向高地址的指针大,

int a[] = {5,4,3};
int *= a;
int *= p+2;
if

注意:指针之间的比较时必须是同一区域的比较(同一个数组之间进行比较),非同一区域的比较是没有意义的

练习:

char s[32] = "hello";
char *= 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个字节

总结:

  1. 指针的大小取决于操作系统,32位的操作系统就是4个字节
  2. 内存地址是固定的,但是变量的地址是不固定的,栈区的地址是随机分配的
  3. 指针的数据类型是他指向的空间的数据类型决定

段错误

Segmentation fault (core dumped)

段错误 (核心已转储)

  1. 野指针

定义了一个指针没有初始化,会产生野指针

int *p;
scanf("%d",p);
printf("%d",*p);

解决:给指针进行赋值

int *= NULL;
scanf("%d",p);
printf("%d",*p);

指针p 释放了(free),没有赋值为NULL

解决:p = NULL;

  1. 内存泄漏

对非法空间进行操作

char *= "hello";//hello存放在常量区,不可被修改,可以被访问
scanf("%s",p);
printf("%s",*p);

解决:

char s[10] = {"hello"};
char *= 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 *= s;
    int num = 0,i;
    for (= 0; i < strlen(s); i++)
    {
        num = (num*10)+ *- 48;
        p++;
    }
    printf("%d\n",num);
    return 0;
}

#include <stdio.h>

int main(int argc, char const *argv[])
{
    char s[10] = {"12345"};
    char *= s;
    int num = 0;
    while(*p)//*p!='\0'
    {
        num =(num*10) + *- 48;
        p++;
    }
    printf("%d",num);
    return 0;
}

指针修饰

const常量化

只要被const修饰的内容就是一个只读,不可以被修改

  1. .修饰变量

int = 10;

a = 20;

const int a = 10;//a就不能被修改

或者

int const a = 10;

a不可以修改,但是可以通过间接修改的方式修改掉a的值,通过指针修改

  1. 修饰指针

2.1修饰指针*p

const int *p;//修饰p不可以被修改

2.2修饰指针p

int *const p = &a;指针的指向不能被修改

const int * const p = &a;//修饰指针p和*p

void空

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 *= &a;
int **= &p;
访问a的值:a ***q
访问a的地址:&a p *q

指针和数组

直接访问:按照变量的地址去访问变量的值(通过数组名去访问)

间接访问:通过存放变量的地址的变量去访问变量(通过指针去访问)

指针和一维数组

int a[5] = {1,2,3,4,5};
int *= 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];

有小括号的是数组指针,没有小括号的是指针数组

使用场景

  1. 存放普通变量的地址
  2. 可以存放二维数组的地址
  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*+ *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;
}
​​​​​​​

定义时有以下几种情况:

  1. 有返回值,那么返回值的类型要和定义函数时的数据类型相同
  2. 没有返回值,那么定义函数时需要将数据类型定义成void类型,函数内部可以不写return
  3. 有参数,需要将参数以定义变量的方式写在函数名的小括号内,多个参数之间以逗号间隔
  4. 没有参数,参数列表可以直接省略不写
  5. 定义子函数时,如果将子函数写在主函数的下方,需要在使用之前声明子函数
  6. 函数必须调用才能执行

声明函数:

存储类型  数据类型  函数名(参数列表);

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. 返回右边的表达式或者变量返回出去
  2. 阻断代码的执行(当前函数的执行)

练习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 (*== 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(*!= a)
    {
        p++;
        i++;        
    }
    return i;
}

int main(int argc, char const *argv[])
{
    char s[] = "hello";
    printf("%d\n",fun('o',s));
    return 0;
}

函数的传参

  1. 值传递:

实参向形参传递数据(实参-------->形参),形参的改变不会影响实参的改变,实参的改变不会影响形参的改变

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;
    *= *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 (= 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 (= 0; i < 3; i++)
    {
        printf("%d ",arr[i]);
    }
    return 0;
}

栈和堆的区别:

  1. 栈空间的开辟和释放都是由系统自动处理,而堆空间需要人为的开辟和释放
  2. 栈空间比较小,堆空间相对大一些
  3. 栈空间处理数据的速度会比较快,堆空间相对慢一些
  4. 栈空间开辟内存地址是连续的,但是堆空间的地址分配是随机的

堆空间的开辟

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 *= (int *)malloc(sizeof(int) * 5);
    if(== 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 *= (int *)malloc(sizeof(int) * 5);
    if(== NULL)
    {
        printf("开辟失败\n");
    }
    else
    {
        printf("开辟成功 %p\n",p);
    }
    free(p);
    = NULL;
    return 0;
}

防止指针为野指针

注意:
  1. malloc和free要搭配使用,防止内存泄漏的问题
  2. 开辟堆空间后不可以对指向堆空间的指针重新赋值
  3. 使用完堆空间要及时释放

练习:

void fun(char *p) 
{
= (char *)malloc(32);
	strcpy(p, "hello");	
}
int main()
{
    char *= NULL;
    fun(m);
    printf("%s\n", m);
    return 0
}
执行结果:段错误
产生原因:因为指针p是一个局部变量,fun执行结束指针p就会被销毁,堆空间丢失,导致内存泄漏
想象的效果:在子函数中开辟空间,在主函数中使用

1.使用返回值的方式
char * fun() 
{
    char *= NULL;
    p = (char *)malloc(32);
    strcpy(p, "hello");	
    return p;
}
int main()
{
    char *= NULL;
    m = fun();
    printf("%s\n", m);
    return 0
}

2.传参方式
void fun(char **p) 
{
	*= (char *)malloc(32);
	strcpy(*p, "hello");	
}
int main()
{
    char *= 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 (= 0; i < 9; i++)
    {
        printf("%d ",dest[i]);
    }
    return 0;
}
​​​​​​​

注意:strcpy在使用时,复制会包含\0

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值