C语言学习笔记——指针

1.指针的基本介绍

  1. 指针,也就是内存的地址;所谓指针变量,也就是保存了内存地址的变量
  2. 获取变量的地址,用&。例如:&num
  3. 指针类型,指针变量存的是一个地址这个地址指向的空间存的才是值。比如:int *ptr = # ptr 就是指向 int 类型的指针变量, 即 ptr 是 int * 类型。
  4. 获取指针类型所指向的值,使用:*(取值符号),比如:int * ptr , 使用*ptr 获取 ptr 指向的值

2.什么是指针

指针是一个变量,其值为另一个变量的地址,即内存位置的直接地址。就像其他变量或常量一样,在使用指针存储其他变量地址之前,对其进行声明。指针变量声明的一般形式为

int *ip;/* 一个整型的指针 */
double *dp;/* 一个 double 型的指针 */
float *fp;/* 一个浮点型的指针 */
char *ch;/* 一个字符型的指针 */

3.指针的算术运算

指针是一个用数值表示的地址。可以对指针执行算术运算。可以对指针进行四种算术运算:++、--、+、-

3.1.指针递增操作(++)

#include
void main() {
	int arr[3] = { 10, 100, 1000 }, i = 0;
	int* ptr = arr;
	for (i = 0; i < 3; i++) {
		printf("数组%d的地址为:%p\n", i, ptr);
		printf("数组%d的内容为:%d\n", i, *ptr);
		ptr++;//在int类型中,基本单位为4byte,因此ptr++实际上是+4,指向下一个数组
	}
}

C语言学习笔记——指针_第1张图片

因为数组是在内存空间中是连续的,因此对指向数组空间的指针变量进行++(或者--)操作的时候,实际上是+(或者-)一个指针类型的单位大小的byte(例如:int为4byte,double为8byte),因此,它就会指向下一个(或者上一个)数组元素

3.2.指针递减操作

#include
void main() {
	int arr[3] = { 10, 100, 1000 }, i = 0;
	int* ptr = &arr[2];
	for (i = 2; i >= 0; i--) {
		printf("数组%d的地址为:%p\n", i, ptr);
		printf("数组%d的内容为:%d\n", i, *ptr);
		ptr--;//在int类型中,基本单位为4byte,因此ptr--实际上是-4,指向上一个数组元素
	}
}

C语言学习笔记——指针_第2张图片

3.3.指针+操作

#include
void main() {
	int arr[3] = { 10, 100, 1000 }, i = 0;
	int* ptr = arr;
	ptr += 1;//在int类型中,基本单位为4byte,因此ptr--实际上是-4,指向上一个数组元素
	printf("数组%d的地址为:%p\n", i, ptr);
	printf("数组%d的内容为:%d\n", i, *ptr);
}

C语言学习笔记——指针_第3张图片

 指针+(或-)操作,与指针++(或--)操作同理,只是它更能快速的定位到你想要的数组元素

3.4.指针-操作

#include
void main() {
	int arr[3] = { 10, 100, 1000 }, i = 0;
	int* ptr = &arr[2];
	ptr -= 1;//在int类型中,基本单位为4byte,因此ptr--实际上是-4,指向上一个数组元素
	printf("数组%d的地址为:%p\n", i, ptr);
	printf("数组%d的内容为:%d\n", i, *ptr);
}

C语言学习笔记——指针_第4张图片

4.指针的比较

指针可以用关系运算符进行比较,如 ==、<、 <= 和 > 、>=。如果 p1 和 p2 指向两个变量,比如同一个数组中的不同元素,则可对 p1 和 p2 进行大小比较

#include
void main() {
	int i = 0, arr[3] = { 10, 100, 100 };
	int* ptr = arr;
	//判断指针变量ptr内容,即其所指向的内存空间的地址
	//是否小于等于数组arr中第二个元素的地址
	//如果是,则进行printf
	while (ptr <= &arr[1]) {
		printf("arr[%d]的地址为:%p\tarr[%d]的内容为%d\n", i, ptr, i, *ptr);
		ptr++;
		i++;
	}
}

