C语言-指针

0.引入

int a; //定义了一个整型变量 名为a

a = 100; // a作为左值, 把100存放到a所对应的存储单元中

int b = a; // a作为右值, 取a所对应的存储单元(变量本身)的值, 然后再把这个值存放到变量b对应的空间中

在C语言中, 任何一个变量名 都有两层含义:

左值lvalue : 代表变量所对应的那块地址空间

右值rvalue : 代表这个变量的值本身(存储单元的值)

对于变量的访问 也是有两种情况:

把一个值保存到这个变量的地址空间中 write 写

从一个变量的地址空间中去取值 read 读

在定义变量的时候, 系统已经把 变量名 和 变量对应的地址 相关联起来了

因此 普通的代码中 直接通过变量名 来对变量进行访问

但是 系统实际上是通过地址来访问变量的

如果我知道了一个变量的地址

是不是可以通过该变量的地址 来访问这个变量呢?

可以的

如何通过一个对象的地址 来访问这个变量呢?

指针 pointer

1.变量的访问方式

1)直接访问

直接通过变量名来访问

缺陷: 受作用域的限制

例子:

int func( int a )

{

在func函数中 能否通过变量名a访问到main()函数中的a呢?

不能

}

int main()

{

int a = 100;

func( a );

}

2)间接访问

通过对象的地址来访问

高效, 不受作用域的限制

2.地址与指针

地址:

系统把内存 以字节为单位 划分成许多份 进行编号, 这个编号就是内存单元的地址

(无符号32位整数)

在C语言中, 指针和地址的概念是差不多的, 可以认为指针就是有类型的地址

一个对象的首地址, 称之为该对象的 "指针"

它标志着该对象的内容是从哪开始的, 我们知道了这个对象的首地址, 就能够访问到该对象的地址空间

3.指针变量

指针变量也是一个变量, 它是用来保存一个对象的地址

例子:

int a = 10; //a是一个整型变量, 保存的值是一个整数 10

而指针变量 保存的是地址

指针变量的定义语法:

数据类型 * 指针变量名;

数据类型 * 指针变量名 = 初始值;

"数据类型" : 表示指针指向的对象的类型

"指向": 比如 我 指向了 你, 意思就是 我 保存了 你的地址

"指针变量名" : 符合C语言合法标识符的定义规则

一般建议, 我们定义指针的时候就给它初始化 (或者 说在使用指针之前 一定要给它赋值 )

要注意赋值符号两边的类型要一致

例子:

int * p;

4.如何获取地址?

  1. 取地址符 &

语法:

&对象名 //表示取该对象的地址 (变量,数组元素等)

例子:

int a = 10;

int * p = &a; //取变量a的地址, 赋值给指针变量p 进行初始化

  1. 有些对象的名字 本身就代表地址

数组名 : 数组名就是数组的首地址, 也是数组首个元素的地址

函数名

...

注意:

1)这两种方法获取到的地址 都是有类型的

2)在32位操作系统下, 地址都是4个字节的(32位), 即我们分配给指针变量的空间大小都是4个字节(32位)

所以 把 指针变量 强制转换成 其他类型的指针变量 它们的值是不会失真

(在64位操作系统下, 指针都是占8个字节)

例子:

int a = 10;

int *p = &a;

char * s;

s = (char *)p;

printf("p = %p\n", p );

printf("s = %p\n", s );

printf("sizeof(p) = %ld\n", sizeof(p) ); // 8 在64位操作系统下

printf("sizeof(s) = %ld\n", sizeof(s) ); // 8

==================

%p : 以十六进制的形式打印指针变量的值

☆☆☆ 3)指针变量的类型只决定 该指针变量的变化 和 实际地址变化 之间的倍率

例子:

int a = 5; //假设a的地址为 0x1000

int * p = &a;

char *s = (char *)p;

printf("%p\n", p ); // 0x1000

printf("%p\n", p+1 ); // 0x1004

printf("%p\n", s ); // 0x1000

printf("%p\n", s+1 ); // 0x1001

p+1 : p指向的是int类型的空间, +1之后 指向了下一个int类型的空间

s+1 : s指向的是char类型的空间, +1之后 指向下一个char类型的空间

总结: ☆☆☆

p+i (p表示指针, i表示整数)

指针和整数做加减运算时, 不是简单的加减数值, 而是加减i个指向单元的长度

例子: p和s的定义同上

printf("%p\n", p-1 ); // 地址变化 -4

printf("%p\n", s-1 ); // 地址变化 -1

printf("%p\n", p+3 ); // 地址变化 +12

printf("%p\n", s+3 ); // 地址变化 +3

注意:

指针和指针相加,是毫无意义的

int *p = &a;

int *q = &b;

p+q --> 未知的,无意义的

5.如何访问 指针 指向的空间呢? ☆☆☆

指向符 * (解引用)

语法:

