c语言指针

c语言指针

1. 内存和地址

1.1内存
  1. 首先讨论一下内存,电脑上的内存有 8G/16G/32G等,那么这么大的一个内存空间如何有效管理呢?
    • 其实就是把内存划分一个个内存单元(每个内存单元大小1个字节),一个字节(Byte)是8个比特(bit)
      • 一个比特(bit)可以放一个二进制(0/1),一个字节(Byte)就好比一个八人房,而每个八人房都有一个门牌号,这个门牌号在c语言中叫地址/内存单元编号/指针
    • 有了内存单元编号,就可以快速有效的找到内存单元进行管理。
1.2编址(了解即可)
  1. 首先,必须理解,计算机内是有很多的硬件单元,而硬件单元之间需要相互协同工作,所谓协同,至少相互之间可以进行数据传递,但硬件之间是相互独立的,需要”线“将彼此连接起来,硬件中CPU和内存也是有大量数据交互的,也必须用”线“连接起来,不过我们目前只需要关心一组”线“(地址总线)。

    1. CPU访问内存中某个字节空间,必须知道这个字节空间在内存什么位置,而内存中字节很多,所需要给内存进行编址,编址完后就有了指针
    • 我们如今计算机有32位,64位,32位计算机就是有32根地址总线,64位就是64根,每根线只能发出两种电脉冲,一高一低的脉冲,这个高低用1/0表示,32根就是有2^32种组合,每个组合都代表一个地址/指针

2.指针变量和地址

2.1指针变量演示与解释
#include<stdio.h>
int main()
{
	int a = 10;
	int *ptr = &a;//这里的int意思是ptr指向的对象a是int类型的,*是说明p是指针变量,int *是ptr的类型	
	return 0;
}

注意:指针变量就是用来存放地址的,存放在指针变量里的值,都会被当成地址使用。

#include<stdio.h>
int main()
{
	int a = 10;
	int *p =&a;
	*p = 0;//单独拿出 *和变量 这里的*就单纯代表解引用操作符/间接访问操作符,*p就是a
    printf("%d",a);//最后打印结果为0
	return 0;
}
2.2指针变量的大小
#include<stdio.h>
int main()
{
	int a = 10;
	int *p=&a;
	
	char ch='w';
	char *ptr=&ch;
	
	printf("%zd",sizeof(p));//在x64环境下大小为8个字节,x86环境下大小为4个字节
	printf("%zd",sizeof(ptr));//在x64环境下大小为8个字节,x86环境下大小为4个字节
	return 0;
}
  • 指针变量不管是char型、int型、或者double型等等,其大小都一样,x64 环境下都为8字节,x86 环境下都为4字节

  • 打印sizeof( )时,最好用%zd,%d不建议使用某些编译器会报警告。

  • sizeof( )的( )里可以放变量对应的类型也可放变量本身效果一样。

    • #include<stdio.h>
      int main()
      {
      	int a = 10;
      	int *p=&a;
      	printf("%zd",sizeof(p));//x64环境下输出结果为8,x86环境下输出结果为4
      	printf("%zd",sizeof(int*));//x64环境下输出结果为8,x86环境下输出结果为4
      	return 0;
      }
      
      
      
      
      

3.指针变量类型的意义

3.1 引言

由于指针变量大小都一样,有些人会疑惑指针变量没有意义啊,不管创建什么类型的指针变量大小都一样,存的又都是地址,那么接下来我简略来解释一下指针变量类型的意义。

3.2指针的解引用代码演示与解释
#include<stdio.h>
int main()
{
	int n = 0x11223344;
	int *p=&n;
	*p = 0;
    printf("%d",*p);//输出结果为0
	return 0;
}

如果把上段代码的int *改成char *如下所示:

#include<stdio.h>
int main()
{
	int n = 0x11223344;
	char *p=&n;
	*p = 0;
    printf("%d",*p);//输出结果不为0,输出的结果并不是你预期的0
	return 0;
}
3.3指针加减整数
#include<stdio.h>
int main()
{
	int n = 0x11223344;
    
	int *p=&n;
    char *pc=&n;
    
    printf("p = %p\n",p);
    printf("p+1 = %p\n",p+1);
    
    printf("pc = %p\n",pc);
    printf("pc+1 = %p\n",pc+1);
	return 0;
}

结果如下:

在这里插入图片描述

会发现char* 的指针大小加一后相差一个字节int*的指针大小加一后相差四个字节

  • 指针类型是有意义的
  • 指针类型决定了,指针进行+1/-1操作的时候,一次跳过几个字节
