第六次博客

这个作业属于哪个班级 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循环中的代码繁琐

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

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

原文地址:https://www.cnblogs.com/qzmcflmf/p/14199025.html