C语言之指针进阶篇(1)

简介: C语言之指针进阶篇(1)

引言

今天学习指针的进阶版,那还是回顾一下我们前面学习过的指针知识。


  • 内存单元是有编号,编号 == 地址 == 指针
  • 指针变量就是个变量,用来存放地址,地址唯一标识一块内存空间。
  • 指针(地址/指针)的大小是固定的4/8个字节(32位平台/64位平台)。
  • 指针变量的大小是4/8个字节。
  • 指针有类型,指针的类型决定了指针的+-整数的步长,指针解引用操作的时候的权限。
  • 指针运算。


字符指针

%c   打印字符
%s   打印字符串

在指针初阶中我们学习过指针的类型,我们知道有一种指针类型为字符指针char*

#include<stdio.h>
int main()
{
  char c = 'x';
  char ch = "abcdef";
  char* pc = &ch;//存放字符串首元素的地址,也就是'a'的地址
  char* p = &c;//存放字符的地址
  printf("%c", *p);
  return 0;
}

除了上面的传统使用方法,我们还有另外一种使用方法:

#include<stdio.h>
int main()
{
  char* pc = "abcdef";
  printf("%c\n", "abcdef"[0]);
  printf("%s\n", pc);
  printf("%c", *pc);
  return 0;
}

  • char * pc = "abcdef"本质是把字符串"abcdef"首地址放到了字符指针变量pc中。
  • "abcdef"是表达式,值就是a的地址。
  • 一个常量字符串的首地址+%s打印=字符串所有内容(根据首地址遍历字符串所有内容)
  • "abcdef"也可以理解为数组名 == 相当于 数组的首地址被存放。
  • *pc是只能访问一个字符。

但是上面的使用存在一定的风险,什么风险呢?

"abcdef"是常量字符串表达式,"常量"是不能改变的。

常量字符串是不能被修改的。

那怎么解决呢?

加上const即可。

#include<stdio.h>
int main()
{
  const char* pc = "abcdef";//不会被修改了
  printf("%s\n", pc);
  *pc = "g";
  return 0;
}

练习——剑指offer

#include <stdio.h>
int main()
{
  char str1[] = "tangsiqi";
  char str2[] = "tangsiqi";
  char* str3 = "tangsiqi";
  char* str4 = "tangsiqi";
  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");
  if (&str3 == &str4)
    printf("str3 and str4 are same\n");
  else
    printf("str3 and str4 are not same\n");
  return 0;
}


  • str1和str2是在内存中开辟了不同的空间去存放字符串。
  • str3和str4是内存中不同的空间指向了同一个字符串的首地址。

指针数组

在前面我们学习过指针数组以及它的使用(模拟二维数组的作用)。


指针数组是指针还是数组呢?


是数组啦。是存放指针的数组。


字符数组——存放字符的数组


整型数组——存放整型的数组


指针数组——存放指针(地址)的数组,存放在数组中的元素都是指针类型的。


//例如:char * arr[5];   int * ch[6];


//arr[5]表明是数组

模拟二维数组

//使用指针数组,模拟一个二维数组
#include<stdio.h>
int main()
{
  int arr1[] = { 1,2,3,4,5 };
  int arr2[] = { 2,3,4,5,6 };
  int arr3[] = { 3,4,5,6,7 };
     //指针数组
  int* arr[] = { arr1,arr2,arr3 };
               //0   1     2
  int i = 0;
  int j = 0;
  for (i = 0; i < 3; i++)//找到首元素
  {
    for (j = 0; j < 5; j++)//遍历每一个数组的元素
    {
      printf("%d ", arr[i][j]);
    }
    printf("\n");
  }
  return 0;
}

除了上面整型指针数组,还有字符指针数组。

#include<stdio.h>
int main()
{
  char* arr[5] = { "tangsiqi","xueguodi","haha","hehe","xiaoxiao" };
  int i = 0;
  for (i = 0; i < 5; i++)
  {
    printf("%s\n", arr[i]);
  }
  return 0;
}

int * arr1[10];//整形指针数组
char * arr2[4];//一级字符指针的数组
char * *arr3[5];//二级字符指针的数组

数组指针

数组指针是指针?还是数组?

指针。

指针数组——是数组,是存放指针的数组。

数组指针——是指针,指向数组的指针。

字符指针——指向字符的指针。

整型指针——指向整型的指针。

浮点型的指针——指向浮点型的指针。