*地址 //访问该地址对应的那块空间

即 *地址 , 既可以作为左值, 也可以作为右值

左值: 表示地址代表的那块空间

右值: 表示地址所代表的那块空间里面的值

例子:

int a = 10;

int *p = &a;

*p = 5; // 左值, ==> a --> 5

int b = *p; // 右值, ==> b --> 5

注意:

指向符* 和 取地址& 它们之间有什么关系?

由上面的例子 得出

*p = 5; <==> a = 5;

而 p = &a;

==> *p = 5;

==> *(&a) = 5; <==> a = 5;

==> *(&a) <==> a

* 和 & 可以直接约掉的

  1. 野指针 和 空指针

1)野指针

是指向一个"未知的"地址的指针

例子:

int a; // 变量a的值 是未知的,不确定的

int *p ; // 指针变量p的值也是未知的, 是一个 野指针

定义一个变量, 系统就会开辟一块内存空间, 不管有没有对它赋值,这个变量都是有值的,只不过这个值是未知的

如果我们在定义指针变量的时候,没有初始化这个指针,那么它就指向一个未知的地址

就是一个野指针

使用野指针会有风险的

如果 *p ---> *(未知的地址) --> 访问一块未知的空间

如果这个地址是你不能访问的(绝大多数情况下 都是不能访问的 )

那么运行的时候 就会引发 "段错误" "核心已转储" (非法访问内存)

例子:

int *p; //野指针

*p = 6; // error

int a;

p = &a; //OK

*p = 6; //OK --> a = 6;

编程建议:

野指针在程序中的 危害是很大( 会造成BUG, 而且这个BUG很难找)

所以 建议大家 在定义指针的时候 一定要记得初始化(在访问指针之前,一定要给它赋值)

================================

调试代码: 找错误(BUG)

1)语法错误

编译的时候,会检查语法错误, 例如: 少了分号, 少量括号, 中文符号 '\346' ...

解决方法: 根据报错提示 找到对应代码的位置 找出错误并修改

2)逻辑错误

要运行后,要根据运行的结果才能得出是否有逻辑错误

解决方法:

(2.1)找出错误的位置

打印相关的提示信息 : 一些数据的值(变量), 还可以 打印行号,函数名,文件名

//打印行号,函数名,文件名

printf("line = %d, func = %s, file = %s\n", LINE, FUNCTION, FILE );

(2.2)分析出错的原因并修改

printf("line = %d \n", LINE);

A

printf("line = %d \n", LINE);

B

printf("line = %d \n", LINE);

C

printf("line = %d \n", LINE);

D

printf("line = %d \n", LINE);

  1. 空指针 NULL

指向不存在的地址(NULL), 就是一个空指针

例子:

int *p = NULL; //空指针

*p = 6; //error

if( p == NULL )

{

p = &a;

}

注意:

如果访问空指针, 也会造成"段错误"

因为 这个NULL是不可访问的, 那么对内存的非法访问,就会造成段错误

7.一维数组 和 指针

数组元素和普通变量是一样的,也是有自己的地址, 并且数组元素之间的地址是连续的

数组名就是数组的首地址, 即数组首个元素的地址(即a[0]的地址), 是常量, 其值不能被改变

例子:

int a[5];

int *p = &a[0];

请问有哪些方法 把10赋值给a[0]?

a[0] = 10;

*p = 10; // *(&a[0]) --> a[0]

*a = 10; // *a <==> *(&a[0]) --> a[0]

//数组名a就是数组的首地址, 即数组首个元素的地址(即a[0]的地址)

printf("a = %p\n", a );

printf("&a[0] = %p\n", &a[0] );

数组名a 和 数组首个元素a[0]的地址 是一样的

可以得出

a <==> &a[0]

--> *a <==> *(&a[0])

--> *a <==> a[0]

  1. 表达式 *(a+1) 的含义是什么?

*(a+1) ==> *( &a[0] + 1 )

p+i : 指针和整数做加减运算时, 是加减i个指向单元的长度

且数组元素的地址是连续的, a[0]的下一个是a[1], a[1]的下一个是a[2], ...

因此

*(a+1) ==> *( &a[0] + 1 )

==> *( &a[1] )

==> a[1]

☆☆☆ 结论:

*(a+i) <==> a[i] (i>=0)

练习:

int a[10];

int *p = &a[2];

请问表达式p[2] 表示什么含义?

p[2] ==> *( p + 2 )

==> *( &a[2] + 2 )

==> *( &a[4] )

==> a[4]

char a[3] = {'a', 'b', 'c' };

printf("%ld %ld %c\n", sizeof(a), sizeof(a+1), *(a+1) ); // 3 4或8 b

sizeof(a) 求整个数组a所占的字节大小?

3

sizeof(a+1) 此时a代表这个数组的首地址

a+1 ==> &a[0] + 1 ==> &a[1]

