C语言-指针

指针
1. 定义指针变量

类型* 指针变量名;

  • 指针变量中只记录了内存中某字节的地址编号,我们把它当作一个内存块的首地址,当使用指针变量访问内存时具体访问多少个字节,由指针变量的类型决定。

    char* p; 	// 能访问1字节 
    short* p;	// 能访问2字节
    int* p;		// 能访问4字节
    
  • 指针定义容易出错的一点

    int* p1,p2,p3; 		// p1是指针变量,p2、p3是普通的int类型变量
    int *p1,*p2,*p3;	// p1、p2、p3都是指针变量
    
    typedef int* intp;
    intp p1,p2,p3;		
    // p1、p2、p3都是指针变量 因为intp是一个数据类型
    // 就像int a,b,c; 一样 他们都是int类型
    
  • 指针也是一个变量,默认值是随机的,为了安全(避免产生野指针),如果初始化值不知道赋什么值,至少先初始化为NULL;

  1. 指针变量赋值

    指针变量 = 内存地址

    int* p = malloc(4); // 把堆内存的地址赋值给指针变量
    int* p = #	//num变量的类型必须与p的类型相同
    
  2. 指针变量解引用

    *指针变量

    解引用就是根据该指针变量中存储的内存地址,去访问内存,访问的字节数由指针变量的类型决定。

  3. 空指针

    也就是存储的是NULL的指针变量,操作系统规定不能访问该内存,访问就会段错误。

    所以使用指针变量时,比如函数参数是指针变量,应该先判断是否为NULL,再解引用。

  4. 野指针

    指针变量中存储的地址无法确定是否为合法地址。

    一般野指针都是认为创造的,那么如何避免?

    • 及时初始化指针变量,要么赋值合法内存地址,要么NULL;

    • 不返回局部变量、块变量的地址,因为当函数执行完毕局部变量和块变量就被销毁;

    • 与堆内存配合的指针变量,当堆内存被释放、销毁,该指针变量要及时的赋值为NULL,因为只是free的话只是释放掉指针所指的内存空间,自己本身并未置空。

      野指针比空指针危害大很多,具有潜伏性、随机性,使用指针是需多留意。

  5. 指针的进步值

    前面说到解引用时不同类型的指针变量访问的内存字节数不一样,他们实际访问的字节数就叫做指针变量的进步值。

  6. 指针运算
    指针+n = 指针所代表的整数+进步值*n		
    指针-n = 指针所代表的整数-进步值*n
    指针1-指针2 = (指针1所代表的整数-指针2所代表的整数)/进步值 
    
    #include <stdio.h>
    int main()
    {
    int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
        int *p1 = &arr[0];
        int *p2 = &arr[9];
        printf("%p %p\n", p2, p1);
        printf("%d %d %d \n", p2 - p1, *(p1 + 1), *(p2 - 1));
    }
    //000000000061FE04 000000000061FDE0
    //9 2 9
    
  7. 数组与指针
    • 数组名就是数组内存块的首地址,它是个常量地址,所以它作函数的参数时,才能蜕变成指针变量

    • 指针变量可以使用[]解引用,数组名也可以*遍历,它们是等价的。

      #include <stdio.h>
      
      int main()
      {
          int arr[10] = {1,2,3,4,5,6,7,8,9,10};
          for(int i=0; i<10; i++)
          {   
              printf("%d %d\n",arr[i],*(arr+i));             }   
      }
      1 1
      2 2
      3 3
      4 4
      5 5
      6 6
      7 7
      8 8
      9 9
      10 10
      
    • 数组名是常量,而指针是变量。

    • 指针变量有它自己的存储空间(存储数组地址),而数组名就是地址,它没有存储地址的内存。

    • 指针变量与它的目标内存是指向关系,而数组名与它的目标内存是映射关系。

  8. 通用指针

    一些通用的函数,它们的参数可能是任意类型的指针,但编译器规定不同类型的指针不能进行赋值,为了兼容各种类型的指针,C语言中设计了void类型的指针,它能与任意类型的指针互相转换,
    它能解决不同类型的指针参数的兼容性问题。

    void* p1;
    int* p2 = p1;
    void* p3 = p2;
    

    void类型的指针变量的进步值是1(字节)。

    void类型的指针变量不能解引用 ,必须转换成其它类型的指针才能解引用。

    几个通用操作函数

    #include<strings.h>
    void bzero(void *s, size_t n);
    功能:把内存块s的n个字节,赋值为0#include<string.h> 
    void *memset(void *s, int c, size_t n);
    功能:把内存块s的n个字节,赋值为c(0~255)
        
    void *memcpy(void *dest, const void *src, size_t n);//const 只读
    功能:从src内存块拷贝n个字节的内容到dest内存块
        
    int memcmp(const void *s1, const void *s2, size_t n);
    功能:比较s1和s2内存块的n个字节
        s1 > s2 返回1
        s1 < s2 返回-1
        s1 == s2 返回0
    

    实现:

    void my_bzero(void* s,size_t n)
    {
    	if(NULL == s || 0 == n)
    		return;
    	for(int i=0; i<n; i++)
    	{
    		*(char*)(s+i) = 0;
    	}
    }
    // 方便后续进行链式调用
    void* my_memset(void* s,int c,int n)
    {
    	if(NULL == s || 0 == n)
    		return NULL;
    	for(int i=0; i<n; i++)
    	{
    		*(char*)(s+i) = c;
    	}
    	return s;
    }
    
    void* my_memcpy(void* dest,void* src,size_t n)
    {
    	if(NULL == dest || NULL == src || 0 == n)
    	   return NULL;
    	for(int i=0; i<n; i++)
    	{
    		*(char*)(dest+i) = *(char*)(src+i);
    	}	
    	return dest;
    }
    
    int my_memcmp(void* s1,void* s2,int n)
    {
    	if(NULL == s1 || NULL == s2 || 0 == n)
    		return 0x80000000;
    	unsigned char* p1 = s1;
    	unsigned char* p2 = s2;
    	for(int i=0; i<n; i++)
    	{
    		if(p1[i] > p2[i])
    			return 1;
    		if(p1[i] < p2[i])
    			return -1;
    	}
    	return 0;
    }	
    
  9. 二级指针

    就是存储指针变量内存地址的变量。

    类型 二级指针 = &一级指针;

    //类型必须相同

    • 解引用

      ​ *二级指针 此时它等价于一级指针

      ​ **二级指针 此时它等价于 *一级指针

      int num;

      int* p =&num

      int** p=&p;

      #include <stdio.h>
      
      int main()
      {
          int num = 1234;
          int num1 = 4567;
          int* p = &num; 
          int** pp = &p; 
      
          *pp = &num1; // p = &num1;
          printf("%d\n",*p);/4567
          **pp = 123456789; // num1 = 123456789;
          printf("%d\n",num1);                       
      /*	num address=0xbf89e93c num=1234
      	num1 address=0xbf89e940 num1=4567
      
          p=0xbf89e93c *p=1234 &p=0xbf89e944
      
          pp=0xbf89e944 **pp=1234 &pp=0xbf89e948 		*pp=0xbf89e93c
      
          *p=4567 p=0xbf89e940
          num1=123456789
      */
      }
      
  10. 指针数组

    由指针变量构成的数组,也可以说它的身份是数组,成员是指针变量。

    类型* 数组名[n];

    int * arr[10];

    //就等于定义了10个指针变量

    • 可以来构造不规则数组和字符串数组
  11. 数组指针

    专门指向数组的指针变量,它的进步值是整个数组的字节数

    类型 (*指针变量名) [n];

    #include <stdio.h>
    int main(int argc,const char* argv[])
    {
    	int arr[4][5] = {
    		{11,12,13,14,15},
    		{21,22,23,24,25},
    		{31,32,33,34,35},
    		{41,42,43,44,45}
    	};
        
    	int (*p)[4] = (void*)arr;
        
    	for(int j=0; j<5; j++)
    	{
    		for(int i=0; i<4; i++)
    		{
    			printf("%d ",p[j][i]);
    		}
    		printf("\n");
    	}
    }
    /*
    11 12 13 14 
    15 21 22 23 
    24 25 31 32 
    33 34 35 41 
    42 43 44 45 
    */
    

    就好像行指针一样,一次指向n个元素,加1指向下一个n个元素。

  12. 函数指针

    专门存储函数地址(函数在text内存的首地址)的指针变量。

    1、先确定指向的函数的格式(函数声明)。

    2、照抄函数声明。

    3、用小括号包含函数名。

    4、在函数名前加*

    5、在函数名末尾加_fp,防止命名冲突。

    6、用函数名给函数指针赋值后,函数指针就可以当作函数调用了。

    #include <stdio.h>
    void func(void)
    {
        printf("...");
    }
    
    int main()
    {
        void (*func_fp)(void) = func;//函数名就是首地址
        func_fp();                                                                
    }
    
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值