【笔记】【C语言】第八章 善于利用指针

8.1 地址和指针

 

(1)数据在内存中是如何存储的?什么是地址?

  首先,我们会定义一个变量,用于存储一个数据。比如 int a=1;

  然后在编译时,系统遇到定义语句,就给a分配内存单元。因为是整数类型的,所以分配4个字节。

  内存区的每个字节,系统都做了编号,这就是“地址”。假设分给a的内存单元是2000-2003。所以我们在使用变量a的时候,会从2000-2003中读取数据。

  因为地址指向该变量单元,所以将地址形象地称为“指针”

  

  那么如何实现地址的记录呢?

  取出a变量,可以记录2000这个首地址,和a这个数据类型存储时候地长度,就是4。这两个量地记录就可以实现a的访问。

  所以C语言中的地址,包括位置信息和类型信息

  在C语言中,一般是通过变量名引用变量的值。所以如果a对应的就是2000这个地址,访问起来就十分方便。这也是C语言的一大特点。

 

  总结:

  编译时,系统为变量分配内存,并建立变量名和地址的对应表。

  执行语句时,通过变量名,找到相应的地址,按照数据类型读出变量的值。

 

(2)直接访问 和 间接访问

  直接访问:按变量地址存取变量值。

    scanf("%d",&x);
    printf("%d",x);
    k=a+b;

  间接访问:将变量i的地址,存放在另一个变量中。

  在访问的时候,先找到i_pointer,从中取出 i 的地址(2000),然后找到2000,2001的字节,取出 i 的值。

(3)指针:

  一个变量的地址,称为该变量的指针

  如果有一个变量专门用来存放另一变量的地址(指针),则这个变量称为“指针变量”

 

(4)程序示例与分析

 1 #include<stdio.h>
 2 int main()
 3 {
 4     int a=100,b=10;
 5     int *pointer_1,*pointer_2;
 6     pointer_1 = &a ;
 7     pointer_2 = &b ;
 8     
 9     printf("a=%d\n",a);
10     printf("%d\n",*pointer_1);            //输出a的值 
11     
12     printf("%p\n",&a);                    //输出的是a变量的地址
13     printf("%p\n",pointer_1);             //通过指针变量ap输出a变量的地址
14     printf("%p\n",&pointer_1);            //指针变量ap本身自己的地址
15     return 0;
16 }

  5:* 表示该变量的类型为 指针型变量 。

  6:pointer_1分配的内存中,存储着a变量的地址

  9:直接访问法,访问a的值。步骤:a表示首地址,类型决定字节数,从内存中读取出a的值。

  10:间接访问法,*pointer,读取出&a(即a的地址)。然后因为要输出%d,所以再去内存区,按照读出的地址,读取数据

  12:&a是a的地址,要求输出地址格式的数据,正好符合。

  13:pointer存储的是&a,实际也是地址。

  14:&pointer意为,得到地址直接输出,不去读取pointer的值。

 (5)一个变量的指针,包括 以存储单元编号表示的纯地址它指向的存储单元的数据类型

 

 

8.2 指针变量的定义和使用

 

(1)定义一般形式:

  基类型 *指针变量名

    float a;
    float *it_1 = &a;
  1. * 表示该变量的类型为 指针型变量
  2. 指针变量名是it_1.
  3. 必须指定基类型。(指针类型是基本数据类型,派生出来的类型)
  4. 指针变量只能存放地址,不可以直接给他赋值某个整数
  5. &a和p,实际上都包括地址和类型信息。应使赋值号两边类型一致
  6. 指针变量的值——指针变量的指向——修改temp
  7. 指针变量所指变量的值——修改*temp

(2)引用:

#include<stdio.h>
int main()
{
    int a=100;
    int *pointer_1;
    //1 给指针变量赋值 
    pointer_1 = &a ;
    //2 间接引用变量a 
    printf("%d\n",*pointer_1 );
    *pointer_1 ++;                      //等同于a++
    *pointer_1 = 1;                  //等同于a=1 
    //3 引用指针变量的值 
    pointer_1 = &a; 
    printf("%X %p",pointer_1,&a);    //输出a的地址
    //x是十六进制小写,X是十六进制大写,o是八进制小写
    //p是输出地址 
    return 0;
}