在32位操作系统下 4

在64位操作系统下 8

*(a+1) ==> a[1] ==> b

=====================================

数组名 在C语言中 有2层含义:

(1)代表整个数组

sizeof(a)

&a + 1

typeof(a)

(2)在合适的情况下,代表指针 数组的首地址, 即数组首个元素的地址

a

a+1

p = a

strlen(a)

练习:

int a[10] = {0,1,2,3,4,5,6,7,8,9}; //假设a[0]的地址为 0x0000

printf("%p, %p, %p\n", a, a+1, &a+1 );

a 代表指针 ==> &a[0] ==> 0x0000

a+1 a代表指针 &a[0]

a+1 ==> &a[0]+1 ==> &a[1] ==> 0x0004

&a+1 a代表整个数组

a的类型是 int [10]

a[0]的类型是 int

&a[0]的类型是 int *

&a的类型是 int [10] *

&a可以看做是一个指向10个int类型元素的指针

&a+1 就是指向下一个 10个int类型元素的指针, 地址偏移了40

==> 0x0028

&(a+1) 代表什么含义?

&(a+1) ==> &( &a[0] + 1 ) ==> &( &a[1] ) ==> 毫无意义

表达式&a[1]的值是一个无符号的32位的整数(a[1]的地址)

这个整数(地址编号), 没有内存空间来保存的

既然没有内存空间, 意味着没有地址, 没有地址就不能取地址

8.二维数组 与 指针

int a[3][4];

二维数组a 可以看做成 有3个元素 每一个元素都是 int [4]类型的 一维数组

a[0] _ _ _ _

a[1] _ _ _ _

a[2] _ _ _ _

因此

*(a+i) <==> a[i] (i>=0)

a[i]指的是该一维数组的第i个元素, 同时也可以表示为第i行的首地址, 即a[i][0]的地址

☆☆☆ 结论:

*( *(a+i) + j ) <==> a[i][j] (i>=0, j>=0)

推导:

*( *(a+i) + j ) ==> *( a[i] + j )

==> *( &a[i][0] + j )

==> *( &a[i][j] )

==> a[i][j]

二维数组 与 指针 的关系

int a[3][4]

=========================================================================

表达式(i>=0, j>=0) 类型 含义

a+i --> &a[0]+i --> &a[i] int [4] * 第i行的地址

*(a+i)+j --> &a[i][j] int * 第i行第j列的那个元素的地址 即&a[i][j]

&a[i][j] 同上

*(*(a+i)+j) --> a[i][j] int 第i行第j列的那个元素


表达式(i>=0, j>=0) 含义 值

a 代表整个数组

sizeof(a), &a, typeof(a)

代表指针,数组的首地址,即数组首个元素的地址 &a[0]

&a[0]

a[0] 代表整个数组

sizeof(a[0]), typeof(a[0]), &a[0]

代表指针,数组的第一行第一列元素的地址 &a[0][0]

&a[0][0]

a[0][0] 数组的第一行第一列元素 a[0][0]


a+1 a代表指针 --> &a[0]+1 --> &a[1] &a[1]

数组第二行的首地址

typeof(a+1) --> typeof(&a[1]) --> int [4] *

&a[1] 同上

&a+1 a代表整个数组

typeof(a) --> int [3][4]

typeof(&a) --> int [3][4] *

&a+1 : 指向下一个数组a类型元素的指针


a[1]+2 --> &a[1][0]+2 --> &a[1][2] 数组第二行第三列的那个元素的地址

*(a+1)+2 --> a[1]+2 --> &a[1][2]

&a[1][2] 同上


*(a[1]+2) --> *( &a[1][0] + 2 ) --> *( &a[1][2] ) --> a[1][2] 数组第二行第三列的那个元素

*(*(a+1)+2) --> a[1][2]

a[1][2] 同上

9.指针数组 和 数组指针

数组指针 --> 指针

本质是一个指针, 它指向一个数组

指针数组 --> 数组

本质是一个数组, 里面的元素都是指针

  1. 数组指针

int a[4];

a的类型是 int [4]

int (*p) [4]; // int [4] * p 由于 []中括号的优先级 高于 *指向符

p是一个指针, 指向了 int [4]

p+1 地址变化了 16

p = &a;

char (*q)[3];

q是一个数组指针, 指向了 char [3]

q+1 地址变化了 3

  1. 指针数组

int * p[4]; // int * (p[4]); //[]中括号的优先级 高于 *指向符

p是一个数组名, 里面有4个元素,且每一个元素的类型都是 int * 类型

10.指针变量作为函数的参数

在C语言中, 函数参数的传递只有一种情况: 值传递 "传递调用"

形参 = 实参的值; //把实参的值赋值给形参

例子:

//形不改实

void exchange( int a, int b )

{

int temp = a;

a = b;

b = temp;

}

int main()