C语言学习笔记——指针_第5张图片

5.指针数组

5.1.指针数组的基本介绍

要让数组的元素指向 int 或其他数据类型的地址(指针)。可以使用指针数组

5.2.指针数组定义

数据类型 *指针数组名[大小];
  1. 例如:double *arr[7]
  2. 申明一个名为arr的double类型指针数组
  3. 其由七个double类型指针组成。因此,arr中的每个元素都是指向double类型的指针

5.3.指针数组代码示例和内存布局示意图

#include
void main() {
	int var[3] = { 10, 100, 1000 };
	int i;
	int* ptr[3];
	for (i = 0; i < 3; i++) {
		ptr[i] = &var[i];//将var数组元素的地址逐一赋值给相应下标的ptr指针数组
	}
	for (i = 0; i < 3; i++) {
		printf("var[%d] = %d\n", i, *ptr[i]);
	}
}

C语言学习笔记——指针_第6张图片

 C语言学习笔记——指针_第7张图片

  1. 申明一个名为var的int类型数组;申明一个名为ptr的指针数组,其每个元素都是指向int类型的指针
  2. 经过第一次循环遍历后,它的每个元素被逐一赋值卫var数组的地址
  3. 指针数组也具有它的内存空间和地址。如图所示,0x1122、0x1126、0x112A,即是ptr指针数组的元素的地址,它们的内容分别是0x1133、0x1137、0x113B,分别对应的是var整型数组中的每个数组元素的地址

5.4.指针数组练习

请编写程序,定义一个指向字符的指针数组来存储字符串列表(四大名著书名), 并通过遍历该指针数组,显示字符串信息  (即:定义一个指针数组,该数组的每个元素,指向的是一个字符串)

#include 
void main() {
	char* books[4] = { "三国演义", "西游记", "红楼梦", "水浒传" };
	int i;
	for (i = 0; i < 4; i++) {
		printf("\nbooks[%d] 指向字符串是=%s %p", i, books[i], &books[i]);
	}
}

C语言学习笔记——指针_第8张图片

6.指向指针的指针

6.1 基本介绍

指向指针的指针是一种多级间接寻址的形式,或者说是一个指针链。通常,一个指针包含一个变量的地址。当我们定义一个指向指针的指针时,第一个指针包含了第二个指针的地址,第二个指针指向包含实际值的位置C语言学习笔记——指针_第9张图片

设图中的Variable的Value为int类型,图中pointer1指向的是Variable,应该申明为 int* pointer1,则pointer2应该申明为int** pointer2

6.2.多重指针(二级,三级)代码示例

  1. 一个指向指针的指针变量必须如下声明,即在变量名前放置两个星号。例如,下面声明了一个指向 int 类型指针的指针:
    int **ptr; // ptr 的类型是 int **
  2. 当一个目标值被一个指针间接指向到另一个指针时,访问这个值需要使用两个星号运算符, 比如 **ptr(当一个目标值被一个指针间接指向时,访问这个值的指针前面需要加两个*)

  3. #include 
    int main() {
    	int var;
    	int* ptr; //一级指针 
    	int** pptr; //二级指针 
    	int*** ppptr; // 三级指针 
    	var = 3000;
    	ptr = &var; // var 变量的地址赋给 ptr 
    	pptr = &ptr;// 表示将 ptr 存放的地址,赋给 pptr
    	ppptr = &pptr; // 表示将 pptr 存放的地址,赋给 ppptr
    	printf("var 的地址 = %p\nvar = %d \n", &var, var);// 0x1133 3000
    	printf("\nptr 的本身的地址 = %p\nptr 存放的地址 = %p\n*ptr = %d \n", &ptr, ptr, *ptr);
    	printf("\npptr 本身地址 = %p\npptr 存放的地址 = %p\n**pptr = %d\n", &pptr, pptr, **pptr);
    	printf("\nppptr 本身地址 = %p\nppptr 存放的地址 = %p\n***pptr = %d\n", &ppptr, ppptr, ***ppptr);
    	return 0;
    }

    C语言学习笔记——指针_第10张图片

    C语言学习笔记——指针_第11张图片

