• C语言博客作业05--指针


    这个作业属于哪个班级 C语言--网络2011/2012
    这个作业的地址 C博客作业05--指针
    这个作业的目标 学习指针相关内容
    姓名 廖浩轩

    0.展示PTA总分

    1.本章学习总结

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

    指针定义
    定义指针变量与定义普通变量非常类似,不过要在变量名前面加星号*,格式为:

    datatype *name;
    

    *表示这是一个指针变量,datatype表示该指针变量所指向的数据的类型 。例如:

    int *p1;
    

    在定义指针变量 p 的同时对它进行初始化,并将变量 a 的地址赋予它,此时 p 就指向了 a。值得注意的是,p 需要的一个地址,a 前面必须要加取地址符&,否则是不对的。

    int a = 100;
    int *p = &a;
    

    *是一个特殊符号,表明一个变量是指针变量,定义 p1、p2 时必须带* 。而给 p1、p2 赋值时,因为已经知道了它是一个指针变量,就没必要多此一举再带上*,后边可以像使用普通变量一样来使用指针变量。也就是说,定义指针变量时必须带*,给指针变量赋值时不能带*

    //定义普通变量
    float a = 99.5, b = 10.6;
    char c = '@', d = '#';
    //定义指针变量
    float *p1 = &a;
    char *p2 = &c;
    //修改指针变量的值
    p1 = &b;
    p2 = &d;
    

    指针相关运算
    *可以称为指针运算符,用来取得某个地址上的数据,请看下面的例子:

    #include <stdio.h>
    int main()
    {
        int a = 15;
        int *p = &a;
        printf("%d, %d
    ", a, *p);  //两种方式都可以输出a的值
        return 0;
    }
    
    运行结果:15 15
    

    指针除了可以获取内存上的数据,也可以修改内存上的数据,例如:

    #include <stdio.h>
    int main()
    {
        int a = 15, b = 99, c = 222;
        int *p = &a;  //定义指针变量
        *p = b;  //通过指针变量修改内存上的数据
        c = *p;  //通过指针变量获取内存上的数据
        printf("%d, %d, %d, %d
    ", a, b, c, *p);
        return 0;
    }
    
    运行结果:99, 99, 99, 99
    

    指针变量也可以出现在普通变量能出现的任何表达式中,例如:

    #include <stdio.h>
    int main()
    {
          int x, y, *px = &x, *py = &y;
          y = *px + 5;  //表示把x的内容加5并赋给y,*px+5相当于(*px)+5
          y = ++*px;  //px的内容加上1之后赋给y,++*px相当于++(*px)
          y = *px++;  //相当于y=*(px++)
          py = px;  //把一个指针的值赋给另一个指针
        return 0;
    }
    
    

    指针做函数参数
    用指针变量作参数交换两个变量的值:

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

    调用 swap() 函数时,将变量 a、b 的地址分别赋值给 p1、p2,这样 p1、p2 代表的就是变量 a、b 本身,交换 p1、p2 的值也就是交换 a、b 的值。函数运行结束后虽然会将 p1、p2 销毁,但它对外部 a、b 造成的影响是“持久化”的,不会随着函数的结束而“恢复原样”。

    1.2 字符指针

    指针如何指向字符串
    使用指针的方式来输出字符串(字符数组):

    #include <stdio.h>
    #include <string.h>
    int main() 
    {
        char str[] = "Hello World";
        char* pstr = str;
        int len = strlen(str), i;
        
        for (i = 0; i < len; i++) //使用*(pstr+i)
        {
            printf("%c", *(pstr + i));
        }
        printf("
    ");
        
        for (i = 0; i < len; i++) //使用pstr[i]
        {
            printf("%c", pstr[i]);
        }
        printf("
    ");
        
        for (i = 0; i < len; i++) //使用*(str+i)
        {
            printf("%c", *(str + i));
        }
        printf("
    ");
        return 0;
    }
    
    运行结果:
    Hello World
    Hello World
    Hello World
    

    直接使用一个指针指向字符串:

    char *str = "Hello World";
    或者
    char *str;
    str = "Hello World";
    
    • 输出这种字符串的方式和输出字符数组的方式基本类似,但两者存在区别:
      它们最根本的区别是在内存中的存储区域不一样,字符数组存储在全局数据区或栈区,第二种形式的字符串存储在常量区。全局数据区和栈区的字符串(也包括其他数据)有读取和写入的权限,而常量区的字符串(也包括其他数据)只有读取权限,没有写入权限。
      内存权限的不同导致的一个明显结果就是,字符数组在定义后可以读取和修改每个字符,而对于第二种形式的字符串,一旦被定义后就只能读取不能修改,任何对它的赋值都是错误的。

    字符串相关函数

    • 字符串求长度:strlen(str)
      例:len=strlen(str);
    • 字符串比较函数:strcmp(str1,str2)
      strcmp函数实际上是对字符的ASCII码进行比较 , 其中str1和str2可以是字符串常量或者字符串变量,返回值为整形,所以区分大小写。
      ① str1<str2,返回负值或者-1; ② str1=str2,返回0; ③ str1>str2,返回正值或者1;
    • 字符串连接函数:strcat(str1,str2)
      将字符串str2连接在str1后,并且str1最后的结束字符NULL会被覆盖掉,并且连接后的字符串的尾部会再增加一个NULL.注意:str1和str2所指的内存空间不能重叠,且str1要有足够的空间来容纳要复制的字符串。返回石str1字符串的首地址。
    • 字符串复制函数:strcpy(str1,str2)和strncpy(str1,str2,n)
      将str2所指的(n个)字符串复制到str1所指的字符串中。注意:src1和str2所指内存区域不可以重叠且str1必须有足够的空间来容纳str2的字符串。

    拓展

    • 字符判断函数
    int isalpha(char ch)  若ch是字母('A'-'Z','a'-'z')返回非0值,(返回1024)否则返回0
    
    int isalnum(char ch)  若ch是字母('A'-'Z','a'-'z')或数字('0'-'9'),返回非0值,否则返回0
    
    int isascii(char ch)  若ch是字符(ASCII码中的0-127)返回非0值,否则返回0
    
    int iscntrl(char ch)  若ch是作废字符(0x7F)或普通控制字符(0x00-0x1F),返回非0值,否则返回0
    
    int isdigit(char ch)  若ch是数字('0'-'9')返回非0值,否则返回0
    
    int isgraph(char ch)  若ch是可打印字符(不含空格)(0x21-0x7E)返回非0值,否则返回0
    
    int islower(char ch)  若ch是小写字母('a'-'z')返回非0值,否则返回0
    
    int isupper(char ch)  若ch是大写字母('A'-'Z')返回非0值,否则返回0
    
    int isprint(char ch)  若ch是可打印字符(含空格)(0x20-0x7E)返回非0值,否则返回0
    
    int ispunct(char ch)  若ch是标点字符(0x00-0x1F)返回非0值,否则返回0
    
    int isspace(char ch)  若ch是空格(' '),水平制表符('	'),回车符('
    '),走纸换行('f'),垂直制表符('v'),换行符('
    ')  返回非0值,否则返回0
    
    int isxdigit(char ch) 若ch是16进制数('0'-'9','A'-'F','a'-'f')返回非0值,  否则返回0
    

    1.3 指针做函数返回值

    C语言允许函数的返回值是一个指针(地址),我们将这样的函数称为指针函数。下面的例子定义了一个函数 strlong(),用来返回两个字符串中较长的一个:

    #include <stdio.h>
    #include <string.h>
    char *strlong(char *str1, char *str2)
    {
        if(strlen(str1) >= strlen(str2))
        {
            return str1;
        }
        else
        {
            return str2;
        }
    }
    int main()
    {
        char str1[30], str2[30], *str;
        gets(str1);
        gets(str2);
        str = strlong(str1, str2);
        printf("Longer string: %s
    ", str);
        return 0;
    }
    

    注意
    函数运行结束后会销毁在它内部定义的所有局部数据,包括局部变量、局部数组和形式参数,函数返回的指针请尽量不要指向这些数据,C语言没有任何机制来保证这些数据会一直有效,它们在后续使用过程中可能会引发运行时错误

    1.4 动态内存分配

    为什么要动态内存分配
    内存不是取之不尽用之不竭,4g、8g、16g是常见的电脑内存大小,打开任务管理器,能看到不同的应用占据的内存情况。如果一个应用程序占了大部分内存,估计别的应用就资源紧张了,那这个应用可能会被卸载,找个节省内存的。

    精简的应用能更有效地使用内存,而不是埋头搞业务逻辑,最后却整出来非常耗费资源的应用来。

    在资源使用很小,代码量很小的时候,很少会涉及到内存泄漏的问题,也就不涉及内存管理的事情,尤其是当前C语言教学陈旧的教材,里面陈旧的习题,和内存管理几乎不沾边,学过的人不会意识到内存管理有什么用。

    堆区和栈区区别
    栈区:存放函数的参数值、局部变量等,由编译器自动分配和释放,通常在函数执行完后就释放了,其操作方式类似于数据结构中的栈。栈内存分配运算内置于CPU的指令集,效率很高,但是分配的内存量有限,比如iOS中栈区的大小是2M。

    堆区:就是通过new、malloc、realloc分配的内存块,编译器不会负责它们的释放工作,需要用程序区释放。分配方式类似于数据结构中的链表。“内存泄漏”通常说的就是堆区。

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

    • malloc和free函数
      (1)void *malloc(size_t size);
      malloc的参数就是需要分配的内存字节数。malloc分配一块连续的内存。如果操作系统无法向malloc提供更多的内存,malloc就返回一个NULL指针。

    (2)void free(void *pointer);
    free的参数要么是NULL,要么是一个先前从malloc、calloc或realloc返回的值。

    • calloc和realloc函数
      (1)void *calloc(size_t num_elements,size_t element_size);
      calloc也用于内存分配。malloc和calloc之间的主要区别是后者在返回指向内存的指针之前把它初始化为0。

    (2)realloc(void *ptr,size_t new_size);
    realloc函数用于修改一个原先已经分配的内存块的大小。如果它用于扩大一个内存,那么这块内存原先的内容依然保留,新增加的内存添加到原先内存块的后面。如果它用于缩小一个内存块,该内存块尾部的部分内存被拿掉,剩余部分内存的原先内容依然保留。

    (1)malloc(size) 在内存的动态存储区中分配一个长度为size的连续空间。
    (2)calloc(n,size) 在内存的动态存储区中分配n个长度为size的连续空间。
    (3)free(p) 释放指针变量p做指向的动态空间。
    (4)realloc(p,size) 将指针变量p指向的动态空间大小改变为size。
    

    使用案例:

    #include<stdio.h>
    #include<stdlib.h>
     
    int main()
    {
      void check(int *);
      int *p1, i;
      p1 = (int *)malloc(5*sizeof(int));
      for ( i = 0; i < 5; i++)
       scanf("%d",p1+i);
      check(p1);
      getchar();
      getchar();
      return 0;
    }
    void check(int *p)
    {
      int i;
      for (i = 0; i < 5; i++)
      if (p[i] < 60) printf("%d", p[i]);
      printf("
    ");
    }
    

    程序没有定义数组,而是开辟了一段动态自由分配区,输入数字时,按照地址复制给动态数组的5个元素,p1指向第一个整型数据,调用check函数时,p1作为实参传递给形参p,因此可以理解为形参p和实参p1公享一段动态分配区。

    1.5 指针数组及其应用

    如果一个数组中的所有元素保存的都是指针,那么我们就称它为指针数组。指针数组的定义形式一般为:

    dataType *str[len];
    可以理解为
    dataType *(str[len]);
    括号里面说明str是一个数组,包含了len个元素,括号外面说明每个元素的类型为dataType *
    

    除了每个元素的数据类型不同,指针数组和普通数组在其他方面都是一样的,例子:

    #include <stdio.h>
    int main() 
    {
        int a = 16, b = 932, c = 100;
        int* arr[3] = { &a, &b, &c };//定义一个指针数组,也可以不指定长度,直接写作 int *arr[]
        int** parr = arr;//定义一个指向指针数组的指针
        printf("%d, %d, %d
    ", *arr[0], *arr[1], *arr[2]);
        printf("%d, %d, %d
    ", **(parr + 0), **(parr + 1), **(parr + 2));
        return 0;
    }
    
    运行结果:
    16, 932, 100
    16, 932, 100
    

    1.6 二级指针

    假设有一个 int 类型的变量 a,p1是指向 a 的指针变量,p2 又是指向 p1 的指针变量,它们的关系如下图所示:

    转换为C语言代码:

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

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

    1.7 行指针、列指针

    行指针

    行指针是指向数组的指针,即上面几种指针类型中的 int (*a)[5];所以,当二维数组要被当做参数进行传递时,可以这样声明:

    void fun(int p[][5]);
    

    列指针

    用列指针输出二维数组:

    #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.PTA实验作业

    2.1 计算最长的字符串长度

    2.1.1 伪代码

    int max_len(char* s[], int n)
    {
    int count0 = 0, count1 = 0;    //设置count1为计数器,count0为计数存档
    char* p;                  //设置指针p用于扫描
    for (int i = 0; i < n; i++)//进行循环
          {
                让p与指针s[i]指向同一空间
                while (*p != NULL)//开始扫描这一行字符
                      {
                            每扫描一个字符计数器count1加一
                            同时p向前移动一位
                      }
               扫描完第一个指针指向的字符串,将字符数存入count0
                if
                      {
                            当后面扫描到更长的字符串,替换count0的值
                      {
                 count1 = 0; //每一轮扫描结束,指向下一个字符串时count1从0开始计数
          }
     return count0; //最后返回count0,就是已扫描到的最长的字符串的个数
    }
    

    2.1.2 代码截图

    2.1.3 找一份同学代码(尽量找思路和自己差距较大同学代码)比较,说明各自代码特点

    2.2 合并两个有序数组

    2.2.1 伪代码

    void merge(int* a, int m, int* b, int n)
    {
     int i = m-1, j = n-1, k = m+n-1;//定义三个变量i,j,k,分别指向a,b,和混合数组的末尾
     while (i >= 0 && j >= 0) 
    {
          if a[i]>b[j]        说明要把a[i]先加入混合数组的末尾,加入后i和k都要自减1
          else 即a[i]<b[j]    就把b[j]加入混合数组的末尾,之后j和k也要自减1
    }
     while(j>=0)   //循环结束后,有可能i和j还大于等于0,若j大于等于0,那么需要继续循环
    {
          将b中的数字继续拷入a
    }
    //若是i大于等于0,那么就不用管,因为混合数组本身就a中
    }
    

    2.2.2 代码截图

    2.2.3 找一份同学代码(尽量找思路和自己差距较大同学代码)比较,说明各自代码特点

    同学代码

    同学代码把数组数组放入新数组中,并找到最大值,然后再进行输出
    我的代码把数组从后往前记录,依次放入对应最大值

    2.3 说反话-加强版

    2.3.1 伪代码

     char string[500001];
     char* p;//定义字符串数组和指针
     p = gets(string);    //将字符串的首地址赋给p
     p = p + strlen(string) - 1;    //将p移动到最后一位
    while (*p == ' ')//去除结尾的空格
    while (p > string)//由后往前输出
    {
           if (*(p - 1) == ' ')  遇到空格输出p
          {
                 while (*(p - 1) == ' ')  删除多个空格
                 if (p != string)  输出空格
          }
     p--
    }
    
    打印第一个单词
    

    2.3.2 代码截图


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

    借鉴了超星视频的做法,用我的理解做了一遍,省略了len,但调试了很多次,代码阅读上比较复杂,超星视频对代码的解读更详细清楚
    附上超星视频伪代码:

  • 相关阅读:
    POJ2774 Long Long Message
    Lyndon Word相关
    后缀自动机(SAM)
    后缀数组(SA)
    [THUSC2016]补退选
    [HNOI2008]GT考试
    CF1080E Sonya and Matrix Beauty
    [JSOI2008]火星人
    两道FFT题目略解
    网络流概念+EdmondKarp算法+Dinic(Dinitz)
  • 原文地址:https://www.cnblogs.com/miao-witch/p/14197540.html
Copyright © 2020-2023  润新知