{

int a = 5, b = 6;

exchange( a, b ); //形不改实, 并没有交换实参的值, 实参只是把值传递给了的形参

printf("a = %d, b = %d\n", a, b ); // 5 6

}

===> 解决方法

void swap( int *p, int *q )

{

int temp;

temp = *p;

*p = *q;

*q = temp;

}

int main()

{

int a = 5, b = 6;

swap( &a, &b );

printf("a = %d, b = %d\n", a, b ); // 6 5

}

int a[5] = {1,2,3,4,5};

int *p = (int *)(&a+1);

printf("%d %d\n", *(a+1), *(p-1) ); // 2 5

*(a+1) ==> a[1] ==> 2

*(p-1) :

&a+1 : a代表整个数组

a的类型是 int [5]

&a的类型是 int [5] *

&a+1 指向下一个int [5]类型的空间, 地址偏移了20

p = &a+1 , 那么p就执行了这个数组的最后面

a[0] a[1] a[2] a[3] a[4]

↑ ↑ ↑

&a ↑ &a+1

p-1 p

p的类型是 int *

p-1 地址就往前偏移了4 , 即 &a[4]

*(p-1) ==> *( &a[4] ) ==> a[4] ==> 5

char b[2][3] = {'1', '2', '3', '4', '5', '\0' };

printf("%ld, %ld, %s\n", sizeof(b[1]), sizeof(b[1]+1), *(b+1) );

char b[2][3] 可以看做成一个一维数组, 里面有2个元素

b[0] _ _ _

b[1] _ _ _

sizeof(b[1]) --> b[1]代表整个数组, 占 3个字节 --> 3

sizeof(b[1]+1) --> sizeof( &b[1][0]+1 )

--> sizeof( &b[1][1] )

--> 在32位操作系统下 4个字节

在64位操作系统下 8个字节

*(b+1) --> b[1] 也是第二行的首地址

--> 以s%输出

--> 45

int b[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11}; //假设b[0][0]的地址为0x0000

printf("%p, %p, %p, %p \n", b, b+1, &b[0]+1, &b+1 );

&b[0][0] --> 0x0000

b : b代表指针, 数组的首地址, 即数组首个元素的地址

--> 0x0000

b+1 : b代表指针, 数组的首地址, 即数组首个元素的地址

--> &b[0] + 1

b[0]的类型是 int [4]

&b[0]的类型是 int [4] *

&b[0]+1 指向了下一个int [4]类型的空间, 地址偏移了16

--> 0x0010

&b[0]+1 : 同上 --> 0x0010

&b+1 : b代表整个数组

b的类型是 int [3][4]

&b的类型是 int [3][4] *

&b+1 指向下一个int [3][4]类型的空间, 地址偏移了48

--> 0x0030

11.字符串指针

字符 char

char a = '1'; // 49

字符串

由0个或者多个字符组成的

规则:

在C语言中,没有字符串类型, C语言中的字符串是通过 char * (字符指针)来实现的

在C语言中的字符串, 用双引号""引起来的一串字符来表示, 并且字符的后面会默认添加一个'\0' (ASCII码值为0)

这个'\0' 我们称之为字符串的结束符(终止符)

我们保存字符串 只需要保存其首地址即可, 可以通过首地址 一个一个的往下找, 直到遇到'\0'为止

期间遇到的所有字符 都是该字符串的内容

所以 字符串也就是在连续的地址依次存放每一个字符串

例子:

char *p = "12345";

编译器在编译时, 会把字符串"12345"的首地址 赋值给指针变量p

注意: 字符串 是保存在内存中 .rodata (只读区域) 中 read - only

typeof('a') --> char

typeof("abcd") --> const char * (const代表只读)

练习:

char *p = "abcde";

*p = 'A'; //error 字符串"abcde"是只读的,不能被修改, 会报错

*(p+1) = 'B'; // error

printf("%s\n", p );

p = "12345"; // OK , p是一个指针变量, 它本身是可以改变的, 可以指向其他地方

// 但是 p指向的内容是字符串,该字符串是不能被修改的

char s[] = {"12345"};

*(s+2) = 'A'; // OK, *(s+2) ==> s[2] 引用数组元素和引用普通变量是一样的

printf("%s\n", s ); //12A45

s[1] = 'B'; //OK

s = "abcde"; // error , s是数组名, 其值是不能被改变的

☆☆☆

总结:

char *p = "hello"; // p是一个指针

char s[] = "hello"; // s是一个数组

指针常量:

数组名s, 是数组的首地址, s的值本身是不能被修改的

数组s里面的元素(内容)是可以改变的

s始终指向同一个位置, 但是s指向的空间里面的内容是可以改变的

指针常量 --> 常量

int * const q = &a;

*q = 30; //OK p指向的内容是可以改变的

printf("a = %d\n", a );

int b;

//q = &b; //error , 但是p本身是不能被改变的

