C指针总结
指针其实是内存单元地址在高级语言中的另一种叫法,地址也是数据,存放这种数据的变量就是指针变量,简称指针。变量的地址称为变量的指针,指变量存放空间的首地址。
(NOTE:指针变量,即指针,只能存放地址)
指向变量的指针
如果指针变量p的值恰好是变量a的地址,就说指针变量p指向了变量a。C语言规定一个指针变量只能指向同种类型的变量,比如
int *p1; //定义指针变量p1,只能指向整型变量
char *p2; //定义指针变量p2,只能指向字符型变量
指针变量的定义格式类型名 *变量名1, *变量名2, ...;
其实比较好理解,只需注意区分指针变量的类型和指针所指向变量的类型。指针变量的类型明确指出它能存放什么类型变量的地址,而不是它能存放什么类型的数据(指针只能存放地址),例如
int *p1, *p2; //定义了两个指向整型数据的指针p1,p2
其中,指针变量p1、p2的类型都是int *,而不是int。所以初学者需要特别注意下面几条语句的使用
int* p1, p2; //错误,虽然指向整型的指针变量类型是int*,但不能这样使用,可以理解为*自动与变量名匹配而不是与类型名匹配
int *p1, *p2, a; //正确,定义了指向整型的指针变量p1、p2和整型变量a
指向数组的指针
数组是若干个相同类型变量的集合,在内存中占用一片连续的地址空间。按照指针的概念,数组在内存中的起始地址就是数组的指针,而数组元素的地址称为数组元素的指针。C语言规定,数组名就是数组的指针,代表数组在内存空间的首地址,同时也指向数组的第一个元素。
(NOTE:数组名,即数组的指针,是地址常量,不能更改)
int *p, data[10];
p = data; //指针变量p指向整型数组data[10]
printf("%d", *p); //输出data[0]的值
printf("%d", *(p+2)); //输出data[2]的值
可以看到指针和整数执行算术运算后得到一个新的指针,那么它指向哪里呢?如果对字符指针加1,运算结果产生的指针指向内存中下一个字符。如果对整型指针加1,运算结果产生的指针指向内存中下一个整数,即移动4个字节而不是1个字节。也就是说,当指针和整数执行算术运算后,整数在运算时始终根据“合适的大小”(指针所指向变量的类型在内存中占用的字节数)进行调整,这个调整便是把整数值和“合适的大小”相乘。在上例中p+2相当于指针后移了8个字节(整型变量在内存中占4个字节)
(NOTE:C不会检查数组的下标溢出,所以针对数组使用下标引用或者间接访问操作一定要特别小心)
指向字符串的指针
C使用字符数组存放字符串,和指向整型数组的指针一样可以使用字符指针指向字符串常量,这时指针代表字符数组(字符串)在内存中的起始地址。
char *str = "I'm very happy.";
等价于
`char *str, temp[] = "I'm very happy.";`
`str = temp;`
当直接使用字符指针指向字符串常量时,实际上系统隐含定义了一个无名字符数组来存放字符串,字符指针指向了这个无名数组,而不是字符指针存放了字符串。
指针作为函数的参数
指针变量作为函数参数时,同样是从实参单向传递指针变量的值给形参,也即形参得到实参值的一份copy,但由于实参变量是指针,它的值是某个变量的内存地址,所以形参的值也是这个变量的内存地址。需要特别注意的是,这时实参和形参作为变量在内存中占用不同的内存单元,但是具有相同的值,即指向相同的内存单元,使用间接访问操作符*可以得到相同的值。
(1)使用间接访问操作改变形参所指向变量的值,则实参所指向变量的值也发生变化
(2)改变形参的值并不会改变实参的值以及实参所指向变量的值
见下例
#include <stdio.h>
//交换两个形参变量的值,并不会改变实参的值和实参所指向变量的值
void swap(int *a, int *b)
{
int *c;
c = a;
a = b;
b = c;
printf("swap: a=%d, b=%d\n", *a, *b);
}
int main()
{
int a, b;
scanf("%d%d", &a, &b);
printf("main_1: a=%d, b=%d\n", a, b);
swap(&a, &b);
printf("main_1: a=%d, b=%d\n", a, b);
return 0;
}
输入
3 8
程序输出
main_1: a=3, b=8
swap: a=8, b=3
main_1: a=3, b=8
可以见到,虽然使用指针变量作为函数参数,但在被调函数内交换的是两个指针变量,而不是指针所指向的变量,因此实参所指向变量的值并未发生改变。
#include <stdio.h>
//交换两个形参所指向变量的值,实参所指向变量的值也发生改变
void swap(int *a, int *b)
{
int c;
c = *a;
*a = *b;
*b = c;
printf("swap: a=%d, b=%d\n", *a, *b);
}
int main()
{
int a, b;
scanf("%d%d", &a, &b);
printf("main_1: a=%d, b=%d\n", a, b);
swap(&a, &b);
printf("main_1: a=%d, b=%d\n", a, b);
return 0;
}
输入
3 8
程序输出
main_1: a=3, b=8
swap: a=8, b=3
main_1: a=8, b=3
这次使用指针变量作为函数参数,且在被调函数内交换指针所指向的变量,因此实参所指向变量的值发生改变。
指针数组
把相同类型的指针变量存放在一个数组中,便构成了指针数组。一般字符指针数组用的比较多,多用于存放不等长的字符串。如下
char *log[10]; //定义字符指针数组,存放日志信息
数组log[10]的元素类型是char *,即字符指针,可以指向不等长的字符串。比如
char *log[10] = {"mac new 00:e0:4c:8b:08:47", "mac old 00:e0:4c:8b:25:eb", "arp spoof 192.168.1.26", ...};
指向函数的指针
函数在编译后运行时,会被分配一块内存区域,这个内存区的起始地址便是函数的入口地址,也即函数的指针。指向函数的指针的定义如下,
返回值类型 (*指针变量名)(参数类型列表);
double (*p)(float, float); //定义了一个函数指针p,指向的函数有两个float型参数且返回double型数据
为方便理解,可以这样想: (实际编译是通不过的,存在语法错误)
double(float,float) *p; //定义一个double(float,float) *型的指针p(仅限于理解,实际存在语法错误)
(NOTE:(*p)
中的括号是必需的,因为*的优先级低于函数定义的(),和下面要介绍的返回指针的函数有区别)
返回指针的函数
函数同样可以返回指针类型,定义如下
类型名 *函数名(参数列表)
{函数功能}
比如
int *fun(int a, int b); //定义一个返回int *型数据的函数fun(int,int)