7.传递指针(地址)给函数

当函数的形参类型是指针类型时,是使用该函数时,需要传递指针,或者地址,或者数组给该形参

7.1.传地址或指针给指针变量

#include
void test(int* p) {
	*p += 1;
}
void main() {
	int num = 90;
	int* p = #
	test(&num);
	printf("num = %d\n", num);
	test(p);
	printf("num = %d", num);
}

C语言学习笔记——指针_第12张图片 C语言学习笔记——指针_第13张图片

  1. test函数体需要传入一个int类型的指针,因此,不管是传入变量num的地址,还是指针p,都可以;变量num的地址为0x1122,指针p的内容为0x1122,因此,它们其实传入的是同一个地址
  2. 因为,test函数是对传入地址的内容进行改变,所以,它在执行后,虽然test栈被销毁,但是,它所改变num的值被保留了下来

7.2.传数组给指针变量

#include
double getAverage(int* arr, int size) {
	int i, sum = 0;
	double avg; 
	for (i = 0; i < size; ++i) {
		// arr[0] = arr + 0 
		// arr[1] = arr + 1 个 int 字节(4)
		// arr[2] = arr + 2 个 int 字节(8) //...
		sum += arr[i];// arr[0] =>数组第一个元素的地址 arr[1] 
		printf("\narr 存放的地址=%p ", arr);
		avg = (double)sum / size; 
		return avg;
	}
}
double getAverage2(int* arr, int size) {
	int i, sum = 0;
	double avg;
	for (i = 0; i < size; ++i) {
		sum += *arr;
		printf("\narr 存放的地址=%p ", arr);
		arr++;// 指针的++运算, 会对 arr 存放的地址做修改
	}
	avg = (double)sum / size;
	return avg;
}
int main() {
	/* 带有 5 个元素的整型数组 */ 
	int balance[5] = { 1000, 2, 3, 17, 50 }; 
	double avg;
	/* 传递一个指向数组的指针作为参数 */ 
	avg = getAverage(balance, 5);
	/* 输出返回值 */ 
	printf("Average value is: %f\n", avg);
	return 0;
}

C语言学习笔记——指针_第14张图片

C语言学习笔记——指针_第15张图片

  1. 在main函数中,设balance数组的首地址为(0x1122)。在执行到avg = getAverage(balance, 5)时,因为double getAverage(int* arr, int size)函数需要传入一个int类型指针变量和一个int类型变量,而getAverage(balance, 5)中balance代表的是balance数组的首地址,传入getAverage数组的是一个地址,相当于指针变量
  2. 在getAverage函数体中,对传入的balance数组的首地址进行一系列操作,而非在函数体内复制一份balance数组并进行操作!

8.返回指针的函数

8.1.返回指针的函数的介绍

C语言允许函数的返回值是一个指针(地址),这样的函数称为指针函数

8.2.代码示例

请编写一个函数 strlong(),返回两个字符串中较长的一个

//请编写一个函数 strlong(),返回两个字符串中较长的一个
#include
#include
char* strlong(char* str1, char* str2) {
	//通过strlen函数计算字符串的长度
	printf("第一个字符串的长度为%d,第二个字符串的长度为%d\n", strlen(str1), strlen(str2));
	if (strlen(str1) >= strlen(str2)) {//如果字符串1的长度大于等于字符串2的长度,则返回指向字符串1的指针
		return str1;
	}
	else {
		return str2;//else,则返回指向字符串2的指针
	}
}
int main() {
	char str1[20],  str2[20], * str;
	printf("请输入一个字符串:\n");
	gets_s(str1, sizeof(str1));
	printf("请输入一个字符串:\n");
	gets_s(str2, sizeof(str2));
	str = strlong(str1, str2);//用str字符指针指向strlong返回的指针
	printf("这两个字符串中,更长的那个字符串是:%s", str);
	return 0;
}