指向常量的指针常量

const int c = 10;

const int * const r = &c;

//r = &a; // error , r本身是不能被改变的

//*r = 55; // error , r指向的内容也是不能改变的

常量指针:

指针p本身是一个变量, 指针p本身是可以改变的,

指针p可以指向其他地方, 但是 p指向的空间里面的内容是不能被修改的

常量指针 --> 指针

//const int *p ;

int const *p = NULL;

int a = 10;

p = &a; //OK p本身是可以改变的

//*p = 20; //error , 但是p指向的内容是不能改变的

printf("a = %d\n", a );

记忆点: 看const所修饰的对象是谁,谁就只读(不能被改变)

  1. 关于字符串相关的处理函数

(1)strlen()

NAME

strlen - calculate the length of a string

SYNOPSIS

#include <string.h>

size_t strlen(const char *s);

功能: 求字符串的长度, 直到遇到'\0', 且不计算'\0'

参数:

s : 所求的字符串的首地址

返回值:

返回该字符串的长度

练习:

设计一个函数, 实现 strlen()的功能

int my_strlen( char *s )

{

if( s == NULL )

{

return 0;

}

/*

//指针

int len = 0;

while( *s != '\0' )

{

len++;

s++;

}

return len;

*/

//数组

int len = 0;

int i = 0;

while( s[i] != '\0' ) // *(s+i) != '\0'

{

len++;

i++;

}

return len;

}

(2) strcpy()

NAME

strcpy, strncpy - copy a string

SYNOPSIS

#include <string.h>

char *strcpy(char *dest, const char *src); // '\0'也会复制

char *strncpy(char *dest, const char *src, size_t n);

功能: 把src字符串 拷贝到 dest指向的空间中

参数:

dest: 目标地址

src: 源字符串

n : 指定要复制的字符个数

返回值:

将dest字符串返回

练习:

写一个函数,实现strcpy()的功能

char * my_strcpy( char *dest, char *src )

{

/*

//指针

char *p = dest;

while( *src != '\0' )

{

*p = *src;

p++;

src++;

}

*p = '\0';

return dest;

*/

//数组

int i=0;

/*

while( src[i] != '\0' )

{

dest[i] = src[i];

i++;

}

dest[i] = '\0';

*/

while( dest[i] = src[i] ) //赋值表达式

{

i++;

}

return dest;

}

char * my_strncpy(char *dest, char *src, int n)

{

int i;

for( i=0; i<n && src[i] != '\0'; i++ )

{

dest[i] = src[i];

}

for( ; i<n; i++ ) //n大于src的长度, 剩下的全部复制为\0

{

dest[i] = '\0';

}

return dest;

}

(3) strcmp()

NAME

strcmp, strncmp - compare two strings

SYNOPSIS

#include <string.h>

int strcmp(const char *s1, const char *s2);

int strncmp(const char *s1, const char *s2, size_t n);

功能: 比较两个字符串

参数:

s1 s2 : 两个字符串

n : 指定要比较的字符个数

返回值:

0 表示两个字符串相等

非0 不相等 返回不相等的那两个字符差值 ( *s1 - *s2 )

>0 s1 > s2

<0 s1 < s2

#include <stdio.h>
#include <string.h>

int my_strcmp( char *s1, char *s2)
{
    //数组 
    int i;
    for( i=0; s1[i] == s2[i] ; i++ )
    {
        if( s1[i] == '\0' )
        {
            return 0;
        }
    }
    return s1[i] - s2[i];

    /*
        //指针 
        for( ; *s1 == *s2 ; s1++, s2++ )
        {
            if( *s1 == '\0' )
            {
                return 0;
            }
        }
        return *s1 - *s2;
    */
}

int main()
{
	char *s1 = "abcdefg";
	char *s2 = "abcdabc";
	
	printf("%d\n", strcmp(s1, s1 ) );	// 0
	printf("%d\n", strcmp(s1, s2 ) ); 	// 4 
	printf("%d\n", strcmp(s2, s1 ) );	// -4
	printf("%d\n", strncmp(s1, s2 , 4) );


	//判断后缀名
	char *p = "01.mp3";
	char *q = "0123456.mp3";
	if( strcmp( q + ( strlen(q)-4 ) , ".mp3" ) == 0 )
	{
		printf("Yes \n\n");
	}

	//自定义接口 
	printf("%d\n", my_strcmp(s1, s1 ) );	// 0
	printf("%d\n", my_strcmp(s1, s2 ) ); 	// 4 
	printf("%d\n", my_strcmp(s2, s1 ) );	// -4
	
}

(4) strcat()

NAME

strcat, strncat - concatenate two strings

SYNOPSIS

#include <string.h>

char *strcat(char *dest, const char *src);

char *strncat(char *dest, const char *src, size_t n);

功能: 把src字符串 拼接到 dest字符串的末尾

