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\n", 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():用于从括号内的参数给出的地址开始,依次输出存储单元中的字符,当遇到第一个’\0’时输出结束,并且自动输出一个换行符’\n’

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

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

void MyStrcpy(char *str1,char *str2)
{
        while(*str2!='\0')//若当前str2所指字符不是字符串结束标志
        {
           *str1 = *str2;//复制字符
            str2++;//使str2指向下一个字符
            str1++;//使str1指向下一个存储单元
        }
        *str1 = '\0';//当复制循环结束时,在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! = '\0')//当没有读到str字符串的结束标志
      {
            str1++;//将指针移动到字符串str1的末尾
      }//注意这里,在读到'\0'之前时,指针已经移到了这个位置,当读到时跳出循环不再指向下一个
      所以复制str2的首地址是'\0'之前的位置
      for(;*str2!='\0';str1++,str2++)
      {
            *str1 = *str2;//将字符串str2复制到字符串str1的后面
      }
            *str1 = '\0';//在连接后的字符串的末尾添加字符串结束标志
            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 == '\0')
            return 0;        
        str1++;
        str2++;
    }
    return *str1 - *str2;
}

5. 计算字符串的长度

unsigned int MyStrlen(const char *pStr)//为防止实参在被调函数中被意外修改,在相应的形参前面加上类型限定符const
{
           unsigned int len = 0;//计数器设置为0
           for(;*pStr !='\0';pStr++)
           {
                len++;//循环统计不包括'\0'在内的字符个数
           }
           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 请说明和超星视频做法区别,各自优缺点。

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

posted on 2020-12-27 21:59  木野  阅读(264)  评论(0编辑  收藏  举报