C语言学习笔记——指针(二)

简介: C语言学习笔记——指针

5.指针数组

5.1.指针数组的基本介绍

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

5.2.指针数组定义

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

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

#include<stdio.h>
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]);
  }
}

6b0f560849dd4ccc9a74ce9470150525.png

d2539341de084b5fb389f44dd4ad9c99.png

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

5.4.指针数组练习

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

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

59883857c583461b8546cc50af31d7d8.png

6.指向指针的指针

6.1 基本介绍

指向指针的指针是一种多级间接寻址的形式,或者说是一个指针链。通常,一个指针包含一个变量的地址。当我们定义一个指向指针的指针时,第一个指针包含了第二个指针的地址,第二个指针指向包含实际值的位置

d3f96fe0d3024b8d888590483a4bb616.png

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

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

  1. 一个指向指针的指针变量必须如下声明,即在变量名前放置两个星号。例如,下面声明了一个指向 int 类型指针的指针:
int **ptr; // ptr 的类型是 int **

2.当一个目标值被一个指针间接指向到另一个指针时,访问这个值需要使用两个星号运算符, 比如 **ptr(当一个目标值被一个指针间接指向时,访问这个值的指针前面需要加两个*)

#include <stdio.h>
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;
}

dfabdc24aeab4e2eb63858413ee05dbd.png

3.d6ce2ab79d66422bacffe8ed228c29ac.png

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

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

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

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

0b339e3e12544d8ea3b6183b62336d29.png


11b5e7dee78045a58f74f00f1d513c92.png

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

7.2.传数组给指针变量

#include<stdio.h>
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;
}

60927534e4bc4369a5ad12016446bc0d.png

fab7ec7130a04cc38e6fde01643e5a0a.png

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<stdio.h>
#include<string.h>
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;
}

9bc1e55897b64820974b0fe0acff02dc.png

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

  1. 用指针作为函数返回值时需要注意,函数运行结束后会销毁在它内部定义的所有局部数据,包括局部变量、局部数组和形式参数,函数返回的指针不能指向这些数据
  2. 函数运行结束后会销毁该函数所有的局部数据,这里所谓的销毁并不是将局部数据所占用的内存全部清零,而是程序放弃对它的使用权限,后面的代码可以使用这块内存
#include <stdio.h>
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;
}

2912d20a8e8d4efb86b553385fc06650.png

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

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

#include <stdio.h>
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;
}

3338c3b92d494d0681e87aa8a8b1f410.png

015c17b76c58403bbaee1935844af74e.png

通过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 <stdio.h>
//说明 //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;
}

500282ffab4d4afe97d4606605c58865.png

5dd97cc8a21c4d5692c6067223ac0ef0.png

  1. 申明一个名为*pmax函数返回值类型为int的函数指针,它需要指向的函数需要传入两个int类型的变量,它指向max函数
  2. pmax函数指针的地址为0x1133,max函数在内存空间(代码区)的首地址为0x1122,因此,它所指向的内存空间(代码区)中max函数的首地址为0x1122
  3. 调用函数的方式可以是(*pmax)(int)(int),也可以使(pmax)(int)(int),前提是前面必须申明过(1)中的调用函数类型
相关文章
|
3天前
|
存储 C语言
【C语言基础】一篇文章搞懂指针的基本使用
本文介绍了指针的概念及其在编程中的应用。指针本质上是内存地址,通过指针变量存储并间接访问内存中的值。定义指针变量的基本格式为 `基类型 *指针变量名`。取地址操作符`&`用于获取变量地址,取值操作符`*`用于获取地址对应的数据。指针的应用场景包括传递变量地址以实现在函数间修改值,以及通过对指针进行偏移来访问数组元素等。此外,还介绍了如何使用`malloc`动态申请堆内存,并需手动释放。
|
6天前
|
存储 人工智能 C语言
C语言程序设计核心详解 第八章 指针超详细讲解_指针变量_二维数组指针_指向字符串指针
本文详细讲解了C语言中的指针,包括指针变量的定义与引用、指向数组及字符串的指针变量等。首先介绍了指针变量的基本概念和定义格式,随后通过多个示例展示了如何使用指针变量来操作普通变量、数组和字符串。文章还深入探讨了指向函数的指针变量以及指针数组的概念,并解释了空指针的意义和使用场景。通过丰富的代码示例和图形化展示,帮助读者更好地理解和掌握C语言中的指针知识。
|
12天前
|
存储 安全 C语言
C语言 二级指针应用场景
本文介绍了二级指针在 C 语言中的应用,
|
27天前
|
存储 编译器 C语言
【C语言篇】深入理解指针2
代码 const char* pstr = "hello world."; 特别容易让初学者以为是把字符串 hello world.放 到字符指针 pstr ⾥了,但是本质是把字符串 hello world. 首字符的地址放到了pstr中。
|
27天前
|
存储 程序员 编译器
【C语言篇】深入理解指针1
assert.h 头⽂件定义了宏 assert() ,⽤于在运⾏时确保程序符合指定条件,如果不符合,就报错终⽌运⾏。这个宏常常被称为“断⾔”。
|
30天前
|
存储 搜索推荐 C语言
C语言中的指针函数:深入探索与应用
C语言中的指针函数:深入探索与应用
|
26天前
|
存储 C++
c++学习笔记06 指针
C++指针的详细学习笔记06,涵盖了指针的定义、使用、内存占用、空指针和野指针的概念,以及指针与数组、函数的关系和使用技巧。
29 0
|
27天前
|
C语言
【C语言】指针速览
【C语言】指针速览
15 0
|
1月前
|
C语言
【C初阶——指针5】鹏哥C语言系列文章,基本语法知识全面讲解——指针(5)
【C初阶——指针5】鹏哥C语言系列文章,基本语法知识全面讲解——指针(5)
|
3天前
|
存储 Serverless C语言
【C语言基础考研向】11 gets函数与puts函数及str系列字符串操作函数
本文介绍了C语言中的`gets`和`puts`函数,`gets`用于从标准输入读取字符串直至换行符,并自动添加字符串结束标志`\0`。`puts`则用于向标准输出打印字符串并自动换行。此外,文章还详细讲解了`str`系列字符串操作函数,包括统计字符串长度的`strlen`、复制字符串的`strcpy`、比较字符串的`strcmp`以及拼接字符串的`strcat`。通过示例代码展示了这些函数的具体应用及注意事项。