C语言 12 指针初阶

简介: C语言 12 指针初阶

头文件

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include <stdio.h>
#include <limits.h>
#include <ctype.h>
#include <math.h>


//**********************************指针***********************************/

1.指针是一个地址 指针是内存中的一个最小单元的编号,也就是地址

2.口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量


内存单元编号 == 地址 == 指针

指针就是地址,但是口头语中的指针一般是指针变量

int main()
{
  int* p;//创建一个指针变量
  int a = 10;//创建一个a变量 向内存中的栈区空间申请4个字节的空间 这4个字节用来存放10这个数值
  //在内存中开辟一块空间
    //内存中每个字节都是地址
  int *pa = &a;//这里我们对变量a,取出它的地址,可以使用&操作符,表示pa指向的是a,a的类型是int
  //a变量占用4个字节的空间,这里是将a的4个字节的第一个字节的地址存放在p变量中,p就是一个指针变量 
  //存放在指针变量中的值都会被当作地址处理
  *pa = 0x00112233;
  return 0;
}


存放在指针中的任何值在未来使用时都被当成地址处理,每个字节都有自己的地址

一个内存单元的大小是1个字节 产生二进制序列的地址

32位机器-->32根地址线

1/0

00000000000000000000000000000000

00000000000000000000000000000001

00000000000000000000000000000010

00000000000000000000000000000011

.............

01111111111111111111111111111111

10000000000000000000000000000000

10000000000000000000000000000001

.............

11111111111111111111111111111111

有2的32个次方字节的空间 等于4个gb

429967296字节 / 1024 = KB

194304KB / 1024 = 4GB

早期电脑上内存有几个GB就足够储存了


在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储

所以一个指针变量的大小就应该是4个字节

在64位的机器上,地址是64个0或者1组成二进制序列,那地址就得用8个字节的空间来存储

所以一个指针变量的大小就应该是8个字节,才能存放一个地址


1.内存被划分为一个个的内存单元,每一个内存单元的大小是一个字节

2.每一个字节的内存单元都有一个编号,这个编号就是地址,地址在C语言中称为指针

3.地址要存储的话 ,存放在指针变量中

4.每个内存单元都有一个唯一的地址来标识

在32位机器上地址的大小是4个字节,所以指针变量的大小也是4个字节

在64位机器上地址的大小是8个字节,所以指针变量的大小也是8个字节


产生地址不需要存起来,只需要存储成为指针的地址

写法:int *p,*q; / int* p,q; 都可以

int main()
{
  int a = 10;
  int* pa = &a;
  *pa = 20;
  printf("a = %d\n", a);
  return 0;
}


//*****************************指针和指针类型*****************************/


int main()
{
  printf("%d\n", sizeof(char*));
  printf("%d\n", sizeof(short*));
  printf("%d\n", sizeof(int*));
  printf("%d\n", sizeof(double*));
  //只要是指针变量,大小都只能是:x86是4,x64是8
  //指针的类型不同类型的指针用于存放不同类型的变量
  return 0;
}
int main()
{
  int a = 0x11223344;
  int* pa = &a;
  printf("%d\n", *pa);
  *pa = 0;
  printf("%d\n", *pa);
  return 0;
}
int main()
{
  int a = 0x11223344;
  char* p = &a;//类型有警告
  char* p = (char*) & a;
  //*p = 0;
  printf("%d", *p);
  return 0;
}  //指针类型是有意义的,指针类型决定了指针进行解引用操作的时候,访问几个字节
int* 4个字节 char* 1个字节 short* 2个字节
int main()
{
  int a = 0;
  int* pa = &a;
  char* pc = (char*) & a;
  printf("pa = %p\n", pa);
  printf("pa+1=%p\n", pa + 1); //int *+1类型跳过4个字节 因为是int*类型
  printf("pc = %p\n", pc);
  printf("pc+1=%p\n", pc + 1);//char *类型+1跳过1个字节 因为是char*类型
  return 0;
}


总结:1.指针的类型决定了指针向前或者向后走一步有多大的距离,指针类型不同,指针+-1向前或向后距离不同

     2.指针的类型决定了对指针解引用的时候有多大的权限(能操作几个字节)

     eg、char*的指针解引用只能访问一个字节,int*的指针解引用能访问四个字节


int main()
{
  int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
  printf("%d\n", arr[5]);
  double l = 11.4;
  double* pa = &l;
  printf("%p\n", *pa);
  printf("%p\n", *pa + 1);
  return 0;
 }
