zoukankan      html  css  js  c++  java
  • 指针

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

    0. 展示PTA总分

    1. 本章学习总结

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

    指针定义:指针变量的定义格式为:类型说明符 *指针变量名,如int* p
    指针相关运算

    1. 指针减去一个指针,表示两个指针之间所差的内存单元或者元素个数,两个指针相加没有意义
    2. 关系运算,如果定义了两个指针变量p和q,并且都已经初始化了,
      如果p==q,则表示p和q指向同一个地址
      p>q,则表示p指向高地址而q指向低地址
      也经常拿p和NULL做比较,用来表示指针当前的状态
    3. *的优先级是一级,即最高级,但它的规律是从右向左,所以*p++应当写作(*p)++才对

    int a=8,b;
    int *p;
    p=&a;

    &a 表示变量a的地址,&a=2000
    p 指针变量p的值,p=2000=&a
    *p 表示指针变量p指向的变量,*p=a=8
    &*p 相当于&(p),&(p)=&a=2000
    *&a 相当于(&a),(&a)=*p=a=8
    &p 表示指针变量p的地址,&p=2000
    *&p 相当于(&p),(&p)=2000

    指针做函数参数:

    在C语言中,函数的参数不仅可以是整数、小数、字符等具体的数据,还可以是指向它们的指针。
    用指针变量作函数参数可以将函数外部的地址传递到函数内部,使得在函数内部可以操作函数外部的数据,
    并且这些数据不会随着函数的结束而被销毁。
    像数组、字符串、动态分配的内存等都是一系列数据的集合,没有办法通过一个参数全部传入函数内部,
    只能传递它们的指针,在函数内部通过指针来影响这些数据集合。

    1.2 字符指针

    ①字符指针:指向字符型数据的指针变量。每个字符串在内存中都占用一段连续的存储空间,并有唯一确定的首地址。
    即将字符串的首地址赋值给字符指针,可让字符指针指向一个字符串。
    举个例子:
    char *ptr = "Hello";//将保存在常量存储区的"Hello"的首地址赋值给ptr

    char *ptr;
    ptr = "Hello";//是等价的,注意不能理解为将字符串赋值给ptr
    再举个例子:
    char str[10] = "Hello";
    char *ptr = str;//数组名代表数组的首地址
    //等价于
    char *ptr;
    ptr = str;//等价于ptr = &str[0];将数组的首地址赋给字符指针ptr

    ②对于数组名str,不能使用str++操作使其指向字符串中的某个字符,因为数组名是一个地址常量,其值是不能被改变的。具体如下:
    *(ptr+i):字符串中第i+1个字符,相当于*(str+i),即str[i]
    也可以用ptr++,移动指针ptr,使ptr指向字符中的某个字符

    ③字符串的长度(指字符串所含的字符个数,但不包括最后的’’)与字符数组的大小(指字符串所包含的字符个数加上’’,故+1)不一样。具体如:
    for(i = 0;str[i] != ''; i++ )
    {
    printf("%c",str[i]);//常用借助字符串结束标志''识别字符串的结束
    }
    scanf("%s",str);//表示读入一个字符串,直到遇到空白字符(空格、回车符、制表符)为止
    //如果输入带有空格的字符串(比如人名)只会读到空格以前而空格以后不会读入,留在了输入缓冲区中
    printf("%s",str);//表示输出一个字符串,直到遇到字符串结束标志''为止(注意这里可以带有空格输出)

    注意:scanf()不能限制输入字符串的长度,很容易引起缓冲区溢出。当使用scanf()gets()时,
    确保输入字符串的长度不超过数组的大小,否则使用限制输入字符串长度的函数: fgets(name,sizeof(name),stdin)
    ④if(str1==str2)时合法语句,比较这两个地址的大小(作为指针,有不同的地址,一定为假),而不是比较字符串的大小,也不是比较字符串的内容。
    字符串相关函数及函数代码原型理解:

    1. 字符串比较函数
      int strcmp(const char* str1, const char* str2);
      strcmp函数实际上是对字符的ASCII码进行比较 , 其中str1和str2可以是字符串常量或者字符串变量,返回值为整形,所以区分大小写。
      ① str1<str2,返回负值或者-1; ② str1=str2,返回0; ③ str1>str2,返回正值或者1;

    2.字符串连接函数
    int strcat(char *str1 , char const *str2);
    将字符串str2连接在str1后,并且str1最后的结束字符NULL会被覆盖掉,并且连接后的字符串的尾部会再增加一个NULL.
    注意:str1和str2所指的内存空间不能重叠,且str1要有足够的空间来容纳要复制的字符串。返回石str1字符串的首地址。在str1的末尾注意放结束标志''。

    3.字符串复制函数
    char * strcpy( char *str1, char const *str2 ); //字符串复制函数
    将str2所指的字符串复制到str1所指的字符串中。注意:src1和str2所指内存区域不可以重叠且str1必须有足够的空间来容纳str2的字符串。
    另外,还有strncpy函数,与strcpy函数功能相似
    语法:char *strncpy(char *destinin, char *source, int maxlen);
    参数:
    destinin:表示复制的目标字符数组;
    source:表示复制的源字符数组;
    maxlen:表示复制的字符串长度。
    复制字符串source中的内容(字符,数字、汉字....)到字符串destinin中,复制多少由maxlen的值决定。
    如果source的前n个字符不含NULL字符,则结果不会以NULL字符结束。
    如果n<source的长度,只是将source的前n个字符复制到destinin的前n个字符,不自动添加'',也就是结果destinin不包括'',需要再手动添加一个''。
    如果source的长度小于n个字节,则以NULL填充destinin直到复制完n个字节。source和destinin所指内存区域不可以重叠且destinin必须有足够的空间来容纳source的字符长度+''。
    strcpy与strncpy的区别:
    strcpy只是复制字符串,但不限制复制的数量,很容易造成缓冲溢出。strncpy要安全一些。
    strncpy能够选择一段字符输出,strcpy则不能。

    4.字符串求长度
    int strlen(char *s); //s为指定的字符串

    5.字符串转数字
    int atoi (const char * str);//转化成整形,头文件:#include <stdlib.h>
    atoi() 函数会扫描参数 str 字符串,跳过前面的空白字符(例如空格,tab缩进等,可以通过 isspace() 函数来检测),
    直到遇上数字或正负符号才开始做转换,而再遇到非数字或字符串结束时('')才结束转换,并将结果返回。
    【返回值】返回转换后的整型数;如果 str 不能转换成 int 或者 str 为空字符串,那么将返回 0。
    另外atol (将字串转换成长型数);atof (将字串转换成浮点型数) ;strtod(将字符串转成double);
    strtol(char *nptr,char **endptr,int base)(转化为long int);strtoul (将字串转换成无符号32位型数)

    6.小写转大写
    int toupper( int ch );//转大写

    7.大写转小写
    int tolower( int ch );//转小写
    拓展:字符处理函数

    函数原型 功能描述
    int isdigit(int c); 判断是否为数字,返回真假值
    int isalpha(int c); 判断是否为字母,返回真假值
    int isalnum(int c) 判断是否为字母或者数字,返回真假值
    int islower(int c) 判断是否为小写字母,返回真假值
    int isupper(int c) 判断是否为大写字母,返回真假值
    int isspace(int c) 若c是空白符(换行符(’ ’)、空格符(’ ‘)、换页符(’f’)、回车符(’ ’)、水平(’ ’)、垂直制表符(’v’)),返回真值

    1.3 指针做函数返回值

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

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

    1.4 动态内存分配

    动态内存是相对静态内存而言的。所谓动态和静态就是指内存的分配方式。动态内存是指在堆上分配的内存,而静态内存是指在栈上分配的内存。
    前面所写的程序大多数都是在栈上分配的,比如局部变量、形参、函数调用等。栈上分配的内存是由系统分配和释放的,空间有限,在复合语句或函数运行结束后就会被系统自动释放。
    而堆上分配的内存是由程序员通过编程自己手动分配和释放的,空间很大,存储自由。
    malloc函数的使用
    malloc 是一个系统函数,它是 memory allocate 的缩写。其中memory是“内存”的意思,allocate是“分配”的意思。
    顾名思义 malloc 函数的功能就是“分配内存”。要调用它必须要包含头文件<stdlib.h>。它的原型为:
    # include <stdlib.h>
    void *malloc(unsigned long size);
    例如如下代码:
    double * ptd;
    ptd = (double * ) malloc (30 * sizeof(double));
    malloc 函数只有一个形参,并且是整型。该函数的功能是在内存的动态存储空间即堆中分配一个长度为size的连续空间。函数的返回值是一个指向所分配内存空间起始地址的指针,类型为 void*型。
    简单的理解,malloc 函数的返回值是一个地址,这个地址就是动态分配的内存空间的起始地址。如果此函数未能成功地执行,如内存空间不足,则返回空指针 NULL。
    注意:在定义指针数组后,一定要给它动态内存分配以及有指向,避免成为野指针。另外,在使用完分配的内存后要给与内存释放,具体如下
    free函数的使用:
    前面讲过,动态分配的内存空间是由程序员手动编程释放的。那么怎么释放呢?用 free 函数。
    free 函数的原型是:
    # include <stdlib.h>
    void free(void *p);
    free 函数无返回值,它的功能是释放指针变量 p 所指向的内存单元。此时 p 所指向的那块内存单元将会被释放并还给操作系统,不再归它使用。操作系统可以重新将它分配给其他变量使用。
    需要注意的是,释放并不是指清空内存空间,而是指将该内存空间标记为“可用”状态,使操作系统在分配内存时可以将它重新分配给其他变量使用。

    p 所指向的内存空间是一样的。所以释放后 p 所指向的仍然是那块内存空间。既然指向的仍然是那块内存空间,那么就仍然可以往里面写数据。可是释放后该内存空间已经不属于它了,该内存空间可能会被分配给其他变量使用。如果其他变量在里面存放了值,而你现在用 p 往里面写入数据就会把那个值给覆盖,这样就会造成其他程序错误。所以当指针变量被释放后,要立刻把它的指向改为 NULL。

    1.5 指针数组及其应用

    指针数组(也就是元素为指针类型的数组)常常作为二维数组的一种便捷替代方式。一般情况下,这种数组中的指针会指向动态分配的内存区域。
    例如:
    char *arr[4] = {"hello", "world", "shannxi", "xian"};
    //arr就是我定义的一个指针数组,它有四个元素,每个元素是一个char *类型的指针,这些指针存放着其对应字符串的首地址。
    这样写往往可以避免内存浪费,那么我们来看看指针数组都有哪些应用
    1. 指针数组在参数传递时的使用
    如果是向子函数传参,这和传递一个普通数组的思想一样,不能传递整个数组过去,如果数组很大,这样内存利用率很低,所以应该传递数组的首地址,
    用一个指针接收这个地址。因此,指针数组对应着二级指针,关于二级指针,后面会详细介绍。
    2. 指针数组的排序
    指针数组的排序非常有趣,因为这个数组中存放的是指针,通过比较指针指向的空间的大小,排序这些空间的地址。函数实现如下:
    void sort(char **pa, int n)//冒泡排序
    {
    int i, j;
    char *tmp = NULL;

    for(i = 0; i < n-1; i++)
    {
    for(j = 0; j < n-1-i; j++)
    {
    if((strcmp(*(pa+j), *(pa+j+1)) > 0)
    {
    tmp = *(pa + j);
    *(pa + j) = *(pa + j + 1);
    *(pa + j + 1) = tmp;
    }
    }
    }
    }
    3. 指针数组与二维数组的区别

    在C语言中,一个字符串常量不可以直接赋给一个数组,比如:char s[10]; s="hello";这种写法是错误的。
    但是一个字符串却可以直接赋给一个指针:char *p; p="hello";这仅限于将一个字符串常量的地址赋给指针p,
    但如果char s[10]; char *p=s; *p="hello";这就是非法的,这就相当于直接把字符串常量赋给一个数组,
    其实字符串常量赋给一个数组可以采用strcpy()函数。

    1.6 二级指针

    如果一个指针指向的是另外一个指针,我们就称它为二级指针,或者指向指针的指针。
    假设有一个 int 类型的变量 a,p1是指向 a 的指针变量,p2 又是指向 p1 的指针变量,它们的关系如下图所示:

    例如:
    int a=10;
    int *p=&a; //取a的地址传给p
    int *(*pp)=&p; //取p的地址传给pp

    1.7 行指针、列指针

    行指针:指的是一整行,不指向具体元素。
    列指针:指的是一行中某个具体元素。
    可以将列指针理解为行指针的具体元素,行指针理解为列指针的地址。
    那么两个概念之间的具体转换是:
    *行指针----列指针
    &列指针----行指针

    行指针 转换成:列指针 列指针等价表示 内容 内容等价表示 含义
    a或a+0 *a a[0] *a[0] *(*a) a[0][0]
    a+1 *(a+1) a[1] *a[1] *(*(a+1)) a[1][0]
    a+2 *(a+2) a[2] *a[2] *(*(a+2)) a[2][0]

    int a[3][4]={1,3,5,7,9,11,3,15,17,19,21,23};

    表示形式 含义 指针类型
    a或者a+0 指向第0行 行指针
    a+1 指向第1行 行指针
    a+2 指向第2行 行指针

    示例1:用列指针输出二维数组。

    示例2:用行指针输出整个二维数组。

    2. PTA实验作业

    2.1 7-2 藏尾诗

    2.1.1 伪代码:
    定义一个二维数组str1[4][20],输入诗,注意没有输入的放'',或者以指针数组的形式输入
    定义一个一维数组str2[10],存放诗的尾
    判断每一行诗的长度,将最后两个字符输入到str2中,注意下标要自增,如str2[j++] = str1[i][lens - 2];
    最后在str2的末尾放结束标志符
    输出str2
    2.1.2 代码截图

    2.2.3 找一份同学代码

    学习点:使用了动态分配,使代码更健壮,因为用户输入的也可能是一首词,用数组的话就需要重新分配更大的内存,就容易造成内存浪费。

    2.2 6-9 合并两个有序数组

    伪代码:
    因为一边排序一遍调整数组a会破坏数组a的数据,所以应先排序,放到另一个数组中,再复制数组到数组a即可
    先定义新数组c,即各数组的小标aIndex,bIndex,cIndex并赋初值为0。
    while (aIndex < m && bIndex < n)//读数组,当某一个数组结束时循环结束。
    {
    两个数组的值进行比较并排序
    if (*(a + aIndex) < *(b + bIndex)a数组下标为aIndex的数存放到数组c中
    else b数组小标为bIndex的数存放到c数组中
    }
    end while
    while (aIndex < m)
    将剩下的数据直接复制到c数组
    end while
    while (bIndex < n)
    同上一个while
    end while
    复制数组c到数组a

    2.2.2 代码截图

    2.2.3 找一份同学代码


    学习点:相较我的代码会显得很简洁,语句很精简,如num[j++]=num[k++];能够熟练掌握并运用二级指针。

    2.3 7-4 说反话-加强版

    2.3.1 伪代码
    int main(void)
    {
    static char input[500001];存放输入的数据
    int begin每个单词开头的下标, end每个单词结尾的下标,sign = 0判断一个单词是否读完,读完就要开始输出了;
    int i = 0, length数组长度,含换行符, j;
    length = strlen(input);
    for (逆向遍历数组)
    {
    if (读到空格时)
    {
    if (一个单词读完,开始输出这个单词)
    {
    sign = 0;
    for (输出从begin到end的字符)
    if (不是第一个单词,则输出空格)
    }
    }
    else确定begin和end的值
    {
    begin = i;
    if (sign == 0)end = i;
    sign = 1;
    }
    }
    if (input[0] != ' ')第一个单词要单独输出,如果第一个单词前有空格,则输出已经在上面完成了
    输出第一个单词
    }

    2.3.2 代码截图

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

    超星视频做法:逆向遍历数组,每个单词计算其长度,读到空格时输出该单词,并清除单词长度len的值为0,循环下去直到读到第一个字符,如果第一个字符前无空格,则需加判断条件单独输出。
    在看完超星视频的做法后,感觉自己的做法很麻烦,而且思路较为混乱、繁琐,而超星视频里还有很多值得学习的做法,如输出控制输出个数,逆向遍历数组的方法,对第一个单词单独处理的办法。

  • 相关阅读:
    Android屏幕尺寸单位转换
    详细解读KMP模式匹配算法
    自定义View实现钟摆效果进度条PendulumView
    解决使用属性动画没有效果,监听发现属性值未发生改变问题
    数组----二维数组中的查找
    JS(二)
    JS(一)
    CSS(二)
    css(一)
    链表----删除链表中重复的节点
  • 原文地址:https://www.cnblogs.com/ywk2002/p/14196950.html
Copyright © 2011-2022 走看看