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.如何获取地址?
- 取地址符 &
语法:
&对象名 //表示取该对象的地址 (变量,数组元素等)
例子:
int a = 10;
int * p = &a; //取变量a的地址, 赋值给指针变量p 进行初始化
- 有些对象的名字 本身就代表地址
数组名 : 数组名就是数组的首地址, 也是数组首个元素的地址
函数名
...
注意:
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)野指针
是指向一个"未知的"地址的指针
例子:
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);
- 空指针 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]
- 表达式 *(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.指针数组 和 数组指针
数组指针 --> 指针
本质是一个指针, 它指向一个数组
指针数组 --> 数组
本质是一个数组, 里面的元素都是指针
- 数组指针
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
- 指针数组
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)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 = ∑ 或者 p = sum;
(3)如何通过函数指针来调用函数?
平时调用函数:
函数名(实参); ==> sum( 1, 2);
通过函数指针来调用函数:
函数指针(实参); ==> p( 1 ,2 ); // p = sum;
(*函数指针)(实参) ==> (*p)( 1,2 ); // p = ∑
例子:
int sum( int a, int b )
{
return a+b;
}
int main()
{
int (*p) (int, int);
int (*q) (int, int);
p = sum; // ok
q = ∑ // 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] ...
- 指针函数
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);
- malloc
void *malloc(size_t size);
功能: 用来分配一块size大小的动态内存空间 (未初始化的)
参数:
size: 指定需要分配的内存空间的大小, 单位是 字节
返回值:
void * 通用指针
成功,返回分配到的内存空间的首地址
失败,返回NULL
注意:
malloc是从堆上面分配的内存空间,这块空间的生存期是 随进程的持续性
这个空间不会自动回收, 必须要用free手动释放 回收资源
- calloc
void *calloc(size_t nmemb, size_t size);
功能: 作用和malloc类似, 也是从堆上面分配一段地址连续的内存空间,只不过它是给数组分配的
参数:
nmemb: 表示你要分配多少个元素
size : 每个元素占多少个字节的空间
返回值:
void * 通用指针
成功,返回分配到的内存空间的首地址
失败,返回NULL
注意:
calloc( n, size ) <==> malloc( n*size );
但是 calloc分配的空间 它会把这个空间的内容全部清0, 而malloc就不会
- 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 < 原来的大小 ,这种情况是未定义的(什么后果都有可能)
- 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');
- 小明 自认为是一个非常🐂🍺的程序员, 它写了如下的代码:
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');