C语言博客作业05-指针

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

指针目录

0.展示PTA总分(0----2)

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

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

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

指针变量的定义
类型名 * 指针变量名
执政变量所指向的变量类型 指针声明符
int *ptr;
p是整型指针,指向整型变量
char *cPtr;
cp 是字符型指针,指向字符型变量

指针的基本运算
指针的值,是某个变量的地址。
int *p,a=3;
p=&a; & 取地址运算符
把a的地址赋给p,即p指向a,指针变量的类型为所指向的变量类型

  • 间接访问运算符,访问指针所指向的变量
    printf("%d",*p);输出p所指向的变量a的变量的值

指针取地址运算和间接访问运算
p=&a; 指针的赋值
p=10; 指针的内容的访问运算
p直接对内存单元操作,改变变量数据;
tips:
1)
&表示取地址,
表示取内容
2)
&
p与&a相同,是地址,&a与a相同,是变量
3)
p)++ 等价于a++ 将p所指向的变量值加1
p++?等价于(p++),先取*p,然后再自加,此时p不再指向a
指针变量的初始化
1)
指针变量先定义,赋值必须是地址
int a;
int *p1;
p1=&a;
2)
在定义指针变量时,可以同时对它赋初值
int a;
int *p1=&a; *p1=&a是错误的!!
int *p2=p1;

3)
不能用数值作为指针变量的初值,但可以将一个指针变量初始化为一个空指针
int p=10000是错误的!!!
p=0;
p=NULL;
p=(int
)1732; (int*)是将数字强转为指针类型

指针的赋值运算
int a=3,p1,p2;
printf("%d\n",*p1);?
p1=&a;
p2=p1;
相同类型的指针才能互相赋值,没有指向的指针是危险的,会出现所谓的段错误!

指针作为函数参数
形参:指针变量 int *p
实参:&a,某个指针
eg:指针作为函数参数模拟角色互换

#include<stdio.h>
void swap(int *px,int *py);
int main(void)
{
	int a = 1, b = 2;
	int* pa = &a, * pb = &b;

	swap(pa, pb);
	printf("%d,%d",a,b);


	return 0;
}
void swap(int* px, int* py)
{
	int t;
	t = *px;
	*px = *py;
	*py = t;
}

要通过函数调用来改变主函数中某一个变量的值:
1)主调函数中,将该变量的地址或者指向该变量的指针作为实参(实参是数组名)
2)被调函数中,用指针类型形参接受该变量的地址(形参是指针变量,可以写成数组形式)
3)在被调函数中,改变形参所指向变量的值
3)数值型数组传数组地址,数组长度

传地址的优点?
数据量少,直接对地址操作,效果更好,可以改变多个变量的值,比return返回更实用

