『C语言进阶』指针进阶(一)

简介: 『C语言进阶』指针进阶(一)

前言

C语言初阶中,我们对指针有了一定的了解,指针是个变量,是用来存放地址的,指针的大小是固定的4/8个字节,指针是有类型的,指针的类型决定了指针的±整数的步长以及指针的运算,小羊最近已经开学,博客可以正常更新了,好啦,接下来让我们再进一步的了解指针!!


一、字符指针

我们可以定义一个字符指针,指向一个字符变量,并通过指针来修改这个字符变量。

例1:

#include<stdio.h>
int main()
{
  char ch = 'x';
  char* p = &ch;//pc是字符指针
  ch = 'a';
  *p = 'a';
  return 0;
}
//ch是字符变量,可以直接改变ch的值,
//pc这时是字符指针,存放的是ch的地址,也可以通过指针来改变变量ch的值。

例2:

#include<stdio.h>
int main()
{
  char arr[] = "abcdefg";//创建数组,用字符串来初始化
  char* p = "abcdefg";//常量字符串,"abcdefg"存放在"常量区",只读,不允许被修改
  //指针变量p存放的是字符串首元素的地址
  *p='a';//erro 这种情况会报错,pa不能改变为w
}

例3:这段代码运行结果是什么?

int main()
{
  char* s = "abcdefg";
  for (int i = 0; i < 4; i++)
  {
    *(s + i) = '0';
  }
  printf("%s\n", s);
  return 0;
}

结果:

运行错误,原因是"abcdefg"是常量字符串,它们存放在"常量区",只读,不允许被修改,所以一般写成const char* s="abcdefg"

笔试题:

int main()
{
  char str1[] = "hello bit.";
  char str2[] = "hello bit.";
  const char* str3 = "hello bit.";
  const char* str4 = "hello bit.";
  if (str1 == str2)
    printf("str1 and str2 are same\n");
  else
    printf("str1 and str2 are not same\n");
  if (str3 == str4)
    printf("str3 and str4 are same\n");
  else
    printf("str3 and str4 are not same\n");
  return 0;
}

结果分析:

str1和str2是两个字符数组,两个数组是相互独立的,在创建时,会各自向空间申请空间,所以地址是不一样的,只是内存中两块不同内存区域存放着相同内容而已,故str1!=str2

str3和str4里面存放的地址是同一个,都是h的地址,所以是一样的


二、指针数组

2.1 认识指针数组

C语言初阶里的初识指针中,铁汁们对指针数组也有一定的了解,指针数组是一个存放指针的数组。

整形数组--存放整形的数组

字符数组--存放字符的数组

指针数组--存放指针的数组

#include<stdio.h>
int main()
{
  int arr1[3];  //存放了三个整形的数组 int int int
  int* arr2[3]; //存放了三个整形指针的数组 int* int* int*
  char* arr3[3];//存放了三个一级字符指针的数组 char* char* char*
  char** arr4[3];//存放了三个二级字符指针的数组 char** char** char**
  return 0;
}

牛刀小试:

int main()
{
  char* arr[] = { "abcd","efgh","igkl" };
  int sz = sizeof(arr) / sizeof(arr[0]);
  for (int i = 0; i < sz; i++)
    printf("%s ", arr[i]);
  return 0;
}

arr是首元素地址

2.2 使用指针数组模拟二维数组

#include<stdio.h>
int main()
{
  int arr1[4] = { 1,2,3,4 };
  int arr2[4] = { 2,3,4,5 };
  int arr3[4] = { 3,4,5,6 };
  int arr4[4] = { 4,5,6,7 };
  int* arr[4] = { arr1,arr2,arr3,arr4 };
  int sz = sizeof(arr) / sizeof(arr[0]);
  for (int i = 0; i < sz; i++)
  {
    for (int j = 0; j < 4; j++)
    {
      printf("%d ", *(arr[i] + j));
    }
    printf("\n");
  }
  return 0;
}

结果分析:

经过调试内存可得:
  int arr1[4] = { 1,2,3,4 };              //arr1的地址是:0x0000001C526FF7F8
  int arr2[4] = { 2,3,4,5 };              //arr2的地址是:0x0000001C526FF828
  int arr3[4] = { 3,4,5,6 };              //arr3的地址是:0x0000001C526FF858
  int arr4[4] = { 4,5,6,7 };              //arr4的地址是:0x0000001C526FF888
由此可见,arr1,arr2,arr3,arr4数组内存中有独立的内存空间,并不是连续的四个内存空间,
我们将这四个一维数组的首元素的地址放在指针数组arr中,通过指针数组来访问这四个一维数组,效果和二维数组是一样的,但是并不是真正的二维数组!!

三、数组指针

3.1 数组指针的定义

定义:指向数组的指针被称为数组指针。

由于指针数组和数组指针对于初学者很容易混肴,所以我们先通过类比的方法来认识数组指针。

