C语言-指针

指针的基本概念

  • ==指针就是地址,而指针变量就是存储地址的变量==。
  • 指针变量的声明:在普通变量声明的基础上加上*:
    • 如指向int类型对象的指针变量:int *p;
  • 取地址运算符:用于获取变量的地址,来给指针变量赋值。
    • 示例:
    int i, *p;
    p = &i;
  • 间接寻址运算符:用于访问指针变量指向的对象的内容。
    • 示例:
    int i, *p;
    p = &i;
    i = *p;
    • 不要混淆间接寻址运算符和指针变量的声明时的*的功能。
    • *p相当于i的==别名==,对*p执行的任何操作,与直接对i做操作的效果一样。

指针与数组

  • 指针可以指向数组元素:
    • 示例:
    int a[10],*p;
    p = &a[0];
    *p = 5;
    • 如果==指针指向数组元素,那么对该指针进行算运算==就是有意义的:
      1. 指针加上整数:指针加上整数,代表该指针指向更右边数组的元素,如int *p = &a[0],给p加上2之后,p指向a[2]
      2. 指针减去整数:指针减去整数,代表该指针指向更左边数组的元素,如int *p = a[2],给p减去2之后,p指向a[0]
      3. 两个指针相减:两个指针相减,结果为这两个指针指向的数组元素之间间隔的数组元素个数,如int *p = &a[5]; int *q = &a[1],那么p和q相减的结果就是4。
  • 可以直接将数组名赋值给指针变量
    • 如:int a[2] = {0, 1, 2}; int *p = a;
    • 实质是p指向该数组的第一个元素a[0]
    • ==数组的名字实质上是指向数组第一个元素的指针==
    • 不能给数组名赋值。
  • 数组型形式参数
    • 在将数组作为形式参数传递给函数的时候,实际传递的是==指向该数组的第一个元素的指针(或者说是数组名)==。
    • ==声明形式参数为指针就相当于声明变量为数组==(但是普通变量不可以这样理解),可以==将数组型形式参数声明为指针==,如:int function(int a[], int n)int function(int *a, int n)等价。
    • 可以给函数传递==数组的片段==,如:function(&a[6], 10)
  • 用指针作为数组名
    • 可以将指针看做数组名进行==取下标操作==。
    • 编译器将p[i]看做`*(p+i)。
    • 对指针进行取下标操作的前提是:==该指针是指向了一个数组的首元素==。
    • 示例:
    int a[10], *p;
    p = a;
    int num = p[1];
  • 指针数组
    • 元素是指针的数组,如:int *p[10],声明了一个数组,这个数组存储10个int型指针变量。
  • 指向指针的指针
    • 示例:
    char *p1;
    char **p2 = &p1;

指针和多维数组

  • C语言中的多维数组,可以理解为将每一行的元素依次排列;
  • 将二维数组看做是一维数组来处理:
    • 指针指向二维数组的第一个元素,指针每次加一,都会指向数组的下一个元素,如果到达本行末尾,就指向下一行的第一个元素。
  • 将指针指向某一行的第一个元素
    • 一般方法:
    int a[10][10], *p;
    //指向第二行的第一个元素
    p = &a[1][0]; 
    • 简写:根据可以对指针进行去下标操作,利用&a[i][0] = &(*(a[i] + 0))
    int a[10][10], *p;
    //指向第三行的第一个元素
    p = a[2];
  • 指向一维数组的指针
    • 对一维数组,我们常用的指针是指向==数组的第一个元素的==;
    • 可以定义==指向一个一维数组的指针==:int (*p)[10],这是声明了一个指向长度为10的整型数组的指针p;
    • 对于二维数组,可以看做是==一个每个元素都是一维数组的一维数组==;
    • 使用指向数组的指针来==按行遍历二维数组==:
    int a[10][11], (*p)[10];
    //省去数组赋值等
    //按列遍历数组,并将数组第五列的值赋为0
    for(p = &a[0]; p < &a[10]; p++) {
      (*p)[4];
    }
    • 在二维数组中,a[i]取的是第i-1行第一个元素的地址,&a[i]取的是第i-1行的地址(第i-1个一维数组的地址)
    • 对于指向数组的指针来说:*p是取出指向的数组,(*p)[i]是取出指向的数组中的第i-1个元素。
  • 用多维数组名作为指针
    • 二维数组的==数组名是一个指向一维数组的指针==,例如int a[10][10]的数组名a代表的意思是&a[0]
  • 多维数组的长度
    • 对于==一维数组在声明时就赋值的情况下,可以省略数组的长度==,如:int a[] = {1, 2}
    • ==二维数组在声明时就赋值可以省略第一维的长度==,最后的二维数组的每一维的长度都是每一行的数组的长度是==根据最长的数组的长度来确定==的。如果有一些行的数据不够填满整行,那么C语言==会用空字符\0来填补==。

字符串数组

二维数组实现字符串数组

  • 字符串字面量
    • 字符串是一个==字符数组==;
    • 字符串字面量就是这个数组的数组名,==是一个char *类型的指针==。
    • 字符串字面量是==不能被修改的==
  • 创建一个二维数组,按照每行一个字符串(==一维字符数组==)的方式,将一系列字符串存入一个数组中。
  • 示例:
char planets[][8] = { "Mercury", "Venus", "Earth",
					  "Mars", "Jupiter", "Saturn",
					  "Urans", "Neptune", "Pluto" };
  • 因为每一行字符串的长度是省略的,所以每一行的长度是系统自动确定的。在二维数组中,每一行的数组的长度是根据最长的数组的长度来确定的。如果有一些行的数据不够填满整行,那么C语言会用空字符\0来填补,造成了空间浪费
  • 可以理解为用这种方式创建的二维数组都是矩形的

指针数组实现字符串数组

  • 要想实现参差不齐的二维数组,需要使用元素为指针的数组
  • 建立一个元素都是指向字符串的指针的数组,来实现字符串数组。
  • 示例:
char *planets[] = { "Mercury", "Venus", "Earth",
					"Mars", "Jupiter", "Saturn",
					"Urans", "Neptune", "Pluto"};

字符串数组的应用:命令行参数

  • 运行程序是需要提供一些信息,这些信息从命令行中提供,称为命令行参数(C语言中也叫程序参数)。
  • 为了访问命令行参数必须将main函数定义为含有两个特殊参数的的函数
    • argc参数:argc是参数计数的参数,为int型,用于记录命令行参数的数量
    • argv参数:argv是指向命令行参数的指针数组,这些命令行参数以字符串的形式存储。
      • argv是char *argv[]型的,实质就是一个字符串数组。
      • argv[0]指向程序名
      • argv[1]argv[argc - 1]指向余下的命令行参数。
      • argv[argc]是一个附加元素,这个元素始终是一个空指针
  • 示例:
#include <stdio.h>
#include <string.h>

#define NUM_PLANETS 9

int main(int argc, char* argv[]) {
	char *planets[] = { "Mercury", "Venus", "Earth",
						"Mars", "Jupiter", "Saturn",
						"Urans", "Neptune", "Pluto" };
	int i, j;

	for (i = 1; i < argc; i++) {
		for (j = 0; j < NUM_PLANETS; j++) {
			if (strcmp(argv[i], planets[j]) == 0) {
				printf_s("%s is planet %d\n", argv[i], j + 1);
				break;
			}
				if (j == NUM_PLANETS) {
					printf_s("%s is not a planet\n", argv[i]);
				}
		}
	}
	return 0;
}

动态存储分配

  • 动态存储分配:在程序==运行期间==分配内存单元的能力
  • 内存分配函数所获得的内存块都来自一个称为**堆(heap)**的存储池。
  • 内存分配函数有三种,都是声明在<stdlib.h>头中的:
    1. malloc函数:分配内存块,但是不对内存块进行初始化。(最常用,最高效)
      • 原型:void *malloc(size_t size),malloc函数分配size个字节的内存块,并且返回指向该内存块的指针。
      • 示例:int *a = (n * sizeof(int));,为长度为n的int型数组分配空间。
    2. calloc函数:分配内存块,并且对内存块进行清零。
      • 原型:void *calloc(size_t nmemb, size_t size);,calloc函数为nmemb个元素的数组分配内存空间,其中每个元素的长度都是size个字节。
      • 在分配了内存之后,calloc函数会==通过把所有位设置为0的方式进行初始化==。
      • 将nemeb设为1,可以为==任何类型的数据项分配内存==。
      • 示例:int *a = calloc(n, sizeof(int)),为长度为n的int型数组分配空间。
    3. realloc函数:调整先前分配的内存块大小。
      • 原型:void *realloc(void *ptr, size_t size);,调用realloc函数时,==ptr必须指向先前通过malloc,calloc或realloc的调用获得的内存块==。size表示内存块的新尺寸。
      • 如果realloc==以空指针作为第一个实际参数,那么它的行为就行malloc函数一样==。
      • 如果realloc==以0作为第二个实际参数,那么它会释放掉内存块==。
  • 内存分配函数的返回值
    • void *:内存分配函数会返回void *类型的值,该类型的指针是==通用指针==,指向分配的内存。
      • 可以将==void *类型的变量赋给任意类型的指针变量。
  • 空指针
    • 当找不到需要的足够大的内存块时,内存分配函数会返回==空指针==;
    • 空指针是==不指向任何地方的指针==,这是一个用于区别于所有有效指针的特殊值。
    • 测试内存分配函数的返回值是否是空指针,用来==判断内存分配是否成功==。
    • NULL宏:空指针用NULL宏来表示。NULL宏在多个头(包括<stido.h><stdlib.h>)中都有定义。
  • 释放存储空间
    • free()函数
      • 原型:void free(void *ptr)
      • ==调用free函数会释放掉ptr指针指向的内存块==
      • free函数的实际参数必须是==先前由内存分配函数返回的指针==。

指向函数的指针

  • 如果在程序中定义了一个函数,那么在编译时系统就会为这个函数代码分配一段存储空间,这段==存储空间的首地址==称为这个函数的地址,而且==函数名表示的就是这个地址==。
  • 函数指针;用一个指针变量来存储函数的地址。
  • 函数指针的声明
    • 声明模板:函数返回值类型 (*函数指针变量名)(函数参数类型列表);
    • 示例:int (*p)(int,int);
  • 函数指针调用函数
    • 通过*运算符来取出实际的函数。也可以直接通过函数指针变量名调用;
    • 示例:
    int fun(int x);//声明函数
    int (*p)(int);//声明函数指针
    p = fun; //函数指针指向fun函数
    int result = (*p)(5);//调用函数
    //或者
    int result = p(5);
  • 存储函数指针的指针数组
    • 可以声明元素为函数指针的数组:void (*funs[10])(int);