参数:

dest : 目标字符串

src : 源字符串

n : 指定要拼接的字符个数

返回值:

返回dest字符串

例子:

char * s1 = "abcdefg";

char s2[32] = {"123456789"};

strcat( s2, s1 );

printf("s2 = %s\n", s2 ); //123456789abcdefg

char s3[32] = {"zxcvbnm"};

strncat( s3, s1, 5 );

printf("s3 = %s\n", s3 ); //zxcvbnmabcde

#include <stdio.h>
#include <string.h>

char * my_strcat(char *dest, char *src)
{
    char *p = dest;     //保存字符串的首地址
    while( *dest )      //让dest往后走到末尾位置 即'\0'
    {
        dest++;
    }

    while( *dest = *src )
    {
        dest++;
        src++;
    }
    return p;   
}

int main()
{
	char * s1 = "abcdefg";
	char s2[32] = {"123456789"};

	strcat( s2, s1 );
	printf("s2 = %s\n", s2 );

	char s3[32] = {"zxcvbnm"};
	strncat( s3, s1, 5 );
	printf("s3 = %s\n", s3 );


	char s4[] = "123abc456efg789456";
	char s5[] = "456";

	char *p = strstr( s4, s5 );
	if( p != NULL )
	{
		printf("p = %s\n", p );
	}
	else 
	{
		printf("No \n");
	}

	//自定义的接口 

	char s6[32] = {"12345"};
	my_strcat( s6, s1 );
	printf("\ns6 = %s\n", s6 );
}

(5) strstr()

NAME

strstr - locate a substring

SYNOPSIS

#include <string.h>

char *strstr(const char *haystack, const char *needle);

功能: 定位子字符串的位置 (判断needle是否为haystack的子字符串)

参数:

haystack , needle , 两个字符串

返回值:

成功, 返回 子字符串所在的位置(地址) (所找到的第一个子字符串)

失败, 返回NULL

例子:

char s4[] = "123abc456efg789456";

char s5[] = "456";

char *p = strstr( s4, s5 );

if( p != NULL )

{

printf("p = %s\n", p ); //456efg789456

}

else

{

printf("No \n");

}

12.函数指针 和 指针函数

1)函数指针

在C语言中, 不仅变量有地址, 数组有地址, 函数也是有地址的

用一个指针变量来保存函数的地址, 那么这个指针该怎么去定义?

(1)定义语法

指针的定义语法:

指向的类型 * 指针变量名;

例子:

int sum( int a, int b )

{

return a+b;

}

函数的类型:

typeof( sum ) --> int (int, int)

意味着 这个函数返回值为int类型,且带有两个int类型的参数

那么, 定义一个指针变量来保存这个函数的地址 该如何定义呢?

int (int, int) * p;

==>

int (*p) (int,int); //p就是一个函数指针,

//指向的这个函数是 有一个int类型的返回值,且带有两个int类型参数的函数

(2)初始化函数指针

p = 函数的地址;

--> p = &函数名; 或者 p = 函数名;

例如:

p = &sum; 或者 p = sum;

(3)如何通过函数指针来调用函数?

平时调用函数:

函数名(实参); ==> sum( 1, 2);

通过函数指针来调用函数:

函数指针(实参); ==> p( 1 ,2 ); // p = sum;

(*函数指针)(实参) ==> (*p)( 1,2 ); // p = &sum;

例子:

int sum( int a, int b )

{

return a+b;

}

int main()

{

int (*p) (int, int);

int (*q) (int, int);

p = sum; // ok

q = &sum; // ok

printf("sum = %d\n", sum(1,2) ); //函数名 直接调用

//通过函数指针来调用函数

printf("p = %d\n", p(1,2) );

printf("*q = %d\n", (*q)(1,2) );

}

(4)函数指针的应用

a)线程池 (二阶段-并发)

b)回调函数

==========================================

回调函数:

就是一个被作为参数传递的函数

可以把一个函数的指针 作为参数 传递给另外一个函数

以便于该函数在处理相应的事件时, 可以灵活的使用不同的方法

示例:

一般的操作系统都实现了中断机制 // 中断: 打断CPU指令执行顺序的事件

但是 用户不能直接定义中断处理函数的

只能在中断处理函数里面 再调用 用户自定义的回调函数

例子:

#include <stdio.h>

void (*call_back)(void); //函数指针

void exit_isr(void) //外部中断处理函数, 由操作系统实现

{

//...

call_back();

//...

}

void abc(void) //用户自定义的处理函数1

{

printf("hello world\n");

}

void xyz(void) //用户自定义的处理函数2

{

printf("hahahahah\n");

}

int main()

{

call_back = abc;

exit_isr();

call_back = xyz;

exit_isr();

}

(5)函数指针数组

int (*p[10]) (int,int);

//定义了一个函数指针数组, 数组名为p, 里面有10个元素,且每一个元素都是函数指针