(3)运算符

  & 取地址运算符

  * 指针运算符

 

(4)指针变量作为函数参数

  作用:

  将一个变量的地址传送到另一个函数中。

  相比起将变量作为参数,将指针变量作为参数,可以在函数中改变变量的值

  而且相比起返回值,使用指针变量作为参数,可以得到多个变化过的值

示例:

  将 a,b的地址 分别赋给 int *p1,int *p2 。

  形参是指针型变量。

  在调用时,采取的是”值传递“。将&a,&b传给p1,p2。

  修改时,p1和p2的值都没变,*p1修改实际是通过间接访问,修改了a的内存中的值。

#include<stdio.h>
int main()
{
    int a,b;
    scanf("%d%d",&a,&b);
    
    void swap(int *p1,int *p2);
    swap(&a,&b);
    printf("%d %d",a,b); 
    return 0;
}

void swap(int *p1,int *p2)
{
    int temp;
    temp = *p1 ;
    *p1 = *p2 ;
    *p2 = temp ;
    //这个函数实际上,都是值的相互赋值,所以最后不用加取地址符。 
}

 错误版本1:

  已知*p1就是a,*temp就是temp这个指针变量,所指向的变量。

  因为没有给temp赋初值,也没有制空。

  所以这时候temp是个野指针。它会随机附上内存地址的一段值,甚至有时候会覆盖前面所用过的内存空间。

void swap(int *p1,int *p2)
{
    int *temp;
    *temp = *p1 ; //这个句子有问题 
    *p1 = *p2 ;
    *p2 = temp ; 
}

错误版本2:

  企图通过改变 指针形参的值 改变 指针实参的值。

void swap(int *p1,int *p2)
{
    int *temp;
    temp = p1 ;     //temp指向a 
    p1 = p2 ;          //p1指向b 
    p2 = temp ;     //p2指向a
    //最后p1,p2指向的变量交换了,但是a,b的值没有经过改变 
}

 

指针运算:

(6)&*it

  从右向左进行结合。 *it 就变量a。

  …&*it 则是 &a。

(7)*&it 

  &a是a的地址,*&a则是&a所指向的变量,也就是a

(8)(*it )++ 

  相当于a++

(9)*(it++)

  先做*it运算,得到a。整体相当于a++

 

 

8.3 数组与指针

 

(1)指针运算:

  当指针指向一个数组元素的时候,可以对指针进行如下操作:

  • p+k p-k p++ p-- 
  • p1-p2 要求 两个指针指向同一个数组的元素时
  • *p++  等同于 a[i]++
  • * ( p++ ) 等同于 a[i++]
  • * ( ++p ) 等同于 a[++i]
  • ++(*p) 等同于 ++a[i]

 

(2)用指针 引用 数组元素:

  下标法:如a[i]

  指针法:如*(a+i)或*(p+i)

  •   a[ i ] 与 *(a+i)     实际执行效果相同,但由于计算元素地址操作,较为费时间
  •   *p,p++        p++自加操作速度快,能够大大提高程序效率

  说明:

  •   [ ] 实际上是变址运算符,a[i]按照a+i计算地址。
  •   a作为数组名,代表数组首元素的地址,是一个常量。a++这种操作无意义。
  •   a[ n ] 虽然并不会出错,但是运行结果不可预期。
  •        指向数组元素的指针向量p,也可以带下标 p[ i ]

 

(3)函数传参有三种方式:值传递,地址传递,引用传递。

  ①地址传递,形参存放地址所以一定为指针变量。指针变量和实参都指向同一个地址。

  所以,被调用函数中对形参指针所指地址中的内容,的任何改变,都会影响到实参。

#include<stdio.h>
int main()
{
    void swap(int *,int *);
    int a,b;
    swap( &a , &b );
    
    return 0;
}

void swap(int *m,int *n)
{ };

  ②引用传递,在形参调用前,加入"&"符号。引用为实参的别名,和实参是同一个变量,则值相同,修改也相同.

  