int main()
{
  int arr[10] = { 0x11223344,0x11223344,0x11223344,0x11223344,0x11223344,
    0x11223344,0x11223344,0x11223344,0x11223344,0x11223344 };
  int* p = arr;
  int i = 0;
  for (i = 0; i < 10; i++)
  {
    *p = 0;
    p++;
  }
  return 0;
}


char*类型指针一次只修改1个字节

short*类型指针一次修改2个字节

int*类型指针一次修改4个字节

10次循环改变了4*字节数


指针类型 用什么类型的指针访问什么样类型的数据 怎样变化 怎样访问


野指针:指针指向的位置是不可知的(随机的,不正确的,没有明确限制的)


int main()
{
  int* p = (int*)0x11223344;
  *p;//随机找了个地址,没有确定目标对象
  return 0;
}

野指针成因:

1.指针未初始化

int main()
{
  int* p;//使用了未初始化的指针 当一个变量不进行初始化的时候 给的地址是随机值 随机值不能作为一个地址
  *p = 10;
  return 0;
}


2.指针越界访问  无法读取内存 局部变量arr的内存被破坏了


int main()
{
  int arr[10] = { 0 };
  int* p = arr;
  int i = 0;
  for (i = 0; i < 11; i++)
  {
    *p = -1;
    p++;
  }
  return 0;
}


3.指针指向的空间释放


int* test()
{
  int a = 48; //局部变量,有自己的地址
  return &a;
}
int main()
{
  //0x0012ff40
  int* p = test();
  //*p是野指针 非法访问
  printf("%d\n", *p);//返回了地址,但是占空间回收了,所以p是野指针,一旦访问就是非法访问
  printf("%p\n", &p);
  return 0;
}


如何避免野指针

1.指针初始化 如果明确指针应该只想哪里,就初始化正确的地址

 不知道指针初始化什么值,为了安全放NULL——0

 放一个空指针,不会让指针乱跑


2.小心指针越界

int main

{

    int *p = NULL;//养成好的习惯

    return 0;

}

3.指针指向空间释放,及时置NULL


4.避免返回局部变量的地址


5.指针使用之前检查有效性

指针没有明确对象,如果不是一个空指针,就大概率合法,NULL本质是0,0作为地址时,这个地址用户程序不能访问


如果p不是空指针,大概率可以使用,c++中NULL可以为0,NULL专门赋值指针


指针运算


1.指针+-整数

int main()
{
  int arr[] = { 1,2,3,4,5,6,7,8,9,10 };
  //使用指针打印数组的内容
  int* p = arr;//数组首元素的地址
  //arr -> p
  //arr == p
  //arr+i == p+i
  //*(arr+i) == *(p+i) == arr[i]
  //*(arr+i) == arr[i]
  //*(i+arr) == i[arr] 数组括号内括号外两元素可以省略
  for (int i = 0; i < 10; i++)
  {
    printf("%d ", *(p + i));//整形指针一次跳过一个元素
    printf("%d ", i[arr]);//数组名是首元素的地址,当知道首元素地址时,可以访问数组所有元素
    printf("%d ", arr[i]);
    printf("%d ", *(arr + i));
    printf("%d ", *(p + i));
    //以上五种方法,本质是通过首元素的地址加上偏移量,找到数组中的内容
    //指针+-整数:通过首元素地址加减偏移量,找到数组中的元素 
    //指针+-整数:指针的关系运算
    //p指向的是数组首元素的地址
    //p+i指向的是数组中下标为i的元素的地址
  }
  return 0;
}

指针+-整数:指针的关系运算

int main()
{
  float values[N_VALUES];
  float* vp;
  for (vp = &values[0]; vp < &values[N_VALUES];)//指针的关系运算
  {
    *vp++ = 0;//指针加减整数
  }
  for (vp = &values[N_VALUES]; vp > &values[0];)//指针的关系运算
  {
    --vp = 0;//指针加减整数
  }
  return 0;
}


C语言标准规定:允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较

但是不允许与指向前一个元素之前的那个内存位置的指针进行比较


指针-指针

int main()
{
  int arr[10] = { 0 };
  //指针-指针的前提是两个指针类型必须相同,两个指针指向同一块区域
  //指针-指针得到的是指针和指针之间的元素个数
  printf("%d\n", &arr[9] - &arr[0]);//指针-指针
  printf("%d\n", &arr[0] - &arr[9]);//小地址减去大地址
  return 0;
}

模拟实现了strlen

1.计数器

2.递归