3.4指针类型这些特点的使用
  • 数组:1 2 3 4 5 6 7 8 9 10遍历一下数组

    • 没学指针前会这么遍历

      • #include<stdio.h>
        int main()
        {
        	int arr[]={1,2,3,4,5,6,7,8,9,10};
        	int i = 0;
        	int sz = sizeof(arr)/sizeof(arr[0]);
        	for(i=0;i<sz;i++)
        	{
        		printf("%d ",arr[i]);
        	}
        	return 0;
        }
        
        
    • 学指针后可以这么遍历

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

4. const修饰指针

4.1代码演示与解释
#include<stdio.h>
int main()
{
	//const 修饰变量,使得被修饰的变量不能被修改
	const int n=100;
	n=20; //error
	printf("%d/n",n);//无法打印,显示第六行出错
	return 0;
}

但是换成指针进行修改却可以成功,如下所示:

#include<stdio.h>
int main()
{
	const int n =100;
	int *p=&n;
	*p=20;
	printf("%d\n",n)//打印结果为20
	return 0;
}
  • 这是为什么呢?

    • 举个例子:一个人要进一个房间,结果这个房间把门被封死了,但这个人却从窗户爬进来了。
  • 那么如何防范这种”爬窗户“行为呢,如下所示:

    • //const修饰指针
      #include<stdio.h>
      int main()
      {
      	const int n =100;
      	const int *const p=&n;//const放在*左边,修饰的是指针指向的内容,const放在*右边,修饰的是指针变量的内容(int *const==const int *)
      	//通过第5、6行所示,就把所有洞都封死了,就绝对安全了
      	*p=20;
      	printf("%d\n",n)//打印结果为20
      	return 0;
      }
      

5.指针运算(三种基本运算)

5.1 指针 ± 整数
  • 代码演示及详解

    • //演示1
      //我们可以根据数组首地址得到各个元素的地址
      #include<stdio.h>
      int main()
      {
          int arr[]={1,2,3,4,5,6,7,8,9,10};
          int *p=&arr[0];
          int i=0;
          int sz=sizeof(arr)/sizeof(arr[0]);
          for(i=0;i<sz;i++)
          {
              printf("%d",*(p+i));//这里p+i==&arr[i]
          }
          return 0;
      }
      
    • //演示2
      #include<stdio.h>
      int main()
      {
          char arr[] ="abcdef";//arr数组内容有a b c d e f \0
          char *p=&arr[0];
         while(*p!='\0')
         {
             printf("%c ",*p);
             p++;
      	}
          return 0;
      }
      
5.2 指针 - 指针
  • 代码演示及及解释

    • #include<stdio.h>
      int main()
      {
          int arr[10]={0};
          int ret =&arr[9]-&arr[0];
          printf("%d",ret);//输出是9,指针-指针表示指针之间元素个数
      }
      
  • 利用指针-指针,粗略地模拟实现 strlen ( )函数

    • #include<stdio.h>
      int my_strlen(char *p)
      {
          char *start=p;
          while(*p!='\0')
          {
              p++; 
          }
          return p-start;
      }
      int main()
      {
          char arr[]="abcdef";
          int len=my_strlen(arr);
          printf("%d\n",len);
          return 0;
      }
      
5.3 指针的关系运算
  • 代码演示

    • #include<stdio.h>
      int main()
      {
          int arr[]={1,2,3,4,5,6,7,8,9,10};
          int sz=sizeof(arr)/sizeof(arr[0]);
          int *p=arr;//数组名也是首元素地址
          while(p<arr+sz)//括号里就是指针的关系运算
          {
              printf("%d",*p);
              p++;
          }
          return 0;
      }
      

6.野指针

概念:野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)个人理解就是指针指向的内容一定是非法的

6.1野指针可能造成的原因
  • #include<stdio.h>
    int main()
    {
        int *p;//未初始化造成的野指针
        p=20;
    	return 0;
    }
    
    
  • #include<stdio.h>
    int main()
    {
        int i=0;
        int arr[10]={0};
        int *p=arr;
        for(i=0;i<=11;i++)//越界访问造成的野指针,p超出数组范围就是野指针了
        {
            *p=i;
            p++;
        }
    	return 0;
    }
    
  • #include<stdio.h>
    int *test()
    {
        int n=100;
        return &n;
    }
    int main()
    {
       int *p=test();//由于函数栈帧的销毁导致空间释放造成的野指针
    	return 0;
    }
    
