指针!!C语言(第一篇)

07-16 1624阅读

指针1

  • 指针变量和地址
    • 1.取地址操作符(&)
    • 2.指针变量和解引用操作符(*)
    • 指针变量的大小和类型
    • 指针的运算
    • 特殊指针
      • 1.viod*指针
      • 2.const修饰指针
      • 3.野指针
      • assert断言
      • 指针的使用和传址调用
        • 1.strlen的模拟实现
        • 2.传值调用和传址调用

          指针变量和地址

          在认识指针之前,我们先引入一个实际生活的例子,比如我们要找一个小区内的房子,如果我们知道它在具体的几号楼,房间编号是多少的话那我们就很容易找到。那么对照到计算机中,我们知道CPU读取数据也是在内存中读取,存储数据也同样在内存中,如果将内存也分成一个个编号和一个个空间,那我们寻找一个数据岂不是更快更便捷?

          其实在计算机中我们同样也是将内存划分为一个个内存单元,一个内存单元取一个字节,也就是8个比特位,每个内存单元也都有一个编号(这个编号就相当于小区房间的门牌号),有了这个内存单元的编号,CPU就可以快速找到一个内存空间。生活中我们把门牌号也叫地址,在计算机中我们把内存单元的编号也称为地址。C语言中给地址起了新的名字叫:指针。所以我们可以理解为:内存单元的编号 = 地址 = 指针。

          1.取地址操作符(&)

          理解了内存和地址的关系,我们再回到C语言,在C语言中创建变量其实就是向内存申请空间,比如:指针!!C语言(第一篇)

          2.指针变量和解引用操作符(*)

          指针变量:那我们通过取地址操作符(&)拿到的地址是⼀个数值,比如:0x006FFD70,这个数值有时候也是需要存储起来,方便后期再使用的,那我们把这样的地址值存放在哪里呢?答案是:指针变量中。下面展示一些 内联代码片。

           
          
          #include 
          int main()
          {
          int a = 10;
          int* p = &a;//取出a的地址并且存在指针变量p中
          return 0;
          }
          

          指针变量也是一种变量,这种变量就是用来存放地址的,存放在指针变量中的值都会理解为地址。指针!!C语言(第一篇)

          解引用操作符: 当我们把一个变量存储在一个指针变量中,如果我们要使用这个指针变量的话,我们要怎样使用呢?

          下面展示一些 内联代码片。

           
          
          #include 
          int main()
          {
          int a=10;
          int* pa=&a;
          *pa=20;//将a中的数值改为20
          printf("%d\n",a);
          return 0;
          }
          

          在上面的代码中, *pa 的意思就是通过pa中存放的地址,找到指向的空间 *pa其实就是a变量了;所以 *pa = 20,这个操作符就是把a改成了20,也就是通过指针来修改a变量中存的数值。

          指针变量的大小和类型

          首先我们要知道指针变量也是有大小,指针变量的大小是通过字节来判断的,指针变量的大小取决于地址的大小:

          比如:32位平台下地址是32个bit位(即4个字节),64位平台下地址是64个bit位(即8个字节)

          指针!!C语言(第一篇)

          虽然所占字节大小与类型无关,但是类型仍然是有意义的,决定了它解引用时候的权限,例如int* pa=&a;char* pc=&a;假如给a重新赋一个值0,就会发现通过调试int类型中的字节全部变为0,而char类型中的字节只有第一个字节变为0。

          指针的运算

          指针+ - 整数:指针也有运算,例如对于整型指针的加减&a→&a+1,就将指针的地址移动了4个字节,但如果是char类型的话,就只移动1个字节,也就是说不同类型的指针移动的字节大小是不相同的。

          指针-指针:指针-指针的绝对值是指针和指针之间元素的个数,但是两个指针指向的是同一块空间才可以。

          特殊指针

          1.viod*指针

          在指针类型中有⼀种特殊的类型是 void * 类型的,可以理解为无具体类型的指针(或者叫泛型指针),这种类型的指针可以用来接受任意类型地址。但是也有局限性, void* 类型的指针不能直接进行指针的±整数和解引用的运算。一般 void* 类型的指针是使用在函数参数的部分,用来接收不同类型数据的地址,这样的设计可以实现泛型编程的效果,使得⼀个函数来处理多种类型的数据。

          2.const修饰指针

          如果在一个程序中我们希望一个变量不能被随便修改,那我们应该怎么办呢?const就可以实现这个作用。

          比如:int a=100; a=200;那么输出的a就等于200,但是如果我们在int前面加上const,那么此时的a就不能被修改了。但是如果我们通过指针也就是用地址来变:下面展示一些 内联代码片。

           
          
          #include 
          int main()
          {
           const int n = 0;
           printf("n = %d\n", n);
           int*p = &n;
           *p = 20;
           printf("n = %d\n", n);
           return 0;
          }
          

          通过上面的代码,即使我们用const来修饰但是通过指针变量我们还是能把变量改变,那么有没有什么办法始终不能改变量里面的值呢?给大家放一张图:指针!!C语言(第一篇)

          3.野指针

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

          如何避免野指针呢?

          1. 指针初始化(如果不知道指向哪里,就赋值NULL空指针)
          2. 不要越界访问(例如我们访问一个数组,当超过数组的范围还要继续访问,将成为野指针)
          3. 指针变量不再使用时,及时置NULL,指针使用之前检查有效性。
          4. 避免返回局部变量的地址

          assert断言

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

          eg:assert(p!=NULL);

          上面代码在程序运行到这一行语句时,验证变量 p 是否等于 NULL 。如果确实不等于 NULL ,程序继续运行,否则就会终止运行,并且给出报错信息提示。assert() 宏接受一个表达式作为参数。如果该表达式为真(返回值非零), assert() 不会产生任何作用,程序继续运行。如果该表达式为假(返回值为零), assert() 就会报错,在标准错误流 stderr 中写入⼀条错误信息,显示没有通过的表达式,以及包含这个表达式的文件名和行号。

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

          指针!!C语言(第一篇)

          assert() 的缺点是,因为引入了额外的检查,增加了程序的运行时间。

          一般我们可以在 Debug 中使用,在 Release 版本中选择禁用 assert 就行,在 VS 这样的集成开发环境中,在 Release 版本中,直接就是优化掉了。这样在debug版本写有利于程序员排查问题,在 Release 版本不影响用户使用时程序的效率。

          指针的使用和传址调用

          1.strlen的模拟实现

          库函数strlen的功能是求字符串长度,统计的是字符串中 \0 之前的字符的个数。

          函数原型如下:下面展示一些 内联代码片。

           
          
          #include 
          size_t my_strlen(const char* s)//保证s不被改变
          {
          	int count = 0;
          	assert(s != NULL);//保证s不能是空指针
          	while (*s)
          	{
          		count++;
          		s++;
          	}
          	return count;
          }
          int main()
          {
          	char arr[] = "abcdef";
          	size_t len = my_strlen(arr);
          	printf("%zd\n", len);
          	return 0;
          }
          

          const保证了字符串内容不被改变。

          2.传值调用和传址调用

          写一个函数交换两个变量的值:下面展示一些 内联代码片。

           
          
          #include 
          void Swap1(int x, int y)
          {
          	int tmp = x;
          	x = y;
          	y = tmp;
          }
          int main()
          {
          	int a = 0;
          	int b = 0;
          	scanf("%d %d", &a, &b);
          	printf("交换前:a=%d b=%d\n", a, b);
          	Swap1(a, b);
          	printf("交换后:a=%d b=%d\n", a, b);
          	return 0;
          }
          

          指针!!C语言(第一篇)

          通过上面的代码我们可以看出来,即使我们使用函数交换两个变量的数值,但是输出的结果仍然不是我们想要的结果,那么问题到底出现在哪呢?这个时候我们就要知道一个叫做传值调用,也就是如果直接将数值传过去,就是传值调用。实参传递给形参的时候,形参会单独创建一份临时空间,对形参的修改不影响实参。那么有没有什么办法呢?我们可以想到使用指针传址的办法,也就是传址调用:

          下面展示一些 内联代码片。

           
          
          #include 
          void Swap2(int*px, int*py)
          {
           int tmp = 0;
           tmp = *px;
           *px = *py;
           *py = tmp;
          }
          int main()
          {
           int a = 0;
           int b = 0;
           scanf("%d %d", &a, &b);
           printf("交换前:a=%d b=%d\n", a, b);
           Swap2(&a, &b);
           printf("交换后:a=%d b=%d\n", a, b);
           return 0;
          }
          

          指针!!C语言(第一篇)

          传址调用,可以让函数和主调函数之间建立真正的联系,在函数内部可以修改主调函数中的变量;所以未来函数中只是需要主调函数中的变量值来实现计算,就可以采用传值调用。如果函数内部要修改主调函数中的变量的值,就需要传址调用。

VPS购买请点击我

文章版权声明:除非注明,否则均为主机测评原创文章,转载或复制请以超链接形式并注明出处。

目录[+]