size_t my_strlen(char* str)
{
  char* start = str;
  while (*str != '\0')//'\0'是一个字符 while(*str)
  {
    str++;
  }
  return str - start;//指针-指针
}
int main()
{
  char arr[] = "abcdef";
  size_t len = my_strlen(arr);
  printf("%zd\n", len);
  return 0;
}

指针和数组

指针就是指针,指针变量是一个变量,存放的地址,指针变量的大小是4/8

数组就是数组,可以存放一组数,数组的大小是取决于元素的类型和个数

数组的数组名是数组首元素的地址,地址可以访问指针变量中

int main()
{
  int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
  printf("%p\n", arr);
  printf("%p\n", &arr[0]);
  return 0;
}

绝大多数情况,数组名表示数组首元素的地址

两个例外:

1.sizeof(数组名),数组名单独放在sizeof内部,数组名表示整个数组,计算的是数组的大小,单位是字节

2.&数组名,数组名表示整个数组,取出的是数组的地址,数组的地址和数组首元素的地址,值是一样的,但是类型和意义不一样

 

int main()
{
  int arr[10] = { 0 };
  printf("%p\n", arr);//int*
  printf("%p\n", arr + 1);//跳过4个字节
  printf("%p\n", &arr[0]);//int*
  printf("%p\n", &arr[0] + 1);//跳过4个字节
  printf("%p\n", &arr);//类型不同,所以+1操作跳过字节数不同
  printf("%p\n", &arr + 1);
  return 0;
}
int main()
{
  int arr[10] = { 0 };
  int* p = arr;
  int i = 0;
  for (i = 0; i < 10; i++)
  {
    printf("%p === %p\n", arr + i, p + i);//两地址相同
  }
  return 0;
}

因为数组在内存中连续存放,所以通过指针访问数组名进行偏移,可以访问数组的元素

int main()
{
  int arr[10] = { 0 };
  int* p = arr;
  int i = 0;
  for (i = 0; i < 10; i++)
  {
    *(p + i) = i;
  }
  for (i = 0; i < 10; i++)
  {
    printf("%d ", *(arr + i));
  }
  return 0;
}

指针是常量的地址,指针可以++,数组不能++

int main()
{
  int arr[10] = { 0 };
  int* p = arr;
  p++;
  return 0;
}

二级指针

int main()
{
  int a = 10;
  int* p = &a;//p是指针变量,也叫一级指针变量
  int** pp = &p;//pp是指针变量,也叫二级指针变量
    //int *是类型 p是指针变量
  int*** ppp = &pp;//ppp是指针变量,也叫三级指针变量
  printf("%p\n", p);
  printf("%p\n", *p);
  printf("%p\n", pp);
  printf("%p\n", *(*pp));
  printf("%p\n", ppp);
  printf("%p\n", *(*(*ppp)));//解引用a
  printf("%d\n", *(*(*ppp)));//a = 10;
  return 0;
}

7.指针数组

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


字符数组:存放字符 char arr[7];

整形数组:存放整形 int arr[6];

指针数组:存放指针 char* arr[5];double* arr2[5];

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 };
  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");
  }
  return 0;
}


目录
相关文章
|
7月前
|
存储 人工智能 Java
一文轻松拿捏C语言的指针的基础使用
本文介绍了C语言中的指针概念,包括直接访问和间接访问内存的方式、指针变量的定义与使用、取址运算符`&`和取值运算符`*`的应用,帮助读者深入理解指针这一C语言的核心概念。君志所向,一往无前!
135 0
|
9月前
|
存储 NoSQL 编译器
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
指针是一个变量,它存储另一个变量的内存地址。换句话说,指针“指向”存储在内存中的某个数据。
299 7
【C语言】指针的神秘探险:从入门到精通的奇幻之旅 !
|
9月前
|
存储 编译器 C语言
【C语言】指针大小知多少 ?一场探寻C语言深处的冒险 !
在C语言中,指针的大小(即指针变量占用的内存大小)是由计算机的体系结构(例如32位还是64位)和编译器决定的。
1063 9
|
9月前
|
安全 程序员 C语言
【C语言】指针的爱恨纠葛:常量指针vs指向常量的指针
在C语言中,“常量指针”和“指向常量的指针”是两个重要的指针概念。它们在控制指针的行为和数据的可修改性方面发挥着关键作用。理解这两个概念有助于编写更安全、有效的代码。本文将深入探讨这两个概念,包括定义、语法、实际应用、复杂示例、最佳实践以及常见问题。
257 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