• C博客作业05--指针


    这个作业属于哪个班级 C语言--网络2011/2012
    这个作业的地址 C博客作业04--数组
    这个作业的目标 学习数组相关内容
    名字 李洋

    0.展示PTA总分(0----2)

    1.本章学习总结(3分)

    1.1 指针定义、指针相关运算、指针做函数参数。

    1.1.1 指针定义

    计算机为了对内存单元中的数据进行操作,一般是按“地址”存取的,也就是说,对内存单元进行标识编号。把一个内存单元看作一个建筑物,建筑物内的房间就是储存器单元,房间号就是地址。而指针就是房间的坐标和通行证,可以通过这把“证”直接进到“房间里”进行各项工作。

    • 指针也就是内存地址,指针变量是用来存放内存地址的变量

    • 不同类型的指针变量所占用的存储单元长度是相同的,而存放数据的变量因数据的类型不同,所占用的存储空间长度也不同。

    1.1.2 指针变量的定义

    1. 一般形式: 类型名 * 指针变量名

    2. 对指针变量赋值:

      • p=&x;
      • p=0;
      • p=NULL;
      • p=(int * ) 1732;/强制转换/

    1.1.3 指针相关运算

    1. 取地址和间接访问运算

    int *p,a=3;
    p=&a;//单目运算&用于给出变量的地址
    
    *p=4;//*还用于访问指针所指的变量,*p的值就是a的值
    

    2. 赋值运算

    指针被定义并赋值后,可以像其他变量一样进行赋值运算,如:

    int a=3,*p1,*p2;
    p1=&a;   //指针p1指向a
    p2=p1;   //把指针p1指向的地址赋给p2,此时p1和p2都指向变量a
    
    

    注意:

    • 讲一个变量的地址作为初始值赋值给一个指针变量是,该变量必须在此前已经定义。
    • 不能用数值作为指针变量的初值。(除0和NULL外)
    • 指针变量之间赋值必须是同类型的指针,即指针所致的变量的类型必须相同。

    3. 指针前进或后退以及对指针所致变量的值的运算

    首先给出指针与数组的关系:

    p指向数组a的首地址,则有*(p+n)与p[n]等价

    注意:p为指针变量,p=p+1是合法的,a=a+1则是不合法的!

    如果已经对数组进行了赋值,对数组求和:

    sum=0;
    for(p=a;p<=&a[99];p++)
    {
          sum+=p;
    }
    

    上面涉及到指针的自增运算,再来看一下用指针做自增运算的规则:

    • 指针移动:

    (*p)++则是对p所指向的变量做自增运算

    1.1.4 指针做函数参数

    1. C语言中实参和形参之间的数据传递是单向的“值传递”方式,调用函数不能改变实参变量的值,当指针作为形参时,同样遵守这一规则。调用函数不能改变实参变量,但是可以改变实参变量所指向的变量的值,这被称为引用调用。在定义函数时,把指针作为形参,调用变量的地址作为实参。

    如在用指针做变量的值互换时:

    #include <stdio.h>
    
    void swap(int *p1, int *p2){
        int temp;  //临时变量
        temp = *p1;
        *p1 = *p2;
        *p2 = temp;
    }
    
    int main(){
        int a = 66, b = 99;
        swap(&a, &b);
        printf("a = %d, b = %d
    ", a, b);
        return 0;
    }
    

    1.2 字符指针

    1.2.1 指针指向字符串

    字符串是一串字符,通常被看作是一个特殊的一维字符数组,与数组的储存类似,字符串中的所有字符连续存放,所以同样可用指针来表示。如果定义一个字符指针接受字符常量的值,该指针就只想字符串的首字符。
    如:

    char *ptr = "Hello";//将保存在常量存储区的"Hello"的首地址赋值给ptr
    与
    char *ptr;
    ptr = "Hello";//是等价的,注意不能理解为将字符串赋值给ptr
    
    
    • (ptr+i):字符串中第i+1个字符,相当于(str+i),即str[i]也可以用ptr++,移动指针ptr,使ptr指向字符中的某个字符

    值得注意的是:对于数组名str,不能使用str++操作使其指向字符串中的某个字符,因为数组名是一个地址常量,其值是不能被改变的。

    • 插入要点
      • gets():可以输入带有空格的字符串。以回车符作为字符串的终止符,同时将回车符从输入缓冲区读走,但不作为字符串的一部分。而scanf()不读走回车符,回车符仍留在输入缓冲区中。 gets()不能限制输入字符串的长度,很容易引起缓冲区溢出。同样即使scanf(“12s%”,name)也不能解决这个问题。当使用scanf()和gets()时,确保输入字符串的长度不超过数组的大小,否则使用限制输入字符串长度的函数: fgets(name,sizeof(name),stdin)
      • puts():用于从括号内的参数给出的地址开始,依次输出存储单元中的字符,当遇到第一个’’时输出结束,并且自动输出一个换行符’ ’

    1.2.2 字符串相关函数及函数代码原型

    1. 实现字符串复制(全复制)

    void MyStrcpy(char *str1,char *str2)
    {
            while(*str2!='')//若当前str2所指字符不是字符串结束标志
            {
               *str1 = *str2;//复制字符
                str2++;//使str2指向下一个字符
                str1++;//使str1指向下一个存储单元
            }
            *str1 = '';//当复制循环结束时,在str1的末尾添加字符串结束标志
    }
    

    2. 复制函数进阶

    strncpy函数

    char  *strncpy(char *s2, const char *s1, size_t n);

    说明:
    函数strncpy从s1指向的数组中最多复制n个字符(不复制空字符后面的字符)到s2指向的数组中。

    3. 实现字符串连接

    char *MyStrcat(char *str1,char *str2)
    {
          char *pStr = str1;//定义一个字符指针并保存字符串str1的首地址
          while(*str1! = '')//当没有读到str字符串的结束标志
          {
                str1++;//将指针移动到字符串str1的末尾
          }//注意这里,在读到''之前时,指针已经移到了这个位置,当读到时跳出循环不再指向下一个
          所以复制str2的首地址是''之前的位置
          for(;*str2!='';str1++,str2++)
          {
                *str1 = *str2;//将字符串str2复制到字符串str1的后面
          }
                *str1 = '';//在连接后的字符串的末尾添加字符串结束标志
                return pStr;//返回连接后的新的字符串str1的首地址
    }
    
    

    4.

    int strcmp(const char *str1,const char *str2)
    {
        /*不可用while(*str1++==*str2++)来比较,当不相等时仍会执行一次++,
        return返回的比较值实际上是下一个字符。应将++放到循环体中进行。*/
        while(*str1 == *str2)
        {
                    assert((str1 != NULL) && (str2 != NULL));                
            if(*str1 == '')
                return 0;        
            str1++;
            str2++;
        }
        return *str1 - *str2;
    }
    

    5. 计算字符串的长度

    unsigned int MyStrlen(const char *pStr)//为防止实参在被调函数中被意外修改,在相应的形参前面加上类型限定符const
    {
               unsigned int len = 0;//计数器设置为0
               for(;*pStr !='';pStr++)
               {
                    len++;//循环统计不包括''在内的字符个数
               }
               return len;
    }
    

    1.3 指针做函数返回值

    • C语言允许函数的返回值是一个指针(地址)。

    用指针作为函数返回值时需要注意的一点是,函数运行结束后会销毁在它内部定义的所有局部数据,包括局部变量、局部数组和形式参数,函数返回的指针请尽量不要指向这些数据,C语言没有任何机制来保证这些数据会一直有效,它们在后续使用过程中可能会引发运行时错误。
    函数运行结束后会销毁所有的局部数据,这个观点并没错,大部分C语言教材也都强调了这一点。但是,这里所谓的销毁并不是将局部数据所占用的内存全部抹掉,而是程序放弃对它的使用权限,弃之不理,后面的代码可以随意使用这块内存。如果func() 运行结束后 n 内存没变,它的值为也不变,但如果使用及时也能够得到正确的数据,如果有其它函数被调用就会覆盖这块内存,得到的数据就失去了意义。

    1.4 动态内存分配

    1.4.1 为什么要动态内存分配?

    1. 内存很宝贵。

    2. 如果全部是静止内存不能释放,对于小的程序可以运行完毕。但是对于大的程序,还没运行完,内存就要被占用完,此时就要发生内存泄露。

    3. 给定一个占用内存可变大小的变量(假设是数组的长度len),给该变量通过函数动态分配内存后,分配内存的大小是根据数组的长度len决定的,假定用户输入len的大小是5,系统就会动态的给该数组分配长度为5的内存,该段代码运行结束后,系统调用free()函数释放分配的内存,然后接着运行剩下的程序。换句话说,动态分配内存可以根据需要去申请内存,用完后就还回去,让需要的程序用。

    1.4.2 堆区和栈区区别

    栈区:
    存放函数的参数值、局部变量等,由编译器自动分配和释放,通常在函数执行完后就释放了

    堆区:
    就是通过new、malloc、realloc分配的内存块,编译器不会负责它们的释放工作,需要用程序区释放。

    1.4.3 动态内存分配相关函数及用法

    1、malloc函数

    void *malloc(unsigned int size);
    //在内存的动态分配区域中分配一个长度为size的连续空间//堆区
    

    函数调用:

    char *p = (char *)malloc(100*sizeof(char));
    if(p != NULL)
    {
       free(p);
       p = NULL;
    }
    

    (1)返回值:通用类型(void* 类型可以通过类型转换强制转换为任何其它类型的指针)
    分配成功,返回一个指向分配起始地址的指针;
    分配失败,返回NULL
    (2)注意:
    ①使用malloc进行动态内存分配后应判断指针是否为空,即是否分配成功;
    ②申请的内存不会进行初始化,为随机值;
    ③使用完毕以后必须手动释放内存空间,否则会造成内存泄露

    注意:
    *free函数只是将参数指针指向的内存归还给操作系统,并不会把参数指针置NULL,为了以后访问到被操作系统重新分配后的错误数据,所以在调用free之后,通常需要手动将指针置NULL。

    2、calloc函数

    void *calloc(unsigned int num, unsigned int size);
    //按照所给的数据项个数和数据类型所占字节数,分配一个 num * size 连续的空间
    

    函数调用:

    char  *p = (char *)calloc(3,sizeof(int));//分配3个int型的存储空间
    

    (1)返回值:
    与malloc函数相同
    (2)注意:
    ①申请内存后会自动初始化内存空间为0;
    ②使用calloc进行动态内存分配后应判断指针是否为空,即是否分配成功;
    ③使用完毕以后必须手动释放内存空间,否则会造成内存泄露

    区别:
    malloc 与 calloc
    malloc函数在申请内存时不会进行初始化,为随机值;
    calloc函数可以在申请内存后会自动初始化内存空间为0

    1.4.4 为多个字符串做动态内存

    直接上代码:

    int i=0,n;
    char *color[20], str[15];
        scanf("%d",&n);
        scanf("%s", str);
        while(i<n)
        {
            color[i] = (char *) malloc( sizeof(char) * ( strlen(str) + 1 ));
            strcpy(color[n], str);
            i++;
            scanf("%s", str)
        }
    
        ......
        free (color)
    

    1.5 指针数组及其应用


    p[n]为一个数组,再由int*说明这是一个整型指针数组,它有n个指针类型的数组元素。这里执行p+1时,则p指向下一个数组元素.
    可以这样 p=a; 这里p表示指针数组第一个元素的值,a的首地址的值。

    如要将二维数组赋给一指针数组:
    int *p[3];
    int a[3][4];
    p++; //该语句表示p数组指向下一个数组元素。注:此数组每一个元素都是一个指针
    for(i=0;i<3;i++)
    p[i]=a[i]
    这里int *p[3] 表示一个一维数组内存放着三个指针变量,分别是p[0]、p[1]、p[2]
    所以要分别赋值。


    首先说明p是一个指针,指向一个整型的一维数组,这个一维数组的长度是n,也可以说是p的步长。也就是说执行p+1时,p要跨过n个整型数据的长度。

    如要将二维数组赋给一指针,应这样赋值:
    int a[3][4];
    int (*p)[4]; //该语句是定义一个数组指针,指向含4个元素的一维数组。
    p=a; //将该二维数组的首地址赋给p,也就是a[0]或&a[0][0]
    p++; //该语句执行过后,也就是p=p+1;p跨过行a[0][]指向了行a[1][]
    所以数组指针也称指向一维数组的指针,亦称行指针。

    1.6 二级指针

    指向指针变量的指针

    展开来说就是:一级指针和二级指针的值都是指向一个内存单元,一级指针指向的内存单元存放的是源变量的值,二级指针指向的内存单元存放的是一级指针的地址。

    用代码来表示为:

    int a =100;
    int *p1 = &a;
    int **p2 = &p1;
    

    指针变量也是一种变量,也会占用存储空间,也可以使用&获取它的地址。C语言不限制指针的级数,每增加一级指针,在定义指针变量时就得增加一个星号。p1 是一级指针,指向普通类型的数据,定义时有一个;p2 是二级指针,指向一级指针 p1,定义时有两个。*

    1.7 行指针、列指针

    • 首先要明白两个重要概念:
      • 行指针:指的是一整行,不指向具体元素。

      • 列指针:指的是一行中某个具体元素。

    可以将列指针理解为行指针的具体元素,行指针理解为列指针的地址。

    • 以下列定义的二维数组为例:
      int a[3][4]={1,3,5,7,9,11,13,15,17,19,21,23};

    **如首行一样,将首行视为一个元素,一个特殊的元素,这个“特殊的”元素是一个一维数组。那么这个二维数组是由是由三个“特殊的”元素组成的一个“特殊的”一维数组。

    a是这个“特殊的”一维数组的名称,也就是首地址,也就是第一个元素的地址,也就是第一行的首地址,是指首行一整行,并不是指某个具体元素。那么我们称之为“行指针”。同理:a+0,a+1,a+2,都是行指针。**
    则有:

    我们针对首行分析,首行的元素分别是:a[0][0],a[0][1],a[0][2],a[0][3]。将其看作一个独立的一维数组,那么 a[0]就是这个数组的名称,也就是这个数组的首地址,也就是第一个元素的地址,也就是a[0]+0。a[0]和a[0]+0都是指具体的元素,那么我们称之为“列指针”。

    • 示例1:用列指针输出二维数组。
    #include <stdio.h>
    int main()
    {
       int a[3][4]={1,3,5,7,9,11,13,15,17,19,21,23};
       int *p= a[0];   // 列指针的定义法
       for(; p < a[0] + 12; p++)
       {
         printf("%d ",*p);
       }
        return 0;
    }
    
    • 示例2:用行指针输出整个二维数组。
    #include <stdio.h>
    int main()
    {
      int a[3][4]={1,3,5,7,9,11,13,15,17,19,21,23};
       int (*p)[4]= &a[0]; // 行指针定义法或者int (*p)[4]= a;
       int i, j;
       for(i = 0; i < 3; i++)
    
         for(j = 0; j < 4; j++)
       {
         printf("%d ",*(*(p + i) + j));
       }
        return 0;
    }
    

    2.PTA实验作业(7分)

    2.1 题目名1(2分)

    2.1.1 伪代码

    2.1.2 代码截图

    2.1.3 与同学代码对比

    我的思路是:
    先试着遍历主串,同时看是否有和子串相同的,如果有,记录在主串中的坐标,在子串主串同时移动,直到不相同或者子串结束为止,同时移动结束后,若子串在结束符,说明找到,返回在主串中的位置,否则说明只有部分对应,子串回到首位,等待下一轮同时移动。

    同学的思路:
    遍历a主串
    判断字串中元素是否和主串中一样子,不同则立即退出循环,开启下一轮寻找
    计数的变量等于子串的长度即说明子串完整的找完了
    找到子串,返回在主串中的位置;否则,返回NULL

    • 对比:
      同学用了数组的方法,而我参照超星的讲解选择了指针的做法,实质上是一样的,都是用了两个循环,第一个来变了主串,第二个来实现判断子串元素是否在主串中完整存在。
      个人来说是比较倾向于指针的做法,比较好表示一些。

    2.2 题目名2(2分)

    2.2.1 伪代码

    2.2.2 代码截图

    2.2.3 与同学代码对比

    我的思路:
    借用一个辅助数组,长度为a和b的长度和,遍历a b两个数组,但非同时移动,当a或b任意一个数组还没遍历完时,比较a b中元素的大小,小的先放入辅助数组,同时该数组移向下一个元素,另一个位置不变。当其中任意一个数组到头了,则把另一个数组剩下的所有元素放入辅助数组。最后把辅助数组中的元素复制到a中。

    同学的思路:
    同学的思路和我的几乎一样(找了三个同学思路都一样...)

    2.3 题目名3(3分)

    2.3.1 伪代码

    2.3.2 代码截图

    2.3.3 请说明和超星视频做法区别,各自优缺点。

    具体做法同超星视频讲解做法相似,倒着遍历整个数组,不是空格则放入辅助数组中,遇到空格说明已完整找到一个单词。随即输出。

  • 相关阅读:
    生成数据库结构设计说明书的存储过程
    C#检测上传图片是否安全函数
    设定下拉列表控件DropDownList,复选按钮CheckBox,RadioButton默认值总结测试
    C#批量重命名工具V2.0
    个人一些工作经验方式积累办法
    修身《孟子》尽心篇
    linux 命令 lspci lsusb
    重拾信心
    linux eclipse
    A lunch in Gordon Market with WWF
  • 原文地址:https://www.cnblogs.com/ly1218/p/14199012.html
Copyright © 2020-2023  润新知