字符指针–指向字符变量的指针,即存放字符变量地址的指针变量。

整型指针–指向整型变量的指针。即存放整型变量地址的指针变量

数组指针–指向数组变量的指针。即存放数组变量地址的指针变量

#include<stdio.h>
int main()
{
  int a = 2;
  int* p = &a; 
  int arr[10]; 
  int* arr[10];  //arr先和[10]结合,所以arr是一个数组,里面存的都是指针变量,即为指针数组
  int(*arr)[10];//arr先和*结合所以arr是一个指针变量,又指向数组,即为指针数组
  return 0;
}

解释:arr先和*结合,说明arr是一个指针变量,然后指向一个大小为10个整型的数组(指向的类型 int [10])。所以arr是一个指针,指向一个数组,叫数组指针。

注意:[ ]的优先级要高于*号的,所以必须加上()来保证arr先和*结合。

3.2 &数组名VS数组名

  • 通常情况下,数组名是首元素地址
  • sizeof(数组名):计算的是整个数组的大小,sizeof内部单独放一个数组名,数组名表示整个数组;
  • &数组名:取出的是数组的地址,&数组名,数组名表示整个数组;
#include<stdio.h>
int main()
{
  int arr[5] = { 0,1,2,3,4 };
  printf("  arr=%p\n", arr);
  printf("arr+1=%p\n", arr + 1);
  printf("\n");
  printf("  &arr[0]=%p\n", &arr[0]);
  printf("&arr[0]+1=%p\n", &arr[0] + 1);
  printf("\n");
  printf("  &arr=%p\n", &arr);
  printf("&arr+1=%p\n", &arr + 1);
  return 0;
}

3.3 数组指针的使用

数组指针指向的是数组,那数组指针中存放的数组的地址

#include<stdio.h>
int main()
{
  int arr[5];
  int(*p)[5]=&arr;//p的类型是int(*)[5],存放的是存放int类型的数组
  int* pp[5];//指针数组,pp是数组,存放的是int*类型
  int* (*ppp) = &pp;//ppp的类型是int*(*)[5],存放的是存放int*类型的数组
  return 0;
}

3.3.1 打印数组元素:

#include<stdio.h>
int main()
{
  int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }, i = 0;
  int* p = arr;
  int(*pp)[10] = &arr;
  int sz = sizeof(arr) / sizeof(arr[0]);
  for (i = 0; i < sz; i++)
  {
    printf("%d ", *(p + i));
  }
  printf("\n");
  for (i = 0; i < sz; i++)
  {
    printf("%d ", *((*pp) + i));
  }
  return 0;
}

3.3.2 打印二维数组元素:

#include<stdio.h>
void Print1(int arr[3][5], int row, int col)
{
  for (int i = 0; i < row; i++)
  {
    for (int j = 0; j < col; j++)
    {
      printf("%d ", arr[i][j]);
    }
    printf("\n");
  }
}
void Print2(int(*p)[5], int row, int col)
{
  for (int i = 0; i < row; i++)
  {
    for (int j = 0; j < col; j++)
    {
      printf("%d ", *(p[i] + j));
    }
    printf("\n");
  }
}
int main()
{
  int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
  Print1(arr, 3, 5);
  printf("\n");
  Print2(arr, 3, 5);
  return 0;
}

3.4 总结:

数组名arr,表示首元素的地址,二维数组的首元素是二维数组的第一行所以这时传递的arr,其实相当于第一行的地址,是一维数组的地址可以数组指针来接收。

补充:

一维数组传参,形参的部分可以是数组,也可以是指针

二维数组传参,形参的部分可以是数组,也可以是指针

注意:形参写成数组形式是为了让人更容易理解,本质上是指针


四、数组传参和指针传参

4.1 一维数组传参

数组传参的时候,形参可以写成同样数组形式,若是用数组作形参,[ ]里面的值可以省略,也可以随意赋值。传过来的arr是数组首元素地址,所以也可以用一级整形指针来接受。

#include<stdio.h>
void test1(int arr[]);//正确
void test2(int arr[10]);//正确
void test3(int* arr);//正确
int main()
{
  int arr1[10] = { 0 };
}

实战演练:

#include<stdio.h>
void test1(int arr[],int sz)
{
  for (int i = 0; i < sz; i++)
  {
    printf("%d ", arr[i]);
  }
}
void test2(int arr[10],int sz)
{
  for (int i = 0; i < sz; i++)
  {
    printf("%d ", arr[i]);
  }
}
void test3(int* arr, int sz)
{
  for (int i = 0; i < sz; i++)
  {
    printf("%d ", *(arr+i));
  }
}
int main()
{
  int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
  int sz = sizeof(arr) / sizeof(arr[0]);
  test1(arr, sz);
  printf("\n");
  test2(arr, sz);
  printf("\n");
  test3(arr, sz);
  return 0;
}

4.2 二维数组传参