// 指向了一个返回值为int,且带有两个int类型参数的函数

p[0]的类型 int (int, int) *

p[1] ...

  1. 指针函数

int * p (int , int ); //指针函数 (返回值为指针类型的普通的函数)

语法:

指针函数的返回值类型 * 指针函数名(参数列表);

13.main函数带参数

语法:

int main( int argc, char *argv[] )

{

}

argc : 是一个int类型, 代表命令行参数的个数 ( ./a.out也算一个)

argv : 是一个字符串数组

argv[0] 保存的就是程序的第一个参数 , 即 ./xxx (例如: ./a.out )

argv[1] 保存的是程序的第二个参数, 即 你实际输入的第一个参数

argv[2]

...

argv[0]保存的是 启动该程序的程序名, 比如: ./a.out

因此 argc的值至少为1

例子:

int main( int argc, char *argv[] )

{

printf("argc = %d\n", argc );

int i;

for( i=0; i<argc; i++ )

{

printf("argv[%d] = %s\n", i, argv[i] );

}

}

运行: ./a.out abc 123 xyz

argc --> 4

argv[0] -- ./a.out

argv[1] -- abc

argv[2] -- 123

argv[3] -- xyz

练习:

实现求两个整数之和, 参数从命令行输入

int main( int argc, char *argv[] )

{

int a = atoi( argv[1] );

int b = atoi( argv[2] );

int s = a+b;

printf("sum = %d\n", s );

}

./a.out 159 341

==============================

atoi() 把数字字符串 转换成 一个整数

NAME

atoi - convert a string to an integer

SYNOPSIS

#include <stdlib.h>

int atoi(const char *nptr);

功能: 把数字字符串 转换成 一个整数

参数:

nptr: 指定要转换的数字字符串

返回值:

返回该数字字符串所对应的整数

例如:

int a = atoi( "123" ); // a = 123

14.二级指针 和 多级指针

int a = 5;

int *p = &a; // p是一个指针变量, 保存变量a的地址

// p是一个 一级指针

对于 指针变量p本身来说, 也是有一个内存空间来保存, 那么 是不是也可以定义一个指针来保存 指针变量p的地址呢?

typeof(p) --> int *

指针的定义语法:

指向的类型 * 指针变量名;

--> typeof(p) * q;

--> int * * q;

// q就是一个二级指针, 它保存一个 一级指针的地址

==> q = &p; //q指向了p

*q ==> *(&p) ==> p ==> &a

**q ==> **(&p) ==> *p ==> *(&a) ==> a

**q = 10; ==> a = 10;

那么 对于二级指针q本身也是有地址, 我们是不是可以再定义一个指针 保存这个 二级指针q的地址呢?

typeof(q) ---> int **

==>

int ***r; //三级指针

r = &q;

....

多级指针

练习:

int a[2][3] = {1,2,3,4,5,6};

请问 表达式 **a 的含义?

**a --> **( &a[0] )

--> *( a[0] )

--> *( &a[0][0] )

--> a[0][0]

--> 1

15.动态内存的分配函数

malloc / realloc / calloc / free()

是从"堆空间"去分配内存的 释放空间

堆heap

堆空间, 生存期是随进程的持续性而一直存在的

从堆上分配的内存,一旦分配成功, 就会一直存在, 直到你手动free释放或者进程结束

NAME

malloc, free, calloc, realloc - allocate and free dynamic memory

SYNOPSIS

#include <stdlib.h>

void *malloc(size_t size);

void free(void *ptr);

void *calloc(size_t nmemb, size_t size);

void *realloc(void *ptr, size_t size);

  1. malloc

void *malloc(size_t size);

功能: 用来分配一块size大小的动态内存空间 (未初始化的)

参数:

size: 指定需要分配的内存空间的大小, 单位是 字节

返回值:

void * 通用指针

成功,返回分配到的内存空间的首地址

失败,返回NULL

注意:

malloc是从堆上面分配的内存空间,这块空间的生存期是 随进程的持续性

这个空间不会自动回收, 必须要用free手动释放 回收资源

  1. calloc

void *calloc(size_t nmemb, size_t size);

功能: 作用和malloc类似, 也是从堆上面分配一段地址连续的内存空间,只不过它是给数组分配的

参数:

nmemb: 表示你要分配多少个元素

size : 每个元素占多少个字节的空间

返回值:

void * 通用指针

成功,返回分配到的内存空间的首地址

失败,返回NULL

注意:

calloc( n, size ) <==> malloc( n*size );

但是 calloc分配的空间 它会把这个空间的内容全部清0, 而malloc就不会

  1. realloc

void *realloc(void *ptr, size_t size);

功能:把ptr指向的空间, 扩展成size大小

ptr指向的空间 必须是malloc/calloc/realloc分配的空间的地址

参数:

