C语言学习笔记-指针
一个变量的地址称为该变量的指针
如果有一个变量专门用来存放另一个变量的地址(既指针),则它称为指针变量
- 在定义指针变量时为什么必须指定基类型
不同类型的数据在内存中所占的字节数和存放方式是不同的,如果想通过指针引用一个变量,只知道地址是不够的,因为不知道要从该地址取几个字节,所以知道数据类型是必要的。
- 函数swap将两个字符串(字符数组作实参,长度不超过100)的内容进行交换。请找出错误
Void swap (char* pa,char* pb)
{
char* temp;
temp = pa;
pa = pb;
pb = temp;
}
① 类型名错误,应该是小写
② 指针变量也是变量,依然遵守单向的值传递方式,所以只会改变形参而不是实参。改正:
void swap_1 (char* pa, char* pb)
{
char temp[120];
strcpy(temp, pa);
strcpy(pa, pb);
strcpy(pb, temp);
}
void swap_2(char** p, char** q) {
char* temp;
temp = *p;
*p = *q;
*q = temp;
}
- 两个地址能相加吗?相减吗?
相加不能,没有实际意义。如果两个地址都指向同一数组,相减就可以
- 数组名能使用 ++ 运算吗?
不能,因为是常量,它的值在程序运行期间无法改变
- 指向数组的指针变量也可以带下标,但是一定要先看\(p\)当前指的元素位置,如:
int main()
{
int a[3] = {1, 2, 3};
int *p = a;
printf("%d\n", p[1]);
p++;
printf("%d\n", p[1]);
}
-
*p++
等价于*(p++)
,因为++
和*
同优先级,所以结合方向为自右向左。具体:先引用\(p\)的值,实现*p
运算,然后再使\(p\)自增1。 -
实参数组名代表一个固定的地址,或者说是指针常量,但形参数组名并不是一个固定的地址,而是按照指针变量处理
void function(int *p) // 也可以写成 void function(int p[])
{
p++;
printf("%d\n", *p);
}
int main()
{
int a[3] = {1, 2, 3};
function(a);
return 0;
}
- 对于二维数组
int a[2][2]
而言,*(a[i] + j) = *(*(a + i) + j) = a[i][j]
。务必记住:a[i]
和*(a + i)
等价,且都是地址。
表示形式 | 含义 |
---|---|
a |
二维数组名,指向一维数组a[0] ,既0行首地址 |
a[0] , *(a + 0) , *a |
0行0列元素地址 |
a + 1 , &a[1] |
1行首地址 |
a[1] , *(a + 1) |
1行0列元素a[1][0] 的地址 |
a[1] + 2 , *(a + 1) + 2 , &a[1][2] |
1行2列元素的地址 |
*(a[1] + 2) , *(*(a + 1) + 2) , a[1][2] |
1行2列元素a[1][2] 的值 |
a
和*a
虽然指向的地址是相同的,但含义不同,大小也不同,前者16个字节,后者8个字节
强调:二维数组名(如 a
)是指向行的。因此a + 1
中的“1”代表一行中全部元素所占的字节数。一维数组名(如 a[0]
, a[1]
)是指向列元素的。a[0] + 1
中的“1”代表一个a
元素所占的字节数。在指向行的指针前面加一个*
就是 *a
和 *(a + 1)
,它们就成为指向列的指针。反之,在指向列的指针前面加&
,就成为指向行的指针。
- 指向由\(m\)个元素组成的一维数组的指针变量:
int (*p)[m]
。注意*p
两侧的括号不可缺少,如果写成*p[4]
,由于方括号[]
运算级别高,因此p
先与[4]
结合,p[4]
是定义数组的形式,然后再与前面的*
结合,*p[4]
就是指针数组
#include <stdio.h>
int main()
{
int a[4] = {1, 3, 5, 7};
int (*p)[4];
p = &a;
printf("%d\n", (*p)[3]);
return 0;
}
注意第5行不应写成 p = a
,因为这样写表示p
的值是&a[0]
,指向a[0]
。p = &a
表示p
指向一维数组(行),(*p)[3]
是p
所指向的行中序号为3的元素。
从int (*p)[4];
可以看到,p
的类型不是int *
型,而是int(*)[4]
型,p
被定义为指向一维整型数组的指针变量,一维数组有4个元素,因此p
的基类型是一维数组,其长度是16字节
- 通过指针引用字符串,c语言对字符串常量是按字符数组处理的,在内存中开辟了一个字符数组用来存放该字符串常量,但是这个字符数组是没有名字的,因此不能通过数组名来引用。只能通过指针变量来引用。看如下代码:
char* string = "I Love You";
等价于
char* string;
string = "I Love You";
/*
wrong: *string = "I Love You";
因为 string 指向的地址是随机的,这样容易覆盖掉其它区域中数据
*/
强调:字符指针变量中存放的是地址(字符串第一个字符的地址),绝不是将字符串放到指针变量中。而且,字符指针变量指向的字符串常量中的内容是不可以被取代的(不能对它们再赋值)
- 高效简捷的
copy_string()
函数,就是有点绕,要分清优先级
void copy_string(char *from, char *to)
{
while(*to++ = *from++);
//等价于:while((*to++ = *from++) != '\0');
}
- 什么是函数指针?
如果在程序中定义了一个函数,在编译时,编译系统为函数代码分配一段存储空间,这段存储空间的起始地址(又称入口地址)称为这个函数的指针。
可以定义一个指向函数的指针变量,用来存放入口地址,这就意味着此指针指向该函数,例如:int (*p)(int, int)
,p
是一个指向函数的指针变量,最前面的int
表示这个函数值(既函数返回的值)是整型的。最后面的括号中有两个int
,表示这个函数有两个int
型参数。注意*p
两侧的括号不可省略,表示p
先与*
结合,是指针变量,然后再与后面的()
结合,()
表示是函数,既该指针变量不是指向一般的变量,而是指向函数。
#include<stdio.h>
int max(int a, int b)
{
return (a > b ? a : b);
}
int main()
{
int (*p)(int, int);
p = max; // 把 max函数 的入口地址赋给 p
int a, b; scanf("%d %d", &a, &b);
int c = (*p)(a, b); // 不能写成: c = p(a, b);
printf("%d\n", c);
return 0;
}
用指向函数的指针作函数参数。指向函数的指针变量的一个重要用途是把函数入口地址作为参数传递到其它函数
- 什么是指针数组?
一个数组,若其元素均为指针类型数据,称为指针数组。
定义一维指针数组的一般形式为:类型名 * 数组名[数组长度];例如int *arr[10]
;
指针数组比较适合用来指向若干个字符串,使字符串处理更加方便。具体的说,当这些字符串不等长时,不用事先指定它的长度,并且方便排序。
#include<stdio.h>
void print(char* name[], int n)
{
char* p = name[0];
int i = 0;
while(i < n) {
p = *(name + i++); // 先求 *(name + i)的值,然后使 i 加 1
printf("%s\n", p);
}
}
int main()
{
char* name[] = {"ou my", "hollo"};
print(name, 2);
return 0;
}
- 怎么定义一个指向指针数据的指针变量?
举例:char **p;
,p
的前面有两个*
号,由于*
运算符的结合性是从右到左,因此**p
相当于*(*p)
,显然*p
是指针变量的定义形式。如果没有最前面的*
号,那就是定义了一个指向字符数据的指针变量。现在它前面又有一个*
号,既char **p;
。可以把它分为两部分看,既:char *
和*p
,后面的*p
表示p
是指针变量,前面的char *
表示p
指向的是char *
型的数据。也就是说,p
指向一个字符指针变量(char *
)(这个字符指针变量指向一个字符型数据)。
#include<stdio.h>
int main()
{
char* name[] = {"ou my", "hollo"};
char **p;
int i;
for (i = 0; i < 2; ++i) {
p = name + i;
printf("%s\n", *p);
}
return 0;
}
指针的内容是个地址,但指针自身也有个地址&p
,所以定义一个指针的指针,指向这个地址(&p
)
-
指针数组作main函数的形参:
int main(int argc, char *argv[])
。argc
和argv
就是main
函数的形参,它们是程序的”命令行参数”。argc
指参数个数,argv
指参数向量,它是一个char
型指针数组,数组中每一个元素(其值为指针)指向命令行中的一个字符串 -
什么是内存的动态分配?
C语言允许建立内存动态分配区域,以存放一些临时用的数据,这些数据不必在程序的声明部分定义,也不必等到函数结束时才释放,而是需要时随时开辟,不需要时随时释放。这些数据是临时存放在称为堆的存储区域。可以根据需要,向系统申请所需大小的空间。
对内存的动态分配是通过系统提供的库函数来实现的,主要有如下函数 【C99标准】:
-
使用
malloc
函数,其函数原型为void * malloc(unsigned int size)
,作用是在内存的动态存储区分配一个长度为size的连续空间。此函数的返回值是所分配区域的第一个字节的地址。 注意指针的基类型为void
,这种指针称为无类型指针,既不指向任何一种具体的类型数据,只提供一个纯地址,而不指向任何具体的对象 。如果函数未成功执行,返回NULL
。 -
使用
calloc
函数,其函数原型为void * calloc(unsigned n, unsigned size)
,作用是在内存的动态存储区分配\(n\)个长度为size的连续空间,返回值也是地址或者NULL
。 -
使用
free
函数,其函数原型为void free(void *p)
,作用是释放指针变量p
所指向的动态空间,无返回值。 -
使用
realloc
函数,其函数原型为void * realloc(void *p, unsigned int size)
,如果已经获得了动态空间,想改变其大小,就可以用这个函数
好图