int a=10;
int * pa=&a;
char b='x';
char * pb=&b;

那么数组指针怎么写呢?

数组指针的定义

定义:数组指针是指针,指向数组的指针。 //type_t (* p) [const_n];

int arr[10];
int *p1[10];
int (*p2)[10];

上面那个代码是指针数组,那个是数组指针?p1,p2分别代表什么?

int arr[10];
int *p1[10];//指针数组
int (*p2)[10];//数组指针
//解释:
//p先和*结合,说明p是一个指针变量,然后指着指向的是一个大小为10个整型的数组。
//所以p是一个指针,指向一个数组,叫数组指针。
//这里要注意:[]的优先级要高于*号的,所以必须加上()来保证p先和*结合。

&数组名vs数组名

//数组名 vs &数组名
// &数组名是整个数组的地址
//数组名是数组首元素的地址
//除了两个例外:
//1.sizeof(数组名),这里的数组名表示整个数组,sizeof(数组名)计算的是整个数组的大小,单位是字节
//2.&数组名,这里的数组名表示整个数组名,取出的是数组的地址。
#include<stdio.h>
int main()
{
  int arr[10];
  printf("%d\n", arr);//int*
  printf("%d\n", arr + 1);
  printf("%d\n", &arr);//这个是什么类型?
  printf("%d\n", &arr + 1);
    //指针类型决定了指针+1,到底+几个字节
  return 0;
}

根据上面代码运行结果我们发现,虽然&arr和arr值是一样的,但是意义应该是不一样的。


实际上:&arr表示的是数组的地址;arr表示的是数组首元素的地址。


数组的地址+1,跳过的整个数组的大小,所以&arr+1相对于&arr的差值是40


int*p[10]=arr    +1跳过4个字节


int(*p)[10]=&arr     +1跳过10个元素每个元素为int的数组也就是 40个字节那应该怎样存放呢?

#include<stdio.h>
int main()
{
  int arr[10];
  int(*p)[10] = &arr;
  //p是用来存放整个数组的地址,p就是数组指针
  char* arr2[5];
  char* (*pc)[5] = &arr2;
  return 0;
}

int (*p) [10];

//*p表明是指针

// 数组指针指向数组10个元素,每个元素的类型是int

特别提醒: 以下写法均是错误的❌

int arr[10];
❌int (*p)[]=&arr;//不能省略
❌int [10]*p=&arr;

数组指针的使用

一维数组使用

一维数组的arr数组名就是首元素地址_用指针接收
int *p =arr;
一维数组的&arr取地址数组名是整个数组的地址_用数组指针接收
int (*p)[10]=&arr;
//一维数组使用
#include<stdio.h>
int main()
{
  int arr[5] = { 1,2,3,4,5 };
  int(*p)[5] = &arr;
//值是放到p里面,不是*p,值是&arr
//&arr是整个数组的地址
  int i = 0;
  for (i = 0; i < 5; i++)
  {
    printf("%d ", (*p)[i]);//p[i]❌
                    //(*&arr) ==arr——*和&可以抵消
  }
}
//一维数组传参
#include<stdio.h>
void print(int(*p)[5])
{
  int i = 0;
  for (i = 0; i < 5; i++)
  {
    printf("%d", (*p)[i]);
  }
}
int main()
{
  int arr[5] = { 1,2,3,4,5 };
  print(&arr);
  return 0;
}

把数组arr的地址赋值给数组指针变量p,但是我们一般很少这样写代码。

二维数组使用