#include<stdio.h>
void monthDay(int year, int yearday, int* monthPtr, int* dayPtr);
int main(void)//年,天数,月份指针,天数指针
{
	int day=0, month=0, year=0, yearday=0;//初始化日期为零
	scanf("%d %d",&year,&yearday);//输入年和天数
	monthDay(year, yearday, &month, &day);//转换为月份日期的函数调用,返回为地址
	printf("%d-%d-%d",year,month,day);

	return 0;
}
void monthDay(int year, int yearday, int* monthPtr, int* dayPtr)
{
	int k, leap;//k为具体天数
	int tab[2][13] =//初始化二维数组
	{
		{0,31,28,31,30,31,30,31,31,30,31,30,31},{0,31,29,31,30,31,30,31,31,30,31,30,31},
	};
	/*建立闰年判别条件*/
	leap = (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
	for (k = 1; yearday > tab[leap][k]; k++)
	{
		yearday = yearday - tab[leap][k];
		*monthPtr = k;
		*dayPtr = yearday;//逐渐减去每一个月的时间
	}



}

数组和地址间的关系
int a[100];
数组名代表一个地址,他的值是数组首元素的地址,(基地址)a+i是数组a的及地址的第i个偏移量
指针+1,实际下移一个数据类型存储单元

注意:
移动的写法,a++!=*a++

注意运算符号的优先级

指针和数组的关系
任何由数组下标来实现的操作都能用指针来完成
例如:

&a[i] == a+i;
p+i == &p[i];
a[i] == *(a+i);
*(p+i) == p[i];

移动指针:p+1
地址加法:移动下一个数据单元
用指针完成对数组的操作

int a[100],*p;
p=a;
int sum=0;
for(p=a;p<a+n;p++)
{
sum=sum+*p;
}

指针做循环变量,务必了解初始和结束地址

遍历数组的方法:
下标法

for(int i=0;i<n;i++)
{
a[i];
}

指针法

for(p=a;p<a+n;p++)
{
*p;
}

使用指针计算数组元素个数和数组元素的存储单元数
p,q;
p=&a[0];
q=p+1;
printf("%d\n",q-p);//指针p和 q之间元素的个数
printf("%\n",(int)q-(int)p);//指针p和q之间的字节数,(int)q代表地址值

指针的算数运算和比较运算
double p,q;
q-p:两个相同类型的指针相减,表示他们之间的存储单元的数目
p+1/q-1:至此昂下一个存储单元,指向上一个存储单元
p<q:两个相同类型的指针关系可以用关系运算符比较大小

1.2 字符指针

包括指针如何指向字符串、字符串相关函数及函数代码原型的理解、字符串相关函数用法(扩展课堂未介绍内容)
字符数组和字符指针的重要区别

如果要改变数组所代表的字符串,只能改变数组元素的内容
如果要改变指针所代表的字符串,通常直接改变指针的值,让他直接指向新的字符串

char sa[]="this is a string";
const charsp="this is a string";
char
sp="this is a string";错误❌
sp="hello";错误❌
sa="hello";错误❌
数组名是常量,不能对它赋值
sp是指针,只能指向,不能直接赋值

**关于const **
const是constant的缩写,意思是恒定不变的!
const定义的是变量,但又相当于常量
不允许给它重新赋值,即使是相同的值也不可以
只读变量,必须在定义的时候就给他赋初值
const char*sp="this is a string";错误❌
sp='a';
char sa[] ="this is a string";正确✔
char
sp=sa;

字符串的输出
char sa[]="array";
const char*sp="point";
printf("%s",sa); array 数组
printf("%s",sp); point 指针
printf("%s","string"); string字符串
printf("%s",sa+2); ray 对数组进行操作
printf("%s",sp+3); int对指针进行操作
printf("%s","string"+1); tring 对字符串进行操作

即 数组名sa,指针sp和字符串string 的值都是地址
printf("",地址);
//%s:输出从指定地址开始,‘\0’结束的字符串

字符指针,先赋值,后引用
定义字符指针后,没有对他赋值,指针的值不确定,
chars;
scanf("%s",s);错误❌ 不要引用未赋值的指针
char
s ,str[20];正确 ✔
s=str;
scanf("%s",s);
定义指针是,现将他的初值置为空
char*s=NULL;

1)指针如何指向字符串

C语言中没有特定的字符串类型,我们通常是将字符串放在一个字符数组中

#include <stdio.h>
#include <string.h>

int main(){
    char str[] = "http://c.china.net";
    int len = strlen(str), i;
    //直接输出字符串
    printf("%s\n", str);//使用%s
    //每次输出一个字符
    for(i=0; i<len; i++){
        printf("%c", str[i]);//使用%c
    }
    return 0;
}

字符数组当然是数组,利用指针对字符数组进行操作。

#include <stdio.h>
#include <string.h>

int main(){
    char str[] = "http://c.biancheng.net";
    char *pstr = str;
    int len = strlen(str), i;

    //使用*(pstr+i)
    for(i=0; i<len; i++){
        printf("%c", *(pstr+i));
    }
    printf("\n");
    //使用pstr[i]
    for(i=0; i<len; i++){
        printf("%c", pstr[i]);
    }
    printf("\n");
    //使用*(str+i)
    for(i=0; i<len; i++){
        printf("%c", *(str+i));
    }
    printf("\n");

    return 0;
}
#include <stdio.h>
 int main(){
     char *str = "Hello World!";
     str = "I love C!";  //正确
     str[3] = 'P';  //错误
 
     return 0;
 }

这段代码能够正常编译和链接,但是在运行时会出现段错误(Segment Fault)或者写入错误。

第四行代码是正确的,可以更改指针变量本身的指向;第5行代码是错误的,不能修改字符串中的字符

课外拓展
** 字符数组和使用一个指针指向字符串是非常相似。区别是什么?

  字符数组和指针字符串的区别是内存中的存储区域不一样,字符数组存储在全局数据区或栈区,指针字符串存储在常量区。

全局区和栈区的字符串(包括其它数据)有读取和写入的权限,而常量区的字符串只有读取权限,没有写入权限。

  内存权限不同导致一个明显的结果就是,字符数组在定义后可以读取和修改每个字符,而对于指针字符串来说,一旦被定义

后就只能读取而不能修改,任何对它的赋值都是错误的。

