学习——理解指针(1)

简介: 学习——理解指针(1)

一、内存和指针

       1、内存与地址

               举个例子,一栋大楼中有许多个房间,如果想要快速的找到一个房间,就需要知道这个 房间的门牌号,这个门牌号就可以理解成这个房间的地址;

               在计算机中其实也是把内存划分为⼀个个的内存单元,每个内存单元的⼤⼩取1个字节。

计算机中常⻅的单位(补充):

  • ⼀个⽐特位可以存储⼀个2进制的位1或者0;
  • 一个字节(byte)等于八个比特位(bit)。

               存储信息的内存编号 <==> 地址 <==> 指针

二、指针变量(&与*)

               在编写代码时:

               int a;        创建了一个整型变量a,向内存中申请了一个整型变量(占4个字节)的空间,每一个字节的内存空间都有它的内存编号。

         <1> &(取地址操作符)

               通过&(取地址操作符)可以获得一个变量的地址,&a就是a的地址;

               有了a的地址,现在需要把地址存起来,这时候就用到了指针变量;

int a;
int* b=&a;

               这里,b就是一个指针变量,类型是int*,*说明b是一个指针变量,int表示b所指向的对象类型是个整型。

        <2> *(解引用操作符)

               指针变量存放地址,如果想通过这个地址找到它指向的变量,就需要用到*(解引用操作),即

int a=10;

int* b=&a;

printf("%d",*b);      

              通过解引用操作,就可以找到指针所指向的变量;

       指针变量可以存储一个变量的地址,但如果这个变量是一个指针变量,这是,就会一个新的概念:二级指针

       二级指针:存放一级指针地址的变量。

int a=10;
int* pa=&a;
int** ppa=&pa;

                 *pa=a。        **ppa=*pa=a。

三、指针类型的意义

      <1> 指针类型不同,通过解引用操作所访问从字节数就不同

例:char*类型的指针解引用操作只访问一个字节,二int*类型访问4个字节;

   int a = 1234443;  int* p = &a;  *p = 0;                

int a=1234443; char*pa=(char*)&a; *p=0;

       <2> 指针类型不同,指针运算所跳过的字节数也不同

char* 类型指针+1跳过1个字节,而int*类型的指针+1跳过4个字节。

       <3>void*指针

void是一个泛型指针,它可以接受任何类型的指针,但是不能进行指针运算和解引用操作。

四、指针运算

       <1>指针+-整数

       我们知道,数组在内存中是连续存储的,所以,只需要找到一个元素的地址,通过指针+-整数会可以访问数组中所有数据。

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

用指针加减整数去实现数组下标访问。

       <2>指针-指针

       指针-指针的绝对值是两个指针之间的元素个数。

前提:两个指针指向同一块内存空间。

#include<stdio.h>
 
int my_strlen(char* str) {
  char* p = str;
  while (*str != '\0') {
    str++;
  }
  return str - p;
 
}
int main() {
  char arr[] = "abcd";
  printf("%d\n", my_strlen(arr));
  return 0;
}

       <3>指针关系运算

指针地址大小的比较。

int main() {
  int arr[10] = { 1,2,3,4,5,6,7,8,9,0 };
  int* p = arr;
  int sz = sizeof(arr) / sizeof(arr[0]);
  while (p < arr + sz) {
    printf("%d ", *p);
    p++;
  }
  return 0;
}

五、const 修饰指针

       当const修饰一个变量时,这个变量就不可以再进行修改;现在,用const修饰一个指针变量,这个指针变量是不是也是不可被修改的呢?

       我们知道,指针变量存放的是地址,地址又有所指向的值,所以用const修饰指针变量,是指针变量不可被修改,还是指针所指向的值不可被修改呢?

       <1>const 修饰*p

       const int *p;

      const 放在*的前面(左面)时,const修饰指针所指向的内容,保证指针指向的内容不能通过指针来改变。 但是指针变量本身的内容可变(就是他存储的地址可以改变)。

       <2>const 修饰p

       int* const p;

       const如果放在*的后面(右边),修饰的是指针变量本身,保证了指针变量的内容不能修改(它存储的地址不能改变),但是指针指向的内容,可以通过指针改变。

六、野指针

       <1>野指针形成原因

       1.指针没有初始化

int* p;
*p=20;

规避:初始化指针;不知道指针该指向哪里,就给指针赋值NULL(空指针)。