ptr: 指向一个堆空间的地址, 是你需要扩张的首地址

size: 重新扩张为size大小

返回值:

void *通用指针

成功,返回 扩张后的内存空间的首地址

失败,返回 NULL

注意:

(1) 如果 ptr == NULL

realloc( NULL, size ) ==> malloc( size )

(2) 如果 ptr != NULL

size > 原来的大小 , realloc用来把ptr指向的空间, 扩张到size大小的空间

原来前面的内容保持不变, 后面新增的空间的内容也不会初始化

size == 0 ---> realloc( ptr, 0 ) --> free( ptr )

size < 原来的大小 ,这种情况是未定义的(什么后果都有可能)

  1. free

void free(void *ptr);

功能: 释放ptr指向的堆空间, 必须是malloc/calloc/realloc分配的空间的地址

参数:

ptr: 指向你需要释放的堆空间的首地址

返回值:

注意:

malloc/realloc/calloc分配的是堆空间,一旦分配成功就一直存在,直到你手动free释放或者进程结束

如果你没有收到释放掉, 那么操作系统也不会把它分配给别人了

最后会造成内存空间越来越小 "内存泄漏" "垃圾内存"

例子:

把以下的代码, 改写成动态分配的内存

int n;

scanf("%d", &n );

int a[n];


==>

int n;

scanf("%d", &n );

//int *a = (int *)malloc( n*sizeof(int) );

int *a = (int *)calloc( n, sizeof(int) );

if( a == NULL )

{

printf("malloc error \n");

return ;

}

改写完之后, 再尝试 从键盘上获取数组元素的值, 并打印输出

int i;

for( i=0; i<n; i++ )

{

scanf("%d", &a[i] );

}

for( i=0; i<n; i++ )

{

printf("%d ", a[i] );

}

putchar('\n');

//重新扩张

a = realloc( a, n*sizeof(int) + 8 );

a[i] = 6;

a[i+1] = 7;

for( i=0; i<(n*sizeof(int)+8)/sizeof(int); i++ )

{

printf("%d ", a[i] );

}

putchar('\n');

  1. 小明 自认为是一个非常🐂🍺的程序员, 它写了如下的代码:

void func()

{

char *p = malloc( 100 );

p = "12345";

printf("p = %s\n", p );

}

请评价以上的代码:

malloc动态分配的100个字节的快捷, 只能通过指针变量p去访问这块空间

但是 又把字符串12345的地址赋值给p, p转而去指向这个字符串了

那么 新增的这个动态分配的100个字节的空间就无法访问(没有指针指向它了,找不到它了)

而操作系统也不能分配给别人, 最后造成了垃圾内存

==============================

NAME

memset - fill memory with a constant byte

SYNOPSIS

#include <string.h>

void *memset(void *s, int c, size_t n);

功能: 把s指向的空间的n个字节 全部设置为c值

参数:

s: 指向需要被设置的内存空间的首地址

c: 指定的内容

n: 指定的字节数

返回值:

返回被设置之后的空间的首地址(s)

例子:

char * p = malloc(10); //malloc分配的未初始化的堆空间

int i;

for( i=0; i<10; i++ )

{

printf("%d ", p[i] );

}

putchar('\n');

//memset( p, 0, 10 );

memset( p, 1, 10 );

for( i=0; i<10; i++ )

{

printf("%d ", p[i] );

}

putchar('\n');

相关推荐
就爱学编程5 小时前
重生之我在异世界学编程之C语言:数据在内存中的存储篇(下)
java·服务器·c语言
落羽的落羽6 小时前
【落羽的落羽 C语言篇】动态内存管理·下
c语言
叫我阿呆就好了7 小时前
C 实现植物大战僵尸(一)
c语言·开发语言
柒月的猫8 小时前
求和(2022蓝桥杯A组试题C)
c语言·算法·蓝桥杯
Ning_.10 小时前
力扣第116题:填充每个节点的下一个右侧节点指针 - C语言解法
c语言·算法·leetcode
挥剑决浮云 -11 小时前
STM32学习之 按键/光敏电阻 控制 LED/蜂鸣器
c语言·经验分享·stm32·单片机·嵌入式硬件·学习
zfenggo11 小时前
c/c++ 无法跳转定义
c语言·开发语言·c++
图灵猿11 小时前
【Lua之·Lua与C/C++交互·Lua CAPI访问栈操作】
c语言·c++·lua
A懿轩A11 小时前
C/C++ 数据结构与算法【树和二叉树】 树和二叉树,二叉树先中后序遍历详细解析【日常学习,考研必备】带图+详细代码
c语言·数据结构·c++·学习·二叉树·
WPG大大通16 小时前
基于DIODES AP43781+PI3USB31531+PI3DPX1207C的USB-C PD& Video 之全功能显示器连接端口方案
c语言·开发语言·计算机外设·开发板·电源·大大通