(4)用数组名作函数参数

  编译时是将arr按指针变量处理的。

  实参——数组名或指针变量

    f(a,n);
    f(p,n); 

  形参——指针变量或数组名。以下两种写法实际相同。

void f(int arr[],int n)
{ }
void f(int *arr,int n)
{ }

  在函数执行过程中,指针变量类型的形参,可以再次被赋值。

 

(5)多维数组的地址:先行后列

(6)应用及注释

// p p2 纯地址相同,但是基类型不同 
int *p = a[0];         //基类型是整形数据 
                    //此时P相当于 &a[0][0]
p++;                 //p相当于  &a[0][1]。

int (*p1)[n];          //基类型为n个整形数据组成的一维数组
int *p1 = a;        //同上
                 
p1++;                  //p1指向a[1][0];2363569
//对于a数组中的任一元素a[i][j],指针的一般形式如下: 
int *pij = *(p1+i)+j;
//元素a[i][j]相应的指针表示为:
int *pij = *(p+i*N+j);
//同样,a[i][j]也可使用指针下标法表示,如下:
int *pij = p[i*N+j];

//对于二维数组a,其a[0]数组由a指向,a[1]数组则由a+1指向,a[2]数组由a+2指向,以此类推。
//因此,*a与a[0]等价、*(a+1)与a[1]等价、*(a+2)与a[2]等价,┅,即对于a[i]数组,由*(a+i)指向。
//由此,对于数组元素a[i][j],用数组名a的表示形式为:
*(*(a+i)+j)
//指向该元素的指针为:
*(a+i)+j

(7)指针数组

 int *p[N];

 

8.4 通过指针引用字符串

(1)存放字符串并且输出

#include<stdio.h>
int main()
{
    //两种定义方法 
    char ch[] = "I love China !";
    char *ch = "I love China !";
    
    printf("%s\n",ch);
    printf("%c\n",ch[7]);
    return 0;
} 

(2)字符数组和字符指针变量的区别:

  • 字符数组由若干个字符元素组成;字符指针变量中存放的是地址(即字符串第一个字符的地址)。
  • 赋值方法:
char str[14];
str [] = { "I Love China!" };
char *a ;
a= "I Love China!"; 
  • ·定义一个字符指针变量时,给指针变量分配内存单元。但如果未对它赋予一个地址值时,则它并未具体指向一个确定的字符数据。

 

char str[10];
scanf("%s",str);
//正确 

char *a;
scanf("%s",a);
//错误
char *a,str[10];
a=str;
scanf("%s",a);
//正确 

 

(3)若定义了一个指针变量,并使它指向一个字符串,就可以用下标形式,引用指针变量所指的字符串中的字符。

    char *a = "I love China!";
    int i;
    for(i=0 ; a[i]!='\0' ; i++ )
        printf("%c",a[i]); 

 

总结:

也正是因为C/C++有指针的特性,所以他是最自由的。可以对内存空间实现直接的操作,这也是省时和高效的原因。(贴近底层操作系统) 

这也带来了危险,比如指针偏移内存泄露。不用时记得赋值NULL

 

例题:寻找d[2][4]中的最大元素的值

#include<stdio.h>
using namespace std;

int d[2][4];

int main()
{
    for(int i=0;i<2;i++)
        for(int j=0;j<4;j++)
            scanf("%d",&d[i][j]);

    int *p = d[0] ;
    int i,j; int *nw;
    //用指针代替循环 
    //结束条件也可以写成 nw < &a[2][4] 
    for(nw=d[0],p=d[0] ; nw<d[0]+8 ; nw++ )
        if( *nw > *p )
            p=nw;
    //枚举整数类型以循环 
    for(i=0,p=d[0] ;i<2;i++)
        for(j=0;j<4;j++)
            if(d[i][j] > *p )
            {
                p = &d[i][j];
            }
    
    printf("%d\n",*p);
    return 0;
} 
View Code

 

 

暂时完结,深入的内容准备暑假再阅读。

参考资料:

  《C程序设计》第八章

  https://blog.csdn.net/qq_33573235/article/details/79530792

posted @ 2022-04-28 17:49  心若笺诗  阅读(78)  评论(0编辑  收藏  举报