6.2如何避免野指针
6.2.1指针初始化
  • 如果你明确知道指针指向哪里就直接赋值地址,如果不知道指针指向哪里,可以给指针赋值NULL,NULL是c语言中定义的一个标识符常量,值是0,0也是地址,只不过0这个地址无法使用的,读写不了0这个地址(就是无法解引用0地址)

    • #include<stdio.h>
      int main()
      {
          int *p=NULL;//把NULL换成0也是可以的,但是不建议,因为所表达的意思就会有些偏差了
          return 0;
      }
      
6.2.2小心指针的越界

一个程序向内存申请了一些空间,通过指针也就只能访问了这些空间,不能超过范围访问,超出了就是越界访问。(通俗的将就是你只能在你申请的空间里使用指针

6.2.3指针变量不在使用时,及时置空(NULL),指针使用前一定检查其有效性
  • 演示

    • //演示1
      #include<stdio.h>
      int main()
      {
          int a = 10;
          int *p = &a;
          int *ptr=NULL;
          if(p != NULL)//使用前检查
          {
              //使用p
      	}
          if(ptr != NULL)//使用前检查
          {
              *ptr =100;//使用ptr
      	}
      	return 0;
      }
      
    • //演示2
      #include<stdio.h>
      int main()
      {
          int arr[10]={1,2,3,4,5,6,7,8,9,10};
          int *p=arr;
          int i=0;
          int sz=sizeof(arr)/sizeof(arr[0]);
          if(p != NULL)//检查一下
          {
              for(i=0;i<sz;i++)
          {
              printf("%d",*p);
              p++;
          }
          }
          p =NULL;//使用完后及时置空
          return 0;
      }
      
      
6.2.4避免返回局部变量(就是函数栈帧都销毁了你却还用它里面的值)

此问题也叫返回栈空间地址问题

7. assert 断言

#include<stdio.h>
int main()
{
    int a = 10;
    int *p=&a;
    if(p!=NULL)
    {
        //...
    }
    //如果每回指针都这么判断的话,难免有些冗杂,这时就出现了assert断言
    return 0;
}

assert.h 头文件定义了宏 assert() ,用于在运行时确保程序符合指定条件,如果不符合,就报错终止运行。这个宏常常被称为“断言”

assert(p!=NULL)//assert(p)这样写也行
  • #include<stdio.h>
    #include<assert.h>
    int main()
    {
        int a = 10;
        int *p=NULL;
        assert(p!=NULL);//此时程序会判断其指针的有效性,p为NULL报出错误并显示错误在第几行。
        return 0;
    }
    
  • assertif(p!=NULL) 强一些,因为assert会报出错误具体在第几行,而if(p!=NULL)不会。

assert 宏接受一个表达式作为参数。如果表达式为真(返回值为零),assert就会报错,在标准错误流 stderr中写入一条信息,显示没有通过的表达式,以及包含这个表达式的文件名和行号。

assert()的使用对程序员非常友好的,使用 assert有几个好处:它不仅能自动标识文件和出问题的行号,还有一种无需更改代码就能开启或关闭 assert()的机制。如果已经确认程序没有问题,不需要再做断言,就在 #include<assert.h>语句面前,定义一个宏 NDEBUG

  • #define NDEBUG
    #include<assert.h>
    
  • #define NDEBUG
    #include<assert.h>
    #include<stdio.h>
    int main()
    {
        int a=10;
        int *p=&a;
        assert(p!=NULL);//这样就不会报出警告了
        return 0;
    }
    

8.指针的使用和传址调用

8.1传址调用

学习指针的目的是为了使用指针解决问题,那什么问题,非指针不可呢?

例如:写一个函数,交换两个整形变量的值

#include<stdio.h>
Swap(int*m,int*n)
{
	int c = 0;
    c = *m;
    *m = *n;
    *n = c;
}
int main()
{
    int a=10;
    int b=20;
    printf("交换前a=%d,b=%d",a,b);
        Swap(&a,&b);
    printf("交换后a=%d,b=%d",a,b);
    return 0;
}

注意:传址调用并非传值调用

8.2 strlen的模拟(健壮版)
#include<stdio.h>
#include<assert.h>
unsigned int My_strlen(const char *p)
{
    unsigned int count=0;
    assert(p!=NULL);
   while(*p!='\0')
    {
        count++;
        p++;
    }
    return count;
}
int main()
{
    char arr[]="hello world";
    unsigned int len=My_strlen(arr);
    printf("%zd",len);
    return 0;
}

注意while后面不要写分号!!!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值