指针

基础知识

*星号:在C语言中表示乘号,也可以表示指针符号。

&取地址符号:加载变量的前面可以取变量地址。

左值:在赋值运算符左边,表示变量名所代表的内存空间

右值:在赋值运算符的右边,表示变量名所代表的内存空间中存放的值

什么是指针?

答:指针本质上就是一个变量,完整的名字应该是指针变量,简称指针。一个指针变量存储的是一个内存地址(32位系统为4字节)。

为什么需要指针?

答:指针的存在是为了实现间接访问。间接访问(间接寻址)是CPU设计时候决定的,所以决定了汇编必须能实现间接寻址,在汇编之上的C语言也必须能实现间接寻址。对于更高级的语言诸如C#、java这类语言帮我们封装了,所以没有指针也可以实现间接寻址。

指针变量的使用步骤:定义指针变量、关联指针变量、解引用。

#include<stdio.h>

int main(void)

{
  int a=3;
  int *p;    //定义指针变量
  p = &a;   //关联指针变量
  *p =5    //解引用
}

在函数种定义指针变量时,遵循局部变量的一般规律。也就是如果定义时没有初始化p的值,则值是随机的(上次使用后未清0的值)。因此就引出了野指针的问题。

什么是野指针?

野指针是指指针所指向的位置是不可知的。在函数内部定义指针变量时,没有对变量初始化就会出现野指针的情况。这时候指针变量p的值是随机的,也就是指针指向的变量是随机的。可能出现如下情况,第一:指向一个不可访问的地址。(程序会触发段错误)

                 第二:指向一个可用的、而且没什么特别意义的空间。(程序不报错,但实际上错了。相当于错误被掩盖了)

                 第三:情况就是指向了一个可用的空间,而且这个空间其实在程序中正在被使用。(导致程序出现离奇错误)

如何避免野指针?

举例:

  int *p=NULL;  //第一点:定义指针时,同时初始化为NULL

  P = &a;     //第二点:在指针使用之前,将其赋值绑定给一个可用地址空间(假设之前已定义了一个变量a)

  if(NULL != P)  //第三点:在指针解引用之前,先去判断这个指针是不是NULL

  { 

    *P = 4;    

  }

  P = NULL;   //第四点:指针使用完之后,将其赋值为NULL

一般的,在中小型程序中,自己水平可以把握的情况下,不必严格参照这个标准;但是在大型程序,或者自己水平感觉不好把握时,建议严格参照这个方法。    

NULL是什么?

#ifdef _cplusplus     // 定义这个符号就表示当前是C++环境
#define NULL 0      // 在C++中NULL就是0
#else
#define NULL (void *)0 // 在C中NULL是强制类型转换为void *的0
#endif

const关键字与指针

第一种:const int *p;    //指针变量p指向的是一个int类型的常量
第二种:int const *p;    //指针变量p指向的是一个int类型的常量
第三种:int * const p;    //指针变量p本身是一个常量,指向的那个值是个int型变量
第四种:const int * const p;  //指针变量p是个常量,它指向的是一个int型常量

数组中几个关键符号(a a[0] &a &a[0])的理解

(1)a:数组名。a做左值时表示整个数组的所有空间,a不能做左值;a做右值表示数组首元素的首地址<==>&a[0];
(2)a[0]:数组的首元素,也就是数组的第0个元素。做左值时表示数组第0个元素对应的内存空间(连续4字节);做右值时表示数组第0个元素的值;
(3)&a:就是数组名a取地址,字面意思来看就应该是数组的地址。&a不能做左值;&a做右值时表示整个数组的首地址。
(4)&a[0]字面意思就是数组第0个元素的首地址。不能做左值,做右值时表示数组首元素的地址。

数组与指针

(1)数组元素使用时不能整体访问,只能单个访问。访问方式有2种:数组形式和指针形式。
(2)数组格式访问数组元素是:数组名[下标]; (注意下标从0开始)
(3)指针格式访问数组元素是:*(指针+偏移量); 如果指针是数组首元素地址(a或者&a[0]),那么偏移量就是下标;指针也可以不是首元素地址而是其他哪个元素的地址,这时候偏移量就要考虑叠加了。
(4)数组下标方式和指针方式均可以访问数组元素,两者的实质其实是一样的。在编译器内部都是用指针方式来访问数组元素的,数组下标方式只是编译器提供给编程者一种壳(语法糖)而已。所以用指针方式来访问数组才是本质的做法。

举例:

int a[5] = {1, 2, 3, 4, 5};
int *p;
p = a;
printf("*(p+1) = %d.
", *(p+1));

类型匹配

char *p;  int a[5];  p = a;    类型匹配,这里的a做右值表示&a[0]是数组首元素首地址
int *p;   int a[5];   p = &a;  类型不匹配,这里&a做右值,a本身表示整个数组,取地址符&加在前面表示数组的地址

define与typedef关键字

#define dpChar char *
typedef char *tpChar;

dpChar p1, p2; sizeof(p1) sizeof(p2)
tpChar p3, p4; sizeof(p3) sizeof(p4)

变量传参实际上只是把值赋值了一份传进去,也就是传说中的传值调用

void func1(int a)

{
printf("a=%d.
",a);
}

int main(void)
{
  int a=4;
  func1(a);
  return 0;
}

 函数传参

(1)数组名作为函数形参传参,实际传递的是整个数组首元素的首地址,因为传参为传值。void func(int a[]){}

(2)指针作为函数形参传参,和数组作为函数形参的实现方式一样。void func1(int *a){}

(3)结构体变量作为函数形参,和普通变量传参时表现一样。由于结构体很大我们通常也是传地址的

一般地函数传参的时候,分为输入性参数和输出型参数,我们通常在输入性参数加上const 关键字,表明他不需要更改。

 (本文内容参考了朱有鹏C语言高级专题)

原文地址:https://www.cnblogs.com/jxjl/p/6942074.html