• 第六次博客


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

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

    整理指针主要知识点,必须包含内容有:

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

    1.指针定义

    什么是指针?

    指针是一个变量,其值为另一个变量的地址,即,内存位置的直接地址。就像其他变量或常量一样,您必须在使用指针存储其他变量地址之前,对其进行声明。

    指针变量的定义格式为:

    类型说明符 *指针变量名 [=初值];

    要同时定义两个指针变量:

    int p1=NULL,p2=NULL;

    (NULL是c语言中预定义的空指针关键字,它作为一个特殊的指针,表示不指向任何对象)

    定义指针变量时要注意以下几点:

    (1)类型说明符表示该指针变量所指向的变量的数据类型。

    (2)定义指针变量时,指针变量名前必须有一个“ * ”号,表示定义的变量是指针变量。

    (3)指针变量在定义时允许对其初始化

    指针变量的使用

    两个重要运算符:

    (1)&:取地址运算符,如p=&a; 则p为变量a的地址。

    (2):指针运算符,后面只能接指针变量。用于访问指针变量所指向的变量。如:*p表示访问指针变量p所指向的变量的内容

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

    对变量a有两种访问方式:

    (1)直接访问。b=a;

    (2)通过指针变量间接访问。b=*p;

    注意:指针是一个地址,而指针变量是存放地址的变量。

    2.指针运算

    (1)指针和整数的加减运算:

    可以通过指针与整数的加减运算来移动指针p,实现对不同数据单元的访问操作。对不同的数据类型,移动的单位长度不同。单位长度一般是指针所指向的变量的
    数据类型长度。

    格式一:p=p+n;(或p=p-n;)

    格式二:p++;(或p–;)

    格式三:++p;(或–p;)

    注意:指针变量的值加1(或减1),并不是给地址加1(或减1),而是加上(或减去)1个数据类型长度,也就是指针所指向的变量在内存中所占的字节数。

    (2)指针和指针的赋值运算:

    int a=10,*p,*q;
    p=&a;
    q=p;
    //p和q的值都是变量a的地址。
    

    3.指针做函数参数

    ·实参和形参之间的数据传输是单向的“值传递”方式,也就是实参可以影响形参,而形参不能影响实参。指针变量作为参数

    也不例外,但是可以改变实参指针变量所指向的变量的值。

    ·在C语言中,函数的参数不仅可以是整数、小数、字符等具体的数据,还可以是指向它们的指针。用bai指针变量作函数参数可以将函数外部的地址传递到函数内部,使得在函数内部可以操作函数外部的数据,并且这些数据不会随着函数的结束而被销毁。

    ·像数组、字符串、动态分配的内存等都是一系列数据的集合,没有办法通过一个参数全部传入函数内部,只能传递它们的指针,在函数内部通过指针来影响这些

    数据集合。有的时候,对于整数、小数、字符等基本类型数据的操作也必须要借助指针,一个典型的例子就是交换两个变量的值。

    #include <stdio.h>
    void swap1(int x,int y),swap2(int *px,int *py),swap3(int *px,int *py);
    int main(void)
    {
    	int a=1,b=2;
    	int *pa=&a,*pb=&b;
    	swap1(a,b);
    	printf("s1:a=%d,b=%d
    ",a,b);
     
    	a=1,b=2;
    	swap2(pa,pb);
    	printf("s2:a=%d,b=%d
    ",a,b);
     
    	a=1,b=2;
    	swap3(pa,pb);
    	printf("s3:a=%d,b=%d
    ",a,b);
    	
    	return 0;
    }
    void swap1(int x,int y)
    {
    	int t;
    	t=x;
    	x=y;
    	y=t;
    }
     
    void swap2(int *px,int *py)
    {
    	int t;
    	t=*px;
    	*px=*py;
    	*py=t;
    }
     
    void swap3(int *px,int *py)
    {
    	int *pt;
    	pt=px;
    	px=py;
    	py=pt;
    }
    

    第一个swap1函数运行结果是:x=2 y=1.但是第一个函数的形参是(int x,int y),实参是(int a,int b)。因为实参可以影响形参,而形参不可以影响实

    参。所以a,b的值是不变的。



    第二个swap2函数由图可知,pa和px都是a的地址,pb和py都是b的地址。此函数改变px,py的值。因为px和a在同一储存单元,py和b在同一储存单元。以改

    变实参指针变量所指向的变量的值。所以pa和pb的值也改变了,最后输出结果也就改变了。

    第三个swap3同样的道理,如上图直接改变了形参指针px和py的值,改变的只是地址,地址所对应的值没有改变,又因为形参是不会影响实参,所以pa和pb没有

    改变。所以a,b值没有改变。

    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)不一样。

    ·scanf("%s",str);表示读入一个字符串,直到遇到空白字符(空格、回车符、制表符)为止
    如果输入带有空格的字符串(比如人名)只会读到空格以前而空格以后不会读入,留在了输入缓冲区中

    printf("%s",str);表示输出一个字符串,直到遇到字符串结束标志''为止(注意这里可以带有空格输出)

    ·gets():可以输入带有空格的字符串。以回车符作为字符串的终止符,同时将回车符从输入缓冲区读走,但不作为字符串的一部分。而scanf()不读走回车符,
    回车符仍留在输入缓冲区中。 gets()不能限制输入字符串的长度,很容易引起缓冲区溢出。同样即使scanf(“12s%”,name)也不能解决这个问题。当使用
    scanf()和gets()时,确保输入字符串的长度不超过数组的大小,否则使用限制输入字符串长度的函数: fgets(name,sizeof(name),stdin)

    puts():用于从括号内的参数给出的地址开始,依次输出存储单元中的字符,当遇到第一个’’时输出结束,并且自动输出一个换行符’ ’

    #include <stdio.h>
    #define N 12
    int main()
    {
        char name[N];
        char *ptrName = name;//声明一个指向数组name的字符指针ptrName
        //注意这里,用字符指针输入字符串前提,必须保证字符指针事先已指向一个数组的首地址,如ptrName指向了数组name,如果只是单纯声明字符指针,编译会报错
        printf("Enter your name:");
        gets(ptrName);//输入字符串存入字符指针ptrName所指向的内存
        //为提高程序安全性,也可以使用fgets(name,sizeof(name),stdin);
        printf("Hello %s!
    ",ptrName);
        return 0;
    }
    

    函数

    ·字符串比较函数

    int strcmp(const char* str1, const char* str2);

    字符串连接函数

    int strcat(char *str1 , char const *str2);

    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的首地址
    }
    

    字符串复制函数

    ·char * strcpy( char *str1, char const *str2 );

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

    ·字符串求长度

    int strlen(char *s);

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

    ·字符串转数字

    int atoi (const char * str);

    ·数字转字符串

    int sprintf( char *buffer, const char *format [, argument] ... );

    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语言没有任何机

    制来保证这些数据会一直有效,它们在后续使用过程中可能会引发运行时错误。

    #include <stdio.h>
    int *func()
    {
        int n = 100;
        return &n;
    }
    int main()
    {
        int *p = func(), n;
        n = *p;
        printf("value = %d
    ", n);
        return 0;
    }
    

    运行结果:

    value = 100
    

    修改后

    #include <stdio.h>
    int *func()
    {
        int n = 100;
        return &n;
    }
    int main()
    {
        int *p = func(), n;
        printf("c.biancheng.net
    ");
        n = *p;
        printf("value = %d
    ", n);
        return 0;
    }
    

    运行结果:

    c.biancheng.net
    value = -2
    

    第一个例子在调用其他函数之前使用 *p 抢先获得了 n 的值并将它保存起来,第二个例子显然没有抓住机会,有其他函数被调用后才使用 *p 获取数据,这个

    时候已经晚了,内存已经被后来的函数覆盖了,而覆盖它的究竟是一份什么样的数据我们无从推断(一般是一个没有意义甚至有些怪异的值)。

    上述提到的销毁并不是将局部数据所占用的内存全部抹掉,而是程序放弃对它的使用权限,弃之不理,后面的代码可以随意使用这块内存。对于上面的两个例

    子,func() 运行结束后 n 的内存依然保持原样,值还是 100,如果使用及时也能够得到正确的数据,如果有其它函数被调用就会覆盖这块内存,得到的数据

    就失去了意义。

    1.4 动态内存分配

    为什么要动态内存分配,堆区和栈区区别。动态内存分配相关函数及用法。举例为多个字符串做动态内存要如何分配。

    为什么要动态内存分配

    ·因为内存太宝贵。

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

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

    什么时候需要动态分配内存

    当程序中有比较大的数据块需要使用内存的时候使用。原因:比较大的数据块如果使用了静态内存,在该数据块运行完毕后不能动态的释放该内存,直到整个程

    序运行完才能释放,如果整个程序比较大,有可能因为内存不够而发生错误。

    堆区和栈区区别

    定义:

    栈区:

    存放函数的参数值、局部变量等,由编译器自动分配和释放,通常在函数执行完后就释放了,其操作方式类似于数据结构中的栈。栈内存分配运算内置于CPU的

    指令集,效率很高,但是分配的内存量有限,比如iOS中栈区的大小是2M。

    堆区:

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

    的就是堆区。

    栈区和堆区大小差异

    栈区:由图中其实可以知道,栈区是向低地址扩展的,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,大小在进程
    分配时是确定的,具体大小看编译器,操作系统。所需大小一般小于10M!太大没有意义,不符合栈是用来快速存取的目标。

    堆区:堆区是向高地址扩展的,是不连续的内存区域(这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的是动态分配的),因为会手动进行分
    配,会大一些,大小不固定。

    栈区和堆区效率差异

    栈区:由系统自动分配,速度较快。但程序员是无法控制的。(只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。)

    堆区:是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便。(首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的 delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中)

    1.5 指针数组及其应用

    数组指针、指针数组、字符串指针

    数组指针

    类型说明符 * 变量名;//定义
    

    例:

    	int num[] = {1,2,3};//数组不能先定义后初始化,定义后只能单个元素初始化	
    						//即int num[];num[] = {1,2,3};(错误)
    						//int num[];num[0]=3;(正确)
    	int *p;
    	p = num;
    

    指针数组

    类型说明符 * 数组名[数组长度];//定义
    

    类型说明符:表示指针所指向变量的数据类型

    • 数组名:表示该数组为指针数组

    例:

    char *str[3] = { "one","two","three" };
    printf("%s
    ", str[0]);
    

    输出结果为:

    one
    

    在C语言和C++等语言中,数组元素全为指针变量的数组称为指针数组,指针数组中的元素都必须具有相同的存储类型、指向相同数据类型的指针变量。

    指针数组比较适合用来指向若干个字符串,使字符串处理更加方便、灵活。

    也可以作为函数的参量使用,使用方式与普通数组类似。

    字符串指针

    类型说明符 * 变量名;//定义
    

    例:

    char *str1 = "helloworld";
    //上面代码等同于 char *str1;str1="helloworld";
    //所以字符串指针相当于字符类型的指针,只不过初始化和定义一步完成
    printf("%s
    ",str1+4);
    

    输出结果为

    oworld
    

    多个字符串用二维数组表示和用指针数组表示区别

    二维字符数组bai一旦定义,那么每个字du符串的zhi最大长度、首地址都不dao能改变了。

    字符指针数组,顾名思义,它是存放字符指针的数组。由于它仅用来存放指针,所以它指向的每个字符串的首地址可改变,字符串最大长度也可以改变。

    相比而言,字符指针数组更灵活一些。

    二维数组的一般定义和赋值方式为:

    char a[3][3];
    int i;
    for(i=0;i<3;i++)
    {
        gets(a[i]);
    }
    

    指针数组的一般定义形式和存储字符串的操作方法:

    char *a[3],s[3];
    int i;
    for(i=0;i<3;i++)
    {
        gets(s);
        a[i]=(char*)malloc(strlen(s)+1);
        strcpy(a[i],s);
    }
    

    1.6 二级指针

    定义

    如果一个指针指向的是另外一个指针,我们就称它为二级指针,或者指向指针的指针。

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

    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是这个“特殊的”一维数组的名称,也就是首地址,也就是第一个元素的地址,也就是第一行的首地址,是指首行一整行,并不是指某个具体元素。那么我们称之为“行指针”。

    #include <stdio.h>
    void 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;
    }
    

    列指针

    定义

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

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

    #include <stdio.h>
    void 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;
    }
    

    行指针和列指针

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

    那么两个概念之间的具体转换是:

    *行指针----列指针

    &列指针----行指针

    对于元素a[1][2],其地址用列指针表示为a[1]+2,等价表示为(a+1)+2,那么内容是(*(a+1)+2);

    2.PTA实验作业(7分)

    2.1 题目名1(2分)

    藏尾诗

    2.1.1 伪代码

    /*伪代码
    for i=0 to 4 then 
    {       
          get//输入数组
          strcpy//将str赋值到s[i]
    }
    for i=0 to 4 then
    {
         printf(s[i]+strlen(s[i])-2)//输出每句最后一个字
    }*/
    

    2.1.2 代码截图

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

    同学的:

    通过s[i][m-2]来确定每句诗最后一个字的位置存入w数组中(其中汉子占两个字节,w[2i]和w[2i+1]设置巧妙,易懂明了

    自己的:

    运用动态内存分配,通过用两个数组输出时较为简便

    2.2 题目名2(2分)

    选择合并2个有序数组这题介绍做法。

    2.2.1 伪代码

    /*伪代码
    while(i,j均大等于0)
    {
       if(a的最大值小于b的最大值)
            a[k--]=b[j--]//存入b
       else
            存入a
    }
    while(j大等于0)//b未存完
       存入b
    

    2.2.2 代码截图

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

    展示同学代码,介绍做法具体区别。


    同学的:

    通过定义一个新数组s,将a数组先存放进去,在通过分多钟情况比较a与b的大小进行重新排序,

    过程清晰易懂,但因为分多钟情况if句较多,效率较低。

    自己的:

    没有新定义数组,将总体分为两个情况:1、两个数组元素均大于等于0进入循环,比较a和b中最大值的大小,将更大的从后往前存入a中,2、当b中的元素未存放完,再写一个循环将b中剩余元素存入a中。

    从后往前遍历两个数组,不需要分类讨论那么多情况

    2.3 题目名3(3分)

    选择说反话-加强版这题介绍字符指针操作如何操作字符串。并说明和超星视频做法区别。

    2.3.1 伪代码

    len=strlen(str)
    for i= len-1 to 0 i--  then//逆向遍历
      if(str[i]!=' ')
         rear=i////指向单词的最后一个字母
         for j=i-1 to 0 j--  then
            if(str[j]==' ')  break
         front=j+1//指向第一个字母
         if() printf(空格)
         for k=front to rear k++  then
             printf
         i=j
    
    

    2.3.2 代码截图


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

    超星:

    分装了函数 通过用三个指针分别控制首、尾、逆向遍历指针,设计巧妙从后向前扫描到每个空格初再用printf("%.*s",长度len,地址p )输出,效率高且代码简洁。

    自己的:

    通过逆向遍历找到每个单词的首字母和末字母的位置,然后循环逐个逆向输出,没有用到指针,设置的变量较多,for循环中的代码繁琐

    也有尝试过用函数和指针做

    通过递归来实现反向输出但一直显示格式错误和运行时错误

  • 相关阅读:
    crontab自动备份MySQL数据库并删除5天前备份
    使用ShowDoc在线管理API接口文档
    概率计算(抽奖活动、命中率)
    保护隐私?找回已记住的秘密?你的余额宝、淘宝还安全吗?
    自制公众平台Web Api(微信)
    我为什么期待M#?
    在.net中为什么第一次执行会慢?
    记”Uri.IsWellFormedUriString”中的BUG
    公司ERP系统重构那些事
    Koala Framework是什么?我为什么要写这个框架?
  • 原文地址:https://www.cnblogs.com/qzmcflmf/p/14199025.html
Copyright © 2020-2023  润新知