2)字符串相关函数及函数代码原型的理解
1.字符串的输入输出
输入: scanf()/可以接受空格/或者 fgets()/不能接受空格并且以'\0'结束/
输出:printf()或者 puts()
stdio.h
2.字符串的复制,连接,比较,求字符串长度
1、求长度

函数:strlen()

格式:strlen(字符串/字符串变量)
函数原型:

//求字符串长度函数strlen
#include <stdio.h>
int str_strlen(char *Str)
{
 int l=0;
 while(*Str!='\0')
 {
  *Str++;
  l++;
 }
 return l;
}

注意:计算字符串的有效长度,不包括结束标志'\0';

2、复制

函数:strcpy()

strcpy(字符串1(目标),字符串2(源))--将字符串2的内容复制给字符串1

注意:字符数组1的长度要能容纳字符串1+2的内容,

函数原型:

拷贝函数strcpy
#include <stdio.h>
char *str_strcpy(char *strDest,char *strSour)
{

 while(*strSour!='\0')
 {
  *strDest=*strSour;
  *strDest++;
  *strSour++;
 }
 *strDest='\0';//直到最后一个给予结束标志符
 return strDest;
}

注意:使用strDest,strSrc这样增强可读性的名字
对于传入参数的strDest,strSrc
进行检查,禁止空指针传入
使用const 来约束strSrc,提高程序的健壮性,如果函数体内的语
句试图改动strSrc的内容。编译器将指出错误


3、比较

函数:strcmp()

格式:strcmp(字符串1,字符串2)

若字符串1与字符串2相等返回--0 字符串1大于2 返回--1 小于返回--- -1
注意:strcmp函数实际上是对字符的ASCII码进行比较 , 其中str1和str2可以是字符串常量或者字符串变量,返回值为整形,所以区分大小写

函数原型:

//字符串比较函数strcmp,。
#include <stdio.h>
int str_strcmp(char *str1,char *str2)
{
 while((*str1==*str2)&&(*str1!='\0'))
 {
  *str1++;
  *str2++;
 }
 if(*str1=='\0'&&*str2=='\0')//结束位置相同,说明长度一样长,否则不一样长
  return 1;
 else
  return -1;
}

4、合并

函数:strcat()

格式:strcat(字符串1,字符串2)--将字符串2的内容合并到字符串1

注意:字符数组1的长度要能容纳1+2的内容,且 str1=str1+str2是非法的·!❌

函数原型:

char *str_strcat(char *strDest,const char *strSour)
{
 while(*strDest!='\0')//先将指针指向结尾的位置
  strDest++;//移动到字符串末尾
 while(*strSour!='\0')
 {
  *strDest=*strSour;//从字符串2的起始位置开始赋值到字符串1的结尾位置,再分别移动
  strDest++;
  strSour++;
 }
 *strDest='\0';//给予结束标志
 return strDest;
}

或
char*strcat(char*str1,const char*str2)
{
char *tempstr;
tempstr=str1;
if(!str1||!str2)//防止空指针传入,否则程序崩溃
{
return NULL;
}
while(*str1)
{
str1++;
}
while(*str2)
{
*str1=*str2;
str1++,str2++;
}
*str1='\0';
return tempstr;//连接后字符串的首地址
//函数类型为指针,则返回地址,首地址不要改变




注意:将字符串str2连接在str1后,str1最后的结束字符NULL会被覆盖掉,并且连接后的字符串的尾部
会再增加一个NULL.注意:str1和str2所指的内存空间不能重叠,且str1要有足够的空间来容纳要复制
的字符串。返回是str1字符串的首地址。

字符串复制和合并函数存在的问题?
源字符串要足够大,否则会溢出导致系统崩溃
while((strDest++=strSrc++)!='\0');
如何解决?
strncpy函数
charstrncpy(chardest,const char*stc ,size_t n)
把src所指向的字符串复制到dest,最多复制n个字符,当长度小于n时,剩余部分将用控字节填充
strncat()函数

 char*strncat(char*dest,const char*stc ,size_t n)

把src所指向的字符串追加到dest所指向的字符串的结尾,追加最多n个字符

字符串比较函数用来比较字符串的大小
if(str1>str2){}比较字符串首元素的地址 错误❌
if (strcmp(str1,str2)>0) 比较字符串内容 {}正确✔

3)字符串相关函数用法

1.3 指针做函数返回值

指针作为函数的返回值,函数的返回值类型需要定义为指针类型
一般定义格式为
数据类型*函数名称(形式参数列表)

注意事项:
一定要保证返回的指针是有效指针,一个常犯的错误是,返回局部变量的指针

1.4 动态内存分配