void test(int arr[3][5])//ok?
{}
OK
void test(int arr[][])//ok?
{}
NO  二维数组传参,函数形参的设计只能省略行数,不能省略列数
二位数组传参,传的是二维数组第一行的地址,而不是第一行第一个元素的地址
void test(int arr[][5])//ok?
{}
OK
void test(int* arr)//ok?
{}
NO
void test(int* arr[5])//ok?
{}
NO  这是一个存放int* 类型的数组
void test(int(*arr)[5])//ok?
{}
OK  这是一个存放数组的一级指针
void test(int** arr)//ok?
{}
NO
int main()
{
    int arr[3][5] = { 0 };
    test(arr);
}

4.3 一维指针传参

void test(int* p)
{}
int main()
{
  int n = 10;
  test(&n);
  int* p = &n;
  test(p);
  int arr[5] = { 0 };
  test(arr);
  return 0;
}

4.4 二维指针传参

void test(int** p)
{}
int main()
{
  int n = 10;
    int* p=&n;
  test(&p);
  int** pp = &p;
  test(pp);
  int* arr[5] = { 0 };
  test(arr);
  return 0;
}

本次的内容到这里就结束啦。希望大家阅读完可以有所收获,同时也感谢各位铁汁们的支持。文章有问题可以在评论区留言,小羊一定认真认真修改,以后写出更好的文章。

相关文章
|
4月前
|
C语言
【c语言】指针就该这么学(1)
本文详细介绍了C语言中的指针概念及其基本操作。首先通过生活中的例子解释了指针的概念,即内存地址。接着,文章逐步讲解了指针变量的定义、取地址操作符`&`、解引用操作符`*`、指针变量的大小以及不同类型的指针变量的意义。此外,还介绍了`const`修饰符在指针中的应用,指针的运算(包括指针加减整数、指针相减和指针的大小比较),以及野指针的概念和如何规避野指针。最后,通过具体的代码示例帮助读者更好地理解和掌握指针的使用方法。
90 0
|
2月前
|
存储 NoSQL 编译器
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
指针是一个变量,它存储另一个变量的内存地址。换句话说,指针“指向”存储在内存中的某个数据。
124 3
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
|
2月前
|
存储 编译器 C语言
【C语言】指针大小知多少 ?一场探寻C语言深处的冒险 !
在C语言中,指针的大小(即指针变量占用的内存大小)是由计算机的体系结构(例如32位还是64位)和编译器决定的。
179 9
|
2月前
|
安全 程序员 C语言
【C语言】指针的爱恨纠葛:常量指针vs指向常量的指针
在C语言中,“常量指针”和“指向常量的指针”是两个重要的指针概念。它们在控制指针的行为和数据的可修改性方面发挥着关键作用。理解这两个概念有助于编写更安全、有效的代码。本文将深入探讨这两个概念,包括定义、语法、实际应用、复杂示例、最佳实践以及常见问题。
64 7
|
3月前
|
存储 C语言
C语言如何使用结构体和指针来操作动态分配的内存
在C语言中,通过定义结构体并使用指向该结构体的指针,可以对动态分配的内存进行操作。首先利用 `malloc` 或 `calloc` 分配内存,然后通过指针访问和修改结构体成员,最后用 `free` 释放内存,实现资源的有效管理。
267 13
|
3月前
|
存储 C语言 开发者
C 语言指针与内存管理
C语言中的指针与内存管理是编程的核心概念。指针用于存储变量的内存地址,实现数据的间接访问和操作;内存管理涉及动态分配(如malloc、free函数)和释放内存,确保程序高效运行并避免内存泄漏。掌握这两者对于编写高质量的C语言程序至关重要。
90 11
|
3月前
|
存储 程序员 编译器
C 语言数组与指针的深度剖析与应用
在C语言中,数组与指针是核心概念,二者既独立又紧密相连。数组是在连续内存中存储相同类型数据的结构,而指针则存储内存地址,二者结合可在数据处理、函数传参等方面发挥巨大作用。掌握它们的特性和关系,对于优化程序性能、灵活处理数据结构至关重要。
|
3月前
|
算法 C语言
C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项
本文深入讲解了C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项,通过实例演示了文件操作的基本流程,帮助读者掌握这一重要技能,提升程序开发能力。
216 3
|
3月前
|
存储 算法 程序员
C 语言指针详解 —— 内存操控的魔法棒
《C 语言指针详解》深入浅出地讲解了指针的概念、使用方法及其在内存操作中的重要作用,被誉为程序员手中的“内存操控魔法棒”。本书适合C语言初学者及希望深化理解指针机制的开发者阅读。
|
3月前
|
程序员 C语言
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门。本文深入探讨了指针的基本概念、声明方式、动态内存分配、函数参数传递、指针运算及与数组和函数的关系,强调了正确使用指针的重要性,并鼓励读者通过实践掌握这一关键技能。
72 1