NULL 是C语⾔中定义的⼀个标识符常量,值是0,0也是地址,这个地址是无法使用的,读写该地址会报错。

       2.指针越界

# include <stdio.h>
int main ()
{
int arr[ 10 ] = { 0 };
int *p = &arr[ 0 ];
int i = 0 ;
for (i= 0 ; i<= 11 ; i++)
{
// 当指针指向的范围超出数组 arr 的范围时, p 就是野指针
*(p++) = i;
}
return 0 ;
}

⼀个程序向内存申请了哪些空间,通过指针也就只能访问哪些空间,不能超出范围访问,超出了就是 越界访问。

       3.指针所指向的空间被释放

#include <stdio.h>
int* test()
{
 int n = 100;
 return &n;
}
int main()
{
 int*p = test();
 printf("%d\n", *p);
 return 0;
}

尽量不使用局部变量的地址。

       <2>assert断言

       指针变量不再使⽤时,及时置NULL,指针使⽤之前检查有效性  

assert(p != NULL );

        用来判断指针是否为空指针,如果为空,代码程序终止。

       assert.h 头⽂件定义了宏 assert() ,⽤于在运⾏时确保程序符合指定条件,如果不符合,就报

错终⽌运⾏。这个宏常常被称为“断⾔”。

       而如果不再需要assert进行断言

# define NDEBUG

使assert失效,不进行判断。

七、传值调用与传址调用

       当你用代码编写一个函数交换两个数的值时

 
void swap1(int x, int y) {
  int t;
  t = x;
  x = y;
  y = t;
}
int main() {
  int a, b;
  scanf("%d%d", &a, &b);
  printf("交换前:a=%d, b=%d", a, b);
  swap1(a, b);
  printf("交换后:a=%d, b=%d", a, b);
  return 0;
}

你会发现,这样并没有把a与b的值进行交换;

       其实,这样写代码只是将a与b的数值传给了函数swap1的形参,而在函数运行时,会创建两个临时变量x,y用来接收a,b的值,而函数也只是将x,y两个的值交换了,并没有把a和b进行交换;当函数执行完以后,a与b没有进行交换。

实参传递给形参的时候,形参会单独创建⼀份临时空间来接收实参,对形参的修改不影响实

参。

像这种只是把数值传给函数形参的,就是传值调用

那又该怎样实现?

可以将a,b的地址传给函数,这样函数在运行工程中,可以直接通过解引用操作访问a与b,并进行修改

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;
}

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

       应用:strlen的模拟实现

int my_strlen(const char * str)
{
 int count = 0;
 assert(str);
 while(*str)
 {
 count++;
 str++;
 }
 return count;
}
int main()
{
 int len = my_strlen("abcdef");
 printf("%d\n", len);
 return 0;
}


相关文章
|
存储 C语言 C++
C语言学习系列-->看淡指针(3)
C语言学习系列-->看淡指针(3)
46 0
|
2月前
|
C语言
学习——理解指针(4)(指针学习最后一节)
学习——理解指针(4)(指针学习最后一节)
|
2月前
|
存储 C++
学习——理解指针(3)
学习——理解指针(3)
|
2月前
|
编译器
学习——理解指针(2)
学习——理解指针(2)
|
5月前
|
存储 安全 编译器
【C++入门 四】学习C++内联函数 | auto关键字 | 基于范围的for循环(C++11) | 指针空值nullptr(C++11)
【C++入门 四】学习C++内联函数 | auto关键字 | 基于范围的for循环(C++11) | 指针空值nullptr(C++11)
|
6月前
|
存储 C语言
C语言学习记录——7000+字长文-复习&学习指针(指针、地址、指针变量、指针与数组、指针与函数、指针数组、多级指针)一
C语言学习记录——7000+字长文-复习&学习指针(指针、地址、指针变量、指针与数组、指针与函数、指针数组、多级指针)一
50 1
|
6月前
|
存储 C语言
C语言学习记录——7000+字长文-复习&学习指针(指针、地址、指针变量、指针与数组、指针与函数、指针数组、多级指针)二
C语言学习记录——7000+字长文-复习&学习指针(指针、地址、指针变量、指针与数组、指针与函数、指针数组、多级指针)二
41 1
|
C++
C++语言学习指针和引用应用案例
C++语言学习指针和引用应用案例
77 1
|
7月前
|
存储 安全 Java
Go语言学习10-指针类型
【4月更文挑战第11天】本篇 Huazie 向大家介绍 Go语言的指针类型
51 2
Go语言学习10-指针类型