C语言学习笔记——指针_第16张图片

8.3.指针函数注意事项和细节

  1. 用指针作为函数返回值时需要注意,函数运行结束后会销毁在它内部定义的所有局部数据,包括局部变量、局部数组和形式参数,函数返回的指针不能指向这些数据
  2. 函数运行结束后会销毁该函数所有的局部数据,这里所谓的销毁并不是将局部数据所占用的内存全部清零,而是程序放弃对它的使用权限,后面的代码可以使用这块内存
    #include 
    int* func() {
    	int n = 100;//局部变量, 在 func 返回时,就会销毁
    	return &n;
    }
    int main() {
    	int* p = func(); //func 返回指针
    	int n;
    	n = *p;
    	printf("\nvalue = %d\n", n);
    	return 0;
    }

    C语言学习笔记——指针_第17张图片

    函数运行结束后,仅仅是放弃了对这块内存空间的使用权限,但是,这块内存空间的内容并没有被销毁,输出这块内存空间的内容,仍然有可能是原来的内容,但是,如果在执行多条语句后,这块内存空间就有可能被别的数据给覆盖,因此,返回的指针不能指向这些数据

  3. C 语言不支持在调用函数时返回局部变量的地址,如果确实有这样的需求,需要定义局部变量为 static 变量

    #include 
    int* func() {
    	static int n = 100; // 如果这个局部变量是 static 性质的,那么 n 存放数据的空间在静态数据区
    	return &n;
    }
    int main() {
    	int* p = func(); //func 返回指针
    	int n;
    	n = *p;
    	printf("\nvalue = %d\n", n);
    	return 0;
    }

    C语言学习笔记——指针_第18张图片

    C语言学习笔记——指针_第19张图片

    通过static int n =100;将100存入了静态存储区,返回的是静态存储区的地址,因此,在之后的main函数的使用中,仍然可以使用静态存储区中存储的100,而静态存储区不会被其他语句所占用

9.函数指针(指向函数的指针)

9.1 基本介绍

  1. 一个函数总是占用一段连续的内存区域,函数名在表达式中有时也会被转换为该函数所在内存区域的首地址(可以理解为把函数名当做函数的首地址,赋给函数指针)
  2. 把函数的这个首地址(或称入口地址)赋予一个指针变量,使指针变量指向函数所在的内存区域,然后通过指针变量就可以找到并调用该函数。这种指针就是函数指针。

9.2.函数指针定义

returnType (*pointerName)(param list);
  1. returnType为函数指针指向的函数返回值类型
  2. pointerName为函数指针名称
  3. param list为函数指针指向的函数的参数列表
  4. 参数列表中可以同时给出参数的类型和名称,也可以只给出参数的类型,省略参数的名称
  5. 注意( )的优先级高于*,第一个括号不能省略,如果写作 returnType *pointerName(param list);就成了函数原型, 它表明函数的返回值类型为 returnType *(变成返回指针的函数)

9.3.函数指针代码示例

用函数指针来实现对函数的调用, 返回两个整数中的最大值

#include 
//说明 //1. max 函数 //2. 接收两个 int ,返回较大数 
int max(int a, int b){ 
	return a>b ? a : b;
}
int main() {
	int x, y, maxVal;
	//说明 函数指针 
	//1. 函数指针的名字 pmax 
	//2. int 表示 该函数指针指向的函数是返回 int 类型
	//3. (int, int) 表示 该函数指针指向的函数形参是接收两个 int 
	//4. 在定义函数指针时,也可以写上形参名 int (*pmax)(int x, int y) = max;
	int (*pmax)(int, int) = max;
	printf("Input two numbers:\n");
	scanf_s("%d %d", &x, &y);
	// (*pmax)(x, y) 通过函数指针去调用 函数max
	maxVal = (*pmax)(x, y);//可以把这个语句中的*去掉
	printf("Max value: %d pmax = %p 本身的地址=%p\n", maxVal, pmax , &pmax);
	return 0;
}