为什么要动态内存分配
堆区申请的空间,想要多少就申请多少
数组要指定数组长度,空间浪费
栈区空间有限
一般情况下,运行中的很多存储要求在写程序时无法确定,故需要动态内存分配
使用动态内存分配能有效使用内存
全局变量,静态局部变量,自动变量由编译器系统分配

课外拓展 堆区和栈区区别

一、区别
注:首先堆和栈可以分为两种,一种是数据结构,另一种是和内存的分配有关,这两种虽然都有栈和堆,但是两者关系并不大,

1、栈、堆是数据结构里面的叫法,注意:有时候有人喜欢这样说 "堆栈" 其实说的就是栈而不是堆。  

2、堆区、栈区则是内存模型的叫法。

二、内存中的栈区和堆区
我们知道底层是C
而C语言的内存模型分为5个区:栈区、堆区、静态区、常量区、代码区。每个区存储的内容如下:

1、栈区:存放函数的参数值、局部变量等,由编译器自动分配和释放,通常在函数执行完后就释放了,
其操作方式类似于数据结构中的栈。栈内存分配运算内置于CPU的指令集,效率很高,但是分配的内存量
有限,局部变量申请大小是有限制的,由栈的剩余空间决定,栈的大小为2m,也有的说是1m,总之,是一个
编译时就会确定的常数,如果申请的空间超过了剩余空间,将会出现问题。

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

3、静态区:全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始
化的全局变量和未初始化的静态变量在相邻的另一块区域。程序结束后,由系统释放。

4、常量区:常量存储在这里,不允许修改。eg:const

5、代码区:顾名思义,存放代码。

动态内存分配相关函数及用法
使用时申请:malloc,calloc
用完就释放:free

void*calloc (unsigned n,unsigned size)
在内存的动态存储区中分配连续n个连续空间,每一存储空间的长度为size,并且分配后还把存储块里全部初始化为0
若申请成功则返回地址,若不成功,则返回NULL;
void free (void *ptr)
释放有动态存储分配函数申请到的整块内存空间,ptr为指向要释放空间的首地址。
free(p);

void malloc(unsigned size)
在内存的动态存储区中分配连续的空间,其长度为size
eg:
p=(int )malloc(nsizeof(int))
若申请成功,则返回一个指向所分配内存空间的其实地址的指针,不成功则返回NULL;
malloc对所分配的存储区域不做任何事情
和free结合使用

举例为多个字符串做动态内存要如何分配

1.5 指针数组及其应用

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

1.6 二级指针

1.7 行指针、列指针

行指针:二级指针
1.形如 int(*p)[n]
指向有n个元素的一维数组

p+i=a+i;
(p+i)=(a+i)=a[i]
2.二维数组与指针

int a[3][4];
int (p)[4];
p=a;
(
p)[0]=a[0][0];
(*(p+1))[0]=a[1][0];
((p+i)+j)=a[i][j];
列指针:一级指针
int a[3][4];
int * p;
p=a;

  • (p+1)=a[0][1];
    //移向下一个元素
    注意区分行指针与列指针
    行指针:p首先指向第0行,然后p+i定位到第i行,然后p+i进行解引用(*(p+i))把行地址转化为列地址,在得到第i行第0列地址后在加j得到第i行第j列地址,在进行解引用得到a[i][j]
    列指针:p直接指向了第0行第0列,找出a[i][j]相对于a[0][0]的偏移量,i * n+j

2.PTA实验作业(7分)

2.1 7-3 字符串的冒泡排序 (2分)

我们已经知道了将N个整数按从小到大排序的冒泡排序法。本题要求将此方法用于字符串序列,并对任意给定的K(<N),输出扫描完第K遍后的中间结果序列。

输入格式:
输入在第1行中给出N和K(1≤K<N≤100),此后N行,每行包含一个长度不超过10的、仅由小写英文字母组成的非空字符串。

输出格式:
输出冒泡排序法扫描完第K遍后的中间结果序列,每行包含一个字符串。

2.1.1 伪代码

数据表达:
字符串存储:使用二维字符数组
输入二维数组:
外层循环for:扫描k遍
{
内层循环for:冒泡n-i-1次
{
if(后一个字符串的长度比前一个字符串的长度要长)
{
交换两个字符串的位置;
}
}end for;
}end for:

2.1.2 代码截图

2.1.3