二维数组的arr是首元素地址,就是第一行数组(一维数组)整个的地址
用数组指针来接受
int(*p)[][5]=arr;
//此时不需要取地址
//二维数组
#include<stdio.h>
int main()
{
  int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
  int(*p)[5] = arr;//二维数组数组名 ==首元素 == 二维数组的第一行
  int i = 0;
  for (i = 0; i < 3; i++)
  {
    int j = 0;
    for (j = 0; j < 5; j++)
    {
      printf("%d", p[i][j]);
    }
    printf("\n");
  }
  return 0;
}
//二维数组
#include<stdio.h>
void print(int(*p)[5], int row, int col)//本质就是二维数组的首元素地址==第一行地址
//void print(int arr[3][5],int row,int col)
{
  int i = 0;
  for (i = 0; i < 3; i++)
  {
    int j = 0;
    for (j = 0; j < 5; j++)
    {
      printf("%d", arr[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} };
  print(arr, 3, 5);
  return 0;
}

上面我们指针数组涉及到一维数组传参和二维数组传参的点我们来学习下。(传arr首元素地址)

一维数组传参

//数组形式
#include<stdio.h>
void print(int arr[])
//void peintf(int arr[10])也可
{
  int i = 0;
  for (i = 0; i < 10;i++)
  {
    printf("%d ", arr[i]);//*(arr+i)
  }
}
int main()
{
  int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
  print(arr);
  return 0;
}
//指针形式
#include<stdio.h>
void print(int* arr)
{
  int i = 0;
  for (i = 0; i < 10; i++)
  {
    printf("%d ", arr[i]);
  }
}
int main()
{
  int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
  print(arr);
  return 0;
}

二维数组传参

//二维数组传参
//数组形式
#include<stdio.h>
void print(int arr[3][5],int row,int col)
{
  int i = 0;
  for (i = 0; i < 3; i++)
  {
    int j = 0;
    for (j = 0; j < 5; j++)
    {
      printf("%d", arr[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} };
  print(arr, 3, 5);
  return 0;
}
//指针形式
#include<stdio.h>
void print(int (*arr)[3][5], int row, int col)//✔
//void print(int (*arr)[5], int row, int col)✔
//void print(int (*arr)[][5], int row, int col)❌——四不像
//void print(int arr[][5], int row, int col)✔
{
  int i = 0;
  for (i = 0; i < 3; i++)
  {
    int j = 0;
    for (j = 0; j < 5; j++)
    {
      printf("%d ", (*arr)[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} };
  print(arr, 3, 5);
//&arr——首元素地址的地址
  return 0;
}

总结

学了指针数组和数组指针我们来看下下面代码的意思:


  • arr是一个能够存放5个整型数据的数组
  • parr1是一个指针数组,数组10个元素,每个元素的类型是int*
  • parr2是一个数组指针,指向数组,指向的数组有10个元素,每个元素的类型是int
  • parr3是一个数组,数组有10个元素,存放10个元素都是数组指针,数组指针是指针,    指向的数组有5个元素,每个元素是int类型。  



首先确定是指针还是数组——看符号的优先级,变量先与那个符号结合🆗🆗

把这部分除去,看剩下的就是变量的类型了。🆗🆗

数组参数

在写代码的时候难免要把【数组】或者【指针】传给函数,那函数的参数应该如何设计?


一维数组传参

#include <stdio.h>
void test(int arr[])//ok?✔
//不会创建一个新的数组,本质是指针所有[]里有无都可
{}
void test(int arr[10])//ok?✔
{}
void test(int *arr)//ok?✔
//本质是指针——首元素地址
{}
void test2(int *arr[20])//ok?✔
{}
void test2(int **arr)//ok?✔
//接收的是首元素地址的地址
{}
int main()
{
int arr[10] = {0};//数组
int *arr2[20] = {0};//指针数组
test(arr);//首元素地址
test2(arr2);//首元素_是指针的地址 
}

数组传参,参数是可以写成数组形式的,可以写成指针形式。

数组传参本质是:传递了数组首元素的地址。

二维数组传参

void test(int arr[3][5])//ok?✔
{}
void test(int arr[][])//ok?❌
{}
void test(int arr[][5])//ok?✔
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int *arr)//ok?❌
{}
void test(int* arr[5])//ok?❌
{}
void test(int (*arr)[5])//ok?✔
{}
void test(int **arr)//ok?❌
{}
int main()
{
int arr[3][5] = {0};
test(arr);//arr首元素地址,第一行(一维数组)整个数组的地址
}

二维数组传参行可以省略,列不能省略。

指针参数

一级指针传参

#include <stdio.h>
void print(int *p, int sz)//形式参数写成一级指针就可以了🆗
{
int i = 0;
for(i=0; i<sz; i++)
{
printf("%d\n", *(p+i));
}
}
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9};
int *p = arr;
int sz = sizeof(arr)/sizeof(arr[0]);
//一级指针p,传给函数
print(p, sz);
return 0;
}

思考:当一个函数的参数部分为一级指针的时候,函数能接受什么参数?

#include<stdio.h>
void test(int *p)
{
//
}
int main()
{
  int a=10;
  int * pa=&a;
  int arr[5];
//传参
  test(&a);//传整型变量的地址
  test(pa);//传整型的指针
  test(arr);//传整型一维数组的数组名
  return 0;
}

二级指针传参

#include <stdio.h>
void test(int** ptr)
{
printf("num = %d\n", **ptr);
}
int main()
{
int n = 10;
int*p = &n;
int **pp = &p;
test(pp);
test(&p);//传指针的地址_地址的地址
return 0;
}

思考:当函数的参数为二级指针的时候,可以接受什么参数?

#include<stdio.h>
void test(int **p)
{
//
}
int main()
{
   int n=10;
   int*p=&n;
   int **pp=&p;
   int* arr[5];//指针数组
//传参
   test(&p);
   test(pp);
   test(arr);
   return 0;
}

关于数组和指针以及它们传参相信大家有了深刻的理解,函数指针我们会在下篇博文讲到。🙂🙂

✔✔✔✔✔最后,感谢大家的阅读,若有错误和不足,欢迎大家指正!

代码-------→【gitee:https://gitee.com/TSQXG

联系-------→【邮箱:2784139418@qq.com】

目录
相关文章
|
7月前
|
存储 人工智能 Java
一文轻松拿捏C语言的指针的基础使用
本文介绍了C语言中的指针概念,包括直接访问和间接访问内存的方式、指针变量的定义与使用、取址运算符`&`和取值运算符`*`的应用,帮助读者深入理解指针这一C语言的核心概念。君志所向,一往无前!
136 0
|
9月前
|
存储 NoSQL 编译器
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
指针是一个变量,它存储另一个变量的内存地址。换句话说,指针“指向”存储在内存中的某个数据。
299 7
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
|
9月前
|
存储 编译器 C语言
【C语言】指针大小知多少 ?一场探寻C语言深处的冒险 !
在C语言中,指针的大小(即指针变量占用的内存大小)是由计算机的体系结构(例如32位还是64位)和编译器决定的。
1066 9
|
9月前
|
安全 程序员 C语言
【C语言】指针的爱恨纠葛:常量指针vs指向常量的指针
在C语言中,“常量指针”和“指向常量的指针”是两个重要的指针概念。它们在控制指针的行为和数据的可修改性方面发挥着关键作用。理解这两个概念有助于编写更安全、有效的代码。本文将深入探讨这两个概念,包括定义、语法、实际应用、复杂示例、最佳实践以及常见问题。
258 7
|
10月前
|
存储 算法 程序员
C 语言指针详解 —— 内存操控的魔法棒
《C 语言指针详解》深入浅出地讲解了指针的概念、使用方法及其在内存操作中的重要作用,被誉为程序员手中的“内存操控魔法棒”。本书适合C语言初学者及希望深化理解指针机制的开发者阅读。
|
10月前
|
存储 NoSQL 编译器
C 语言中指针数组与数组指针的辨析与应用
在C语言中,指针数组和数组指针是两个容易混淆但用途不同的概念。指针数组是一个数组,其元素是指针类型;而数组指针是指向数组的指针。两者在声明、使用及内存布局上各有特点,正确理解它们有助于更高效地编程。
|
10月前
|
存储 程序员 编译器
C 语言数组与指针的深度剖析与应用
在C语言中,数组与指针是核心概念,二者既独立又紧密相连。数组是在连续内存中存储相同类型数据的结构,而指针则存储内存地址,二者结合可在数据处理、函数传参等方面发挥巨大作用。掌握它们的特性和关系,对于优化程序性能、灵活处理数据结构至关重要。
|
10月前
|
算法 C语言
C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项
本文深入讲解了C语言中的文件操作技巧,涵盖文件的打开与关闭、读取与写入、文件指针移动及注意事项,通过实例演示了文件操作的基本流程,帮助读者掌握这一重要技能,提升程序开发能力。
588 3
|
10月前
|
存储 C语言 计算机视觉
在C语言中指针数组和数组指针在动态内存分配中的应用
在C语言中,指针数组和数组指针均可用于动态内存分配。指针数组是数组的每个元素都是指针,可用于指向多个动态分配的内存块;数组指针则指向一个数组,可动态分配和管理大型数据结构。两者结合使用,灵活高效地管理内存。
|
10月前
|
程序员 C语言
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门
C语言中的指针既强大又具挑战性,它像一把钥匙,开启程序世界的隐秘之门。本文深入探讨了指针的基本概念、声明方式、动态内存分配、函数参数传递、指针运算及与数组和函数的关系,强调了正确使用指针的重要性,并鼓励读者通过实践掌握这一关键技能。
195 1