【C语言】指针:
- 指针是个变量,存储其它变量的内存地址。
- 使用 "&" 获取内存地址,使用 "*" 获取内存地址中的值。
- 指针可以作为参数传入函数,也可以作为函数的返回值。
- 指针也可以指向数组、指针、函数等。
- 指针也可以作为数组的元素,即数组中元素是指针。
1、指针变量
声明指针变量:基类型 *指针变量名**;**
指针指向内存地址:指针变量名 = 内存地址**;**
通过指针获取指向的内存地址中的值:*指针变量名**;**
注:基类型是指针指向内存地址中的值的类型。
cs
#include <stdio.h>
int main(void)
{
int a = 10;
printf("a: address is %p, value is %d\n", &a, a);
int *p;
p = &a;
printf("p point to a's address is %p, a's value is %d\n", p, *p);
return 0;
}
// 结果:
a: address is 000000000061FE14, value is 10
p point to a's address is 000000000061FE14, a's value is 10
2、指针作为函数参数
某个值作为参数传入函数,则实际传入的是值的拷贝,函数结束就销毁,不会修改值本身。
若是指针为函数参数,则操作的是指针指向的内存地址。常举的例子是两个值互换。
cs
#include <stdio.h>
// function prototype
void swap_1(int, int); // 参数是值
void swap_2(int *, int *); // 参数是指针
int main(void)
{
int x = 1, y = 2; // 局部变量x,y, 只在该函数内有效
printf("x = %d, y = %d\n", x, y);
swap_1(x, y); // x,y作为参数传入函数,实际传入的是x,y的拷贝
printf("Argument are values. swapped: x = %d, y = %d\n", x, y);
swap_2(&x, &y); // &x是x的内存地址, 作为参数时可视为指向x的指针
printf("Argument are pointers. swapped: x = %d, y = %d\n", x, y);
return 0;
}
void swap_1(int a, int b)
{
int tmp = a; // 局部变量tmp,a,b,只在该函数内有效
a = b;
b = tmp;
}
void swap_2(int *a, int *b)
{
int tmp = *a;
*a = *b;
*b = tmp;
}
// 结果:
x = 1, y = 2
Argument are values. swapped: x = 1, y = 2
Argument are pointers. swapped: x = 2, y = 1
3、指针作为函数返回值
即函数返回的是指针。
cs
int *func(void) // 函数原型
int main(void)
{
int *p;
p = func(); // 接收函数返回的指针
...
return 0;
}
int *func(void)
{
int *pointer;
...
retrun pointer; // 函数返回指针
}
4、指针的运算
① 指针 指向的内存地址是用数值表示的,可以做算术运算(++、--、+、-)。
指针的算术运算只能和整数进行加减。一般用在指针指向数组(即数组第一个元素的内存地址),通过指针的递增或递减,来指向下一个或上一个元素。
- 指针的每一次递增,是指向下一个元素的内存地址。
- 指针的每一次递减,是指向上一个元素的内存地址。
- 指针的每一次递增/递减的跳跃的字节数取决于基类型的长度。例如:int是4字节,指向下一个int是指向4字节以后的内存地址。
② 两指针之间也能进行减法,计算两指针指向的内存地址之间间隔多少元素。一般是指向同一个数组的不同元素,来计算数组有多少个元素。
③ 两指针之间也能进行比较(==、< 、 >等),判断两指针指向的内存地址的大小,返回值是整数(1:True,0:False)。
cs
#include <stdio.h>
int main(void)
{
int a[] = { 2024, 4, 23, 9, 20 };
int *p, *q, m;
p = q = a; // ① 指针p和指针q都指向数组a(第一个元素的内存地址)
printf("p point to address %p, q point to address %p, the first element address is %p\n", p, q, &a[0]);
q++; // ② 指针q指向下一个元素的内存地址,相当于q=q+1
printf("the second element: address is %p, %p\n", q, &a[1]);
q += 3; // ③ 指针q指向跨越3个元素的内存地址,相当于q=q+3
printf("the last element: address is %p, %p\n", q, &a[4]);
m = q - p + 1; // 指针q指向的内存地址与指针p指向的内存地址之间间隔多少元素
printf("%d elements are spaced from p to q\n", m);
printf("if q point to address > p point to address(1:True,0:False): %d\n", q > p); // 比较p,q指向的内存地址的大小
return 0;
}
// 结果:
p point to address 000000000061FDF0, q point to address 000000000061FDF0, the first element address is 000000000061FDF0
the second element: address is 000000000061FDF4, 000000000061FDF4
the last element: address is 000000000061FE00, 000000000061FE00
5 elements are spaced from p to q
if q point to address > p point to address(1:True,0:False): 1
通过指针获取值:*指针。
cs
#include <stdio.h>
int main(void)
{
int a[] = { 2024, 4, 23, 9, 20 };
int *p, m;
p = a; // 指针p指向数组a(第一个元素的内存地址)
printf("p point to a,the value is %d, the first element is %d\n", *p, a[0]);
p++; // 指针p指向数组下一个元素
printf("next value is %d, the second element is %d\n", *p, a[1]);
return 0;
}
// 结果:
p point to a,the value is 2024, the first element is 2024
next value is 4, the second element is 4
注意区分 *p++, *++p , (*p)++, ++*p, p++, ++p:
*p++:先获取指针指向的内存地址中的值(*p),再指针指向下一个内存地址(p=p+1)。
*++p:先指针指向下一个内存地址(p=p+1),再获取指向的内存地址中的值(*p)。
(*p)++:先获取指针指向的内存地址中的值(*p),再将该值+1(*p=*p+1)。
++*p:先将指针指向的内存地址中的值+1(*p=*p+1),再获取值(*p)
p++:先获取指针指向的内存地址(p),再指针指向下一个内存地址(p=p+1)。
++p:先指针指向下一个内存地址(p=p+1),再获取指向的内存地址(p)。
cs
#include <stdio.h>
int main(void)
{
int a[] = { 2024, 4, 23, 9, 20 };
int *p;
p = a;
printf("p point to a,the first value is %d\n", *p++); // *p++:获取值,再指向下一个
printf("next value is %d\n", *p);
return 0;
}
// 结果:
p point to a,the first value is 2024
next value is 4
#include <stdio.h>
int main(void)
{
int a[] = { 2024, 4, 23, 9, 20 };
int *p;
p = a;
printf("p point to a, next value is %d\n", *++p); // *++p:指向下一个,再获取值
printf("value is %d\n", *p);
return 0;
}
// 结果:
p point to a, next value is 4
value is 4
cs
#include <stdio.h>
int main(void)
{
int a[] = { 2024, 4, 23, 9, 20 };
int *p;
p = a;
printf("p point to a, value is %d\n", (*p)++); // (*p)++:获取值,再值+1
printf("value is %d\n", *p);
return 0;
}
// 结果:
p point to a, value is 2024
value is 2025
#include <stdio.h>
int main(void)
{
int a[] = { 2024, 4, 23, 9, 20 };
int *p;
p = a;
printf("p point to a, value is %d\n", ++*p); // ++*p:值+1,再获取值
printf("value is %d\n", *p);
return 0;
}
// 结果:
p point to a, value is 2025
value is 2025
cs
#include <stdio.h>
int main(void)
{
int a[] = { 2024, 4, 23, 9, 20 };
int *p;
p = a;
printf("p point to a, address is %p\n", p++); // p++:获取指向,再指向下一个
printf("address is %p\n", p);
return 0;
}
// 结果:
p point to a, address is 000000000061FE00
address is 000000000061FE04
#include <stdio.h>
int main(void)
{
int a[] = { 2024, 4, 23, 9, 20 };
int *p;
p = a;
printf("p point to a, address is %p\n", ++p); // ++p:指向下一个,再获取指向
printf("address is %p\n", p);
return 0;
}
// 结果:
p point to a, address is 000000000061FE04
address is 000000000061FE04
5、指针数组
指针可以指向数组,而数组中的元素也可以是指针。若一个数组,该数组中每个元素都是指针,则称为指针数组。
cs
int (*a)[] // 指向数组的指针,运算符优先级:"()"和"[]" > "*"
int *a[] // 数组指针,数组的元素是指针
注:字符串通常用数组表示。数组名就是内存地址(数组第一个元素的内存地址),字符串名也是内存地址(字符串第一个字符的内存地址)。占位符%p表示字符串的内存地址,%s表示字符串的内容。
cs
#include <stdio.h>
#include <string.h>
int main(void)
{
char *s[] = { "hello", "world", "good", "luck"}; // 数组中的元素都是指针,指向字符串
int n = sizeof(s) / sizeof(s[0]); // 获取数组元素个数
for(int i = 0; i < n; i++)
{
// 输出数组中每个指针指向的内存地址和内容(字符串)
printf("%d element is pointer, point to address %p, value is %s\n", i, s[i], s[i]);
// 输出每个字符串中的每个字符
for(int k = 0,m = strlen(s[i]); k < m; k++)
{
printf("%c ",s[i][k]);
}
printf("\n");
}
return 0;
}
// 结果:
0 element is pointer, point to address 0000000000404000, value is hello
h e l l o
1 element is pointer, point to address 0000000000404006, value is world
w o r l d
2 element is pointer, point to address 000000000040400C, value is good
g o o d
3 element is pointer, point to address 0000000000404011, value is luck
l u c k
指针数组可以作为参数传入函数,则可以理解为传入函数的是一个指向指针数组的指针,可以通过指针的算术运算指向下一个或上一个元素。注意同时传入指针数组的元素个数,检查边界避免越界。
注:数组作为参数传入函数,视为传入函数的是指向数组的指针。
cs
#include <stdio.h>
void printstring(char *[], int); // 指针数组作为函数参数
int main(void)
{
char *s[] = { "hello", "world", "good", "luck"}; // 数组中的元素都是指针,指向字符串
int n = sizeof(s) / sizeof(s[0]); // 获取数组元素个数
for(int i = 0; i < n; i++)
{
printf("%d element is pointer, point to address %p, value is %s\n", i, s[i], s[i]);
}
printstring(s, n);
return 0;
}
void printstring(char *string[], int length)
{
while(length-- > 0)
{
printf("pointer, point to %s\n", *string++);
}
}
// 结果:
0 element is pointer, point to address 0000000000404000, value is hello
1 element is pointer, point to address 0000000000404006, value is world
2 element is pointer, point to address 000000000040400C, value is good
3 element is pointer, point to address 0000000000404011, value is luck
pointer, point to hello
pointer, point to world
pointer, point to good
pointer, point to luck
main主函数可以接收命令行参数,argc是参数个数,argv是传入的所有命令行参数(第一个是文件名) 。argv is a pointer to an array of character strings that contain the arguments, one per string.
cs
// pointer.c
#include <stdio.h>
int main(int argc, char *argv[])
{
for(int i = 1; i < argc; i++)
{
printf("%d element is pointer, point to address %p, value is %s\n", i, argv[i], argv[i]);
}
printf("\n");
while(--argc > 0)
{
printf("pointer, point to %s\n", *++argv);
}
return 0;
}
// TERMINAL 终端:
gcc -o pointer pointer.c
./pointer hello world good luck
// 结果:
1 element is pointer, point to address 0000000000AA1430, value is hello
2 element is pointer, point to address 0000000000AA14A0, value is world
3 element is pointer, point to address 0000000000AA14C0, value is good
4 element is pointer, point to address 0000000000AA14E0, value is luck
pointer, point to hello
pointer, point to world
pointer, point to good
pointer, point to luck
注意区分: *++argv[0]、(*++argv)[0]
*++argv[0]:argv[0]的字符串中指向下一个字符,获取其值。
(*++argv)[0] :argv指向下一个参数,获取字符串的第一个字符。
【运算符优先级: () [ ] 高于 * ++】
cs
// pointer1.c
#include <stdio.h>
int main(int argc, char *argv[])
{
argv++; // argv指向下一个元素,即argv[1]
printf("*++argv[0] is %c\n", *++argv[0]); // argv[1][0]下一个元素即argv[1][1]的值
return 0;
}
// TERMINAL 终端:
gcc -o pointer1 pointer1.c
./pointer1 hello world
// 结果:
*++argv[0] is e
// pointer2.c
#include <stdio.h>
int main(int argc, char *argv[])
{
argv++; // argv指向下一个元素,即argv[1]
printf("(*++argv)[0] is %c\n", (*++argv)[0]); // argv[1]下一个元素,即argv[2]的第一个字符
return 0;
}
// TERMINAL 终端:
gcc -o pointer2 pointer2.c
./pointer2 hello world
// 结果:
(*++argv)[0] is w
指针数组类似于两维数组,但区别是:
指针数组:
- 数组中的元素都是指针。
- 指针指向的内存地址中的值可以占不同字节数。
- 内存空间:所有指针所占内存空间+所有指针指向的内存地址中的值所占内存空间。
两维数组:
- 数组中的元素是一维数组。
- 数组中的元素占相同字节数。
- 固定内存空间:所有元素所占内存空间。
6、函数指针
函数本身就是存储在内存里的一段代码,指针可以指向函数,通过指针调用函数。指向函数的指针称为函数指针。
- 指向函数的指针,其参数、返回值和函数一致。
- 必须用圆括号()将*和函数指针名括起来,即(*函数指针名)。若不加圆括号则表示函数返回指针。
- 通过指针调用函数,和函数本身调用一样。
- 函数指针指向函数,函数名就是函数的内存地址。
cs
int (*p)(int, int) // 函数指针p,指向带有2个int类型参数和返回int类型数值的函数
int *p(int, int) // 函数p,有2个int类型参数,返回指向int类型的指针
cs
#include <stdio.h>
// function prototype 函数原型
int sum(int, int);
int main(void)
{
int x = 1, y = 2, z;
int (*p)(int, int) = sum; // 指针指向函数sum
z= p(1,2); // 通过指针调用函数
printf("%d + %d = %d\n", x, y, z);
return 0;
}
int sum(int a, int b)
{
return a + b;
}
// 结果:
1 + 2 = 3
函数指针可以作为其它函数的参数,其它函数执行时通过指针调用的函数,称为回调函数。
cs
#include <stdio.h>
#include <stdlib.h>
// function prototype 函数原型
int randdigit(void);
int sum(int (*)(void), int); // 指针函数作为参数
int main(void)
{
int y = 2, z;
z= sum(randdigit,2); // 调用函数,其中一个参数是指针函数,将指向函数randdigit的指针传入函数
printf("x + %d = %d\n", y, z);
return 0;
}
// 产生随机数,并输出
int randdigit(void){
int x = rand();
printf("x = %d\n",x);
return x;
}
// 求和,其中一个参数是函数randdigit返回的随机数
int sum(int (*a)(void), int b) // 指针函数a作为参数,接收通过函数指针a获取的函数randdigit
{
return a() + b; // 通过函数指针a调用函数randdigit
}
// 结果:
x = 41
x + 2 = 43
补充:运算符优先级
类别 | 运算符 | 结合性 |
---|---|---|
后缀 | () [] -> . | 从左到右 |
一元 | + - ! ~ ++ - - (type)* & sizeof | 从右到左 |
乘除 | * / % | 从左到右 |
加减 | + - | 从左到右 |
移位 | << >> | 从左到右 |
关系 | < <= > >= | 从左到右 |
相等 | == != | 从左到右 |
位与 AND | & | 从左到右 |
位异或 XOR | ^ | 从左到右 |
位或 OR | | | 从左到右 |
逻辑与 AND | && | 从左到右 |
逻辑或 OR | || | 从左到右 |
条件 | ?: | 从右到左 |
赋值 | = += -= *= /= %= >>= <<= &= ^= |= | 从右到左 |
逗号 | , | 从左到右 |