//main.c
//author
//连续输入是个字符,以回车结束
#include "stdafx.h"
//输入10个数字
#define N 10
char min(char a, char b);
char max(char a, char b);
int main()
{
    //int a[N] = { 10,9,8,7,6,5,4,3,2,1 };
    int flag = N;
    //指针的方法
    char a[N] = { 0 };
    char *p = a;
    for (int i = 0; i < N; i++)
    {
        scanf_s("%c", p);
        p++;
    }
    p = &a[0];
    for (int i = 0; i < N; i++)
    {
        printf("输入的数为%c\n", *p++);
    }
    p = a;
    while (1)
    {
        for (int i = 0; i < flag - 1; i++)
        {
            char tmp1 = *(p + i);
            char tmp2 = *(p + i + 1);
            *(p + i) = min(tmp1, tmp2);
            *(p + i + 1) = max(tmp1, tmp2);
        }
        if (flag == 2)break;
        flag--;
    }
    p = a;
    for (int i = 0; i < N; i++)
    {
        printf("%c ", *(p + i));
    }

2.2 6-9 合并两个有序数组(2分)

选择合并2个有序数组这题介绍做法。
要求实现一个函数merge,将元素个数为m的升序数组a和长度为n的升序数组b合并到数组a,合并后的数组仍然按升序排列。假设数组a的长度足够大。

2.2.1 伪代码

合并两个有序数组有三种做法:
1:创建一个新数组c,把有序数组a和有序数组b中的元素放到新创建的数组c中,然后利用冒泡排序把数组c中的元素进行有序排序。
2:创建一个新的数组c,此数组的大小大于或等于已知两个数组之和。通过比较两个数组中的元素,谁小就把谁放到空数组中,知道其中一个数组为空,最后把剩下的数组全部放到新创建的始组中。
3:有两个有序数组a和b,其中数组a的末尾有足够的空间容纳数组b,将数组b容纳到数组a中
此题为第三种做法
考虑到a数组很大,可以直接在a数组上进行合并,但是要讲究效率。如果单纯从前往后合并,那么效率会非常低,因为a数组后面的数字需要不停的移动。换一种思路,采用从后往前合并,首先计算出总长度,设置一个指针从a数组最后往前移动

具体伪代码实现如下:

w计算合并后a数组的长度;
hile(b数组的长度递减大于零)
{
每一轮都要判断a的长度是否满足大于零并且递减的指针内容位置a和b谁比较大?a大就将a的该位置的值传入新数组:否则就是b;

}

void merge(int* a, int m, int* b, int n)
{/* 合并a和b到a */
int size = m-- + --n;
    while (n >= 0) //b数组的长度递减大于零
    {
        *(a+(size--)) = m >= 0 && *(a+m) >*(b+n) ? *(a+(m--)) :*(b+(n--));//每一轮都要判断a的长度
    }
}

2.2.2 代码截图

2.2.3


2.3 7-4 说反话-加强版(3分)

void merge(int* nums1, int nums1Size, int m, int* nums2, int nums2Size, int n) {
 int i;
 int j = 0;
 for (i = m; i < m + n; i++) {
  nums1[i] = nums2[j];
  j++;
  }
 for (i = 0; i < m + n - 1; i++) {
  for (j = 0; j < m + n - 1 - i; j++) {
   if (nums1[j] > nums1[j + 1]) {
    int tmp = nums1[j];
    nums1[j] = nums1[j + 1];
    nums1[j + 1] = tmp;
   }
  }
 }
}

本题做法使用数组下标或者使用指针都可,亮点是使用了三目运算符和自增自减运算符,以及使用数组重构,使代码更加简洁

给定一句英语,要求你编写程序,将句中所有单词的顺序颠倒输出。

输入格式:
测试输入包含一个测试用例,在一行内给出总长度不超过500 000的字符串。字符串由若干单词和若干空格组成,其中单词是由英文字母(大小写有区分)组成的字符串,单词之间用若干个空格分开。

输出格式:
每个测试用例的输出占一行,输出倒序后的句子,并且保证单词间只有1个空格。

2.3.1 伪代码

字符串存储(使用一维字符数组;
使用初始位置指针和结束位置指针来控制逆序;
while(没有到结束标志)
{
将结束位置指针指向字符数组末尾
}end while;

while(结束位置指针不等于初始位置指针)
{
逆向遍历字符数组;
利用指针所指向的内容是否为空格来判断单词;
输出单词,使用flag来控制输出格式;
}

2.3.2 代码截图

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

借鉴超星的做法,
超星优点:
定义指针指向字符串,更能动态了解当前字符位置
while(endPtr&&endPtr!='\n')
{
endPtr++;
}
逆向扫描字符串
while(p!=beginPtr)
{
p--;
}
怎么找字符串单词,即当前字符的前一个字符为空格
if(p!=' '&&(p-1)==' ')
{}

posted @ 2020-12-27 23:00  calizo  阅读(512)  评论(0编辑  收藏  举报