C语言学习笔记——指针_第20张图片

 C语言学习笔记——指针_第21张图片

  1. 申明一个名为*pmax函数返回值类型为int的函数指针,它需要指向的函数需要传入两个int类型的变量,它指向max函数
  2. pmax函数指针的地址为0x1133,max函数在内存空间(代码区)的首地址为0x1122,因此,它所指向的内存空间(代码区)中max函数的首地址为0x1122
  3. 调用函数的方式可以是(*pmax)(int)(int),也可以使(pmax)(int)(int),前提是前面必须申明过(1)中的调用函数类型

10.回调函数

10.1.回调函数的基本介绍

  1. 函数指针变量可以作为某个函数的参数来使用的,回调函数就是一个通过函数指针调用的函数
  2. 回调函数是由别人的函数执行时调用你传入的函数(通过函数指针完成)(回调函数就是一个通过函数指针调用函数的函数,嵌套函数中的内层函数)

10.2回调函数的代码示例

使用回调函数的方式,给一个整型数组 int arr[10] 赋 10 个随机数

#include  
#include 
// 回调函数 
// //1.int (*f)(void)
//2. f 就是 函数指针 , 它可以接收的函数是 (返回 int ,没有形参的函数) 
//3. f 在这里被 initArray 调用,充当了回调函数角色
void initArray(int* array, int arraySize, int (*f)(void)) {
	int i;
	//循环 10 
	for ( i=0; i

C语言学习笔记——指针_第22张图片

11.指针的注意事项和细节

  1. 指针变量存放的是地址,从这个角度看指针的本质就是地址
  2. 变量声明的时候,如果没有确切的地址赋值,为指针变量赋一个 NULL 值是好的编程习惯
  3. 赋为 NULL 值的指针被称为空指针,NULL 指针是一个定义在标准库 中的值为零的常量 #define NULL 0 

12.动态内存分配
12.1.C程序中,不同数据在内存中分配说明:

  1. 全局变量——内存中的静态存储区
  2. 非静态的局部变量——内存中的动态存储区——stack 栈
  3. 临时使用的数据——建立动态内存分配区域,需要时随时开辟,不需要时及时释放——heap 堆
  4. 根据需要向系统申请所需大小的空间,由于未在声明部分定义其为变量或者数组,不能通过变量名或者数组名 来引用这些数据,只能通过指针来引用)(堆)

12.2 内存动态分配的相关函数

  1. 头文件 #include 声明了四个关于内存动态分配的函数

  2. 函数原型 void* malloc(usigned int size) //memory allocation 取m和alloc字母组成。

    1. 作用——在内存的动态存储区(堆区)中分配一个长度为 size 的连续空间

    2. 形参 size 的类型为无符号整型,函数返回值是所分配区域的第一个字节的地址(首地址,即此函数是一个指针型函数, 返回的指针指向该分配域的开头位置

      malloc(100);//开辟100 字节的临时空间,返回值为其第一个字节的地址
  3. 函数原型 void* calloc(unsigned n,unsigned size)

    1. 作用——在内存的动态存储区中分配 n 个长度为 size 的连续空间,这个空间一般比较大,足以保存一个数组;函数返回指向所分配域的起始位置的指针;

    2. 分配不成功,返回NULL。

      p = calloc(50, 4); //开辟 50*4 个字节临时空间,把起始地址分配给指针变量 p
  4. 函数原型:void free(void *p)

    1. 作用——释放变量 p 所指向的动态空间,使这部分空间能重新被其他变量使用

    2. p 是最近一次调用 calloc 或malloc 函数时的函数返回值

    3. free 函数无返回值

      free(p); // 释放 p 所指向的已分配的动态空间
  5. 函数原型 void *realloc(void *p,unsigned int size)

    1. 作用——重新分配malloc 或 calloc 函数获得的动态空间大小,将 p 指向的动态空间大小改变为 size,p 的值不变,分配失败返回NULL

    2. 可以增大或者减小已经分配malloc或者calloc获得的空间大小

      realloc(p, 50); // 将 p 所指向的已分配的动态空间 改为 50 字节
  6. 返回类型说明

    1. C 99标准把以上malloc、calloc、realloc函数的基类型定为void类型。这种指针称为无类型指针(typeless pointer),即不指向哪一种具体的类型数据。只表示用来指向一个抽象的类型的数据,即仅提供一个纯地址,而不能指向任何具体的对象(例如:与指向整型变量的指针相区别,整型指针提供一个地址,该地址指向一个整形变量;而无类型指针只提供地址)

    2. c99允许使用基类型为void的指针类型。可以定义一个基类型为void的指针变量(即void*型变量),它不指向任何类型的数据。请注意:不要把“指向void类型”理解为能指向“任何的类型”的数据,而应理解为“指向空类型"或“不指向确定的类型”的数据。在将它的值赋给另一指针变量时由系统对它进行类型转换,使之适合于被赋值的变量的类型。(void*类型指针不能用*p的方式取值)例如:

      #include
      void main() {
      	int a = 3;//定义a为整型变量
      	int* p1 = &a;//定义一个指向int类型的指针变量p1,并指向int型变量
      	char* p2;//定义一个指向char类型的指针变量p2
      	void* p3; //定义一个无类型的指针变量p3(也可以理解为类型为void型)
      	p3 = (void*)p1;//将p1的值转换为void*类型,然后赋值给p3(可以理解为强制类型转换)
      	p2 = (char*)p3;//将p3的值转换换cha*类型,然后赋值给p2
      	printf("%d", *p1);//合法,输出a的值
      	p3 = &a;//只有在C99下才能这么写,相当于p3 = (void*)&a
      	printf("%d", *p3);//不合法,p3是无指向的,不能指向a
      }

      C语言学习笔记——指针_第23张图片

       

12.3.动态内存分配代码示例

动态创建数组,输入 5 个学生的成绩,另外一个函数检测成绩低于 60 分的,输出不合格的成绩

#include
#include
void check(int *p) {
	int i;
	printf("\n不及格的成绩有:");
	for (i = 0; i < 5; i++) {
		if (p[i] < 60) {
			printf("\n%d", p[i]);
		}
	}
}
void main() {
	int* p, i;
	//在堆区开辟一个5 * 4的空间,并将地址(void*),转成(int*),赋给p
	p = (int*)malloc(5 * sizeof(int));
	for (i = 0; i < 5; i++) {
		scanf_s("%d", p + i);
	}
	check(p);
	free(p);//销毁p的空间
}

C语言学习笔记——指针_第24张图片

12.4.动态分配内存的基本原则

  1. 避免分配大量的小内存块。分配堆上的内存有一些系统开销,所以分配许多小的内存块比分配几个大内存块的系统开销大
  2. 仅在需要时分配内存。只要使用完堆上的内存块,就需要及时释放它(如果使用动态分配内存,需要遵守原则: 谁分配,谁释放), 否则可能出现内存泄漏(用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元。直到程序结束。
  3. 总是确保释放以分配的内存。在编写分配内存的代码时,就要确定在代码的什么地方释放内存
  4. 在释放内存之前,确保不会无意中覆盖堆上已分配的内存地址,否则程序就会出现内存泄漏。在循环中分配内存时,要特别小心
  5. 指针使用一览C语言学习笔记——指针_第25张图片

 

你可能感兴趣的:(C/C++,c语言,开发语言,后端,c++,c#)