C语言博客作业05--指针

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

0.展示PTA总分

1.本章学习总结

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

指针定义
定义指针变量与定义普通变量非常类似,不过要在变量名前面加星号*,格式为:

datatype *name;

*表示这是一个指针变量,datatype表示该指针变量所指向的数据的类型 。例如:

int *p1;

在定义指针变量 p 的同时对它进行初始化,并将变量 a 的地址赋予它,此时 p 就指向了 a。值得注意的是,p 需要的一个地址,a 前面必须要加取地址符&,否则是不对的。

int a = 100;
int *p = &a;

*是一个特殊符号,表明一个变量是指针变量,定义 p1、p2 时必须带* 。而给 p1、p2 赋值时,因为已经知道了它是一个指针变量,就没必要多此一举再带上*,后边可以像使用普通变量一样来使用指针变量。也就是说,定义指针变量时必须带*,给指针变量赋值时不能带*

//定义普通变量
float a = 99.5, b = 10.6;
char c = '@', d = '#';
//定义指针变量
float *p1 = &a;
char *p2 = &c;
//修改指针变量的值
p1 = &b;
p2 = &d;

指针相关运算
*可以称为指针运算符,用来取得某个地址上的数据,请看下面的例子:

#include <stdio.h>
int main()
{
    int a = 15;
    int *p = &a;
    printf("%d, %d\n", a, *p);  //两种方式都可以输出a的值
    return 0;
}

运行结果:15 15

指针除了可以获取内存上的数据,也可以修改内存上的数据,例如:

#include <stdio.h>
int main()
{
    int a = 15, b = 99, c = 222;
    int *p = &a;  //定义指针变量
    *p = b;  //通过指针变量修改内存上的数据
    c = *p;  //通过指针变量获取内存上的数据
    printf("%d, %d, %d, %d\n", a, b, c, *p);
    return 0;
}

运行结果:99, 99, 99, 99

指针变量也可以出现在普通变量能出现的任何表达式中,例如:

#include <stdio.h>
int main()
{
      int x, y, *px = &x, *py = &y;
      y = *px + 5;  //表示把x的内容加5并赋给y,*px+5相当于(*px)+5
      y = ++*px;  //px的内容加上1之后赋给y,++*px相当于++(*px)
      y = *px++;  //相当于y=*(px++)
      py = px;  //把一个指针的值赋给另一个指针
    return 0;
}

指针做函数参数
用指针变量作参数交换两个变量的值:

#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;
}

调用 swap() 函数时,将变量 a、b 的地址分别赋值给 p1、p2,这样 p1、p2 代表的就是变量 a、b 本身,交换 p1、p2 的值也就是交换 a、b 的值。函数运行结束后虽然会将 p1、p2 销毁,但它对外部 a、b 造成的影响是“持久化”的,不会随着函数的结束而“恢复原样”。

1.2 字符指针

指针如何指向字符串
使用指针的方式来输出字符串(字符数组):

#include <stdio.h>
#include <string.h>
int main() 
{
    char str[] = "Hello World";
    char* pstr = str;
    int len = strlen(str), i;
    
    for (i = 0; i < len; i++) //使用*(pstr+i)
    {
        printf("%c", *(pstr + i));
    }
    printf("\n");
    
    for (i = 0; i < len; i++) //使用pstr[i]
    {
        printf("%c", pstr[i]);
    }
    printf("\n");
    
    for (i = 0; i < len; i++) //使用*(str+i)
    {
        printf("%c", *(str + i));
    }
    printf("\n");
    return 0;
}

运行结果:
Hello World
Hello World
Hello World

直接使用一个指针指向字符串:

char *str = "Hello World";
或者
char *str;
str = "Hello World";
  • 输出这种字符串的方式和输出字符数组的方式基本类似,但两者存在区别:
    它们最根本的区别是在内存中的存储区域不一样,字符数组存储在全局数据区或栈区,第二种形式的字符串存储在常量区。全局数据区和栈区的字符串(也包括其他数据)有读取和写入的权限,而常量区的字符串(也包括其他数据)只有读取权限,没有写入权限。
    内存权限的不同导致的一个明显结果就是,字符数组在定义后可以读取和修改每个字符,而对于第二种形式的字符串,一旦被定义后就只能读取不能修改,任何对它的赋值都是错误的。

字符串相关函数

  • 字符串求长度:strlen(str)
    例:len=strlen(str);
  • 字符串比较函数:strcmp(str1,str2)
    strcmp函数实际上是对字符的ASCII码进行比较 , 其中str1和str2可以是字符串常量或者字符串变量,返回值为整形,所以区分大小写。
    ① str1<str2,返回负值或者-1; ② str1=str2,返回0; ③ str1>str2,返回正值或者1;
  • 字符串连接函数:strcat(str1,str2)
    将字符串str2连接在str1后,并且str1最后的结束字符NULL会被覆盖掉,并且连接后的字符串的尾部会再增加一个NULL.注意:str1和str2所指的内存空间不能重叠,且str1要有足够的空间来容纳要复制的字符串。返回石str1字符串的首地址。
  • 字符串复制函数:strcpy(str1,str2)和strncpy(str1,str2,n)
    将str2所指的(n个)字符串复制到str1所指的字符串中。注意:src1和str2所指内存区域不可以重叠且str1必须有足够的空间来容纳str2的字符串。

拓展

  • 字符判断函数
int isalpha(char ch)  若ch是字母('A'-'Z','a'-'z')返回非0值,(返回1024)否则返回0

int isalnum(char ch)  若ch是字母('A'-'Z','a'-'z')或数字('0'-'9'),返回非0值,否则返回0

int isascii(char ch)  若ch是字符(ASCII码中的0-127)返回非0值,否则返回0

int iscntrl(char ch)  若ch是作废字符(0x7F)或普通控制字符(0x00-0x1F),返回非0值,否则返回0

int isdigit(char ch)  若ch是数字('0'-'9')返回非0值,否则返回0

int isgraph(char ch)  若ch是可打印字符(不含空格)(0x21-0x7E)返回非0值,否则返回0

int islower(char ch)  若ch是小写字母('a'-'z')返回非0值,否则返回0

int isupper(char ch)  若ch是大写字母('A'-'Z')返回非0值,否则返回0

int isprint(char ch)  若ch是可打印字符(含空格)(0x20-0x7E)返回非0值,否则返回0

int ispunct(char ch)  若ch是标点字符(0x00-0x1F)返回非0值,否则返回0

int isspace(char ch)  若ch是空格(' '),水平制表符('\t'),回车符('\r'),走纸换行('\f'),垂直制表符('\v'),换行符('\n')  返回非0值,否则返回0

int isxdigit(char ch) 若ch是16进制数('0'-'9','A'-'F','a'-'f')返回非0值,  否则返回0

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\n", str);
    return 0;
}

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

1.4 动态内存分配

为什么要动态内存分配
内存不是取之不尽用之不竭,4g、8g、16g是常见的电脑内存大小,打开任务管理器,能看到不同的应用占据的内存情况。如果一个应用程序占了大部分内存,估计别的应用就资源紧张了,那这个应用可能会被卸载,找个节省内存的。

精简的应用能更有效地使用内存,而不是埋头搞业务逻辑,最后却整出来非常耗费资源的应用来。

在资源使用很小,代码量很小的时候,很少会涉及到内存泄漏的问题,也就不涉及内存管理的事情,尤其是当前C语言教学陈旧的教材,里面陈旧的习题,和内存管理几乎不沾边,学过的人不会意识到内存管理有什么用。

堆区和栈区区别
栈区:存放函数的参数值、局部变量等,由编译器自动分配和释放,通常在函数执行完后就释放了,其操作方式类似于数据结构中的栈。栈内存分配运算内置于CPU的指令集,效率很高,但是分配的内存量有限,比如iOS中栈区的大小是2M。

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

动态内存分配相关函数及用法

  • malloc和free函数
    (1)void *malloc(size_t size);
    malloc的参数就是需要分配的内存字节数。malloc分配一块连续的内存。如果操作系统无法向malloc提供更多的内存,malloc就返回一个NULL指针。

(2)void free(void *pointer);
free的参数要么是NULL,要么是一个先前从malloc、calloc或realloc返回的值。

  • calloc和realloc函数
    (1)void *calloc(size_t num_elements,size_t element_size);
    calloc也用于内存分配。malloc和calloc之间的主要区别是后者在返回指向内存的指针之前把它初始化为0。

(2)realloc(void *ptr,size_t new_size);
realloc函数用于修改一个原先已经分配的内存块的大小。如果它用于扩大一个内存,那么这块内存原先的内容依然保留,新增加的内存添加到原先内存块的后面。如果它用于缩小一个内存块,该内存块尾部的部分内存被拿掉,剩余部分内存的原先内容依然保留。

(1)malloc(size) 在内存的动态存储区中分配一个长度为size的连续空间。
(2)calloc(n,size) 在内存的动态存储区中分配n个长度为size的连续空间。
(3)free(p) 释放指针变量p做指向的动态空间。
(4)realloc(p,size) 将指针变量p指向的动态空间大小改变为size。

使用案例:

#include<stdio.h>
#include<stdlib.h>
 
int main()
{
  void check(int *);
  int *p1, i;
  p1 = (int *)malloc(5*sizeof(int));
  for ( i = 0; i < 5; i++)
   scanf("%d",p1+i);
  check(p1);
  getchar();
  getchar();
  return 0;
}
void check(int *p)
{
  int i;
  for (i = 0; i < 5; i++)
  if (p[i] < 60) printf("%d", p[i]);
  printf("\n");
}

程序没有定义数组,而是开辟了一段动态自由分配区,输入数字时,按照地址复制给动态数组的5个元素,p1指向第一个整型数据,调用check函数时,p1作为实参传递给形参p,因此可以理解为形参p和实参p1公享一段动态分配区。

1.5 指针数组及其应用

如果一个数组中的所有元素保存的都是指针,那么我们就称它为指针数组。指针数组的定义形式一般为:

dataType *str[len];
可以理解为
dataType *(str[len]);
括号里面说明str是一个数组,包含了len个元素,括号外面说明每个元素的类型为dataType *

除了每个元素的数据类型不同,指针数组和普通数组在其他方面都是一样的,例子:

#include <stdio.h>
int main() 
{
    int a = 16, b = 932, c = 100;
    int* arr[3] = { &a, &b, &c };//定义一个指针数组,也可以不指定长度,直接写作 int *arr[]
    int** parr = arr;//定义一个指向指针数组的指针
    printf("%d, %d, %d\n", *arr[0], *arr[1], *arr[2]);
    printf("%d, %d, %d\n", **(parr + 0), **(parr + 1), **(parr + 2));
    return 0;
}

运行结果:
16, 932, 100
16, 932, 100

1.6 二级指针

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

转换为C语言代码:

int a =100;
int *p1 = &a;
int **p2 = &p1;

指针变量也是一种变量,也会占用存储空间,也可以使用&获取它的地址。C语言不限制指针的级数,每增加一级指针,在定义指针变量时就得增加一个星号*。p1 是一级指针,指向普通类型的数据,定义时有一个*;p2 是二级指针,指向一级指针 p1,定义时有两个*

1.7 行指针、列指针

行指针

行指针是指向数组的指针,即上面几种指针类型中的 int (*a)[5];所以,当二维数组要被当做参数进行传递时,可以这样声明:

void fun(int p[][5]);

列指针

用列指针输出二维数组:

#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.PTA实验作业

2.1 计算最长的字符串长度

2.1.1 伪代码

int max_len(char* s[], int n)
{
int count0 = 0, count1 = 0;    //设置count1为计数器,count0为计数存档
char* p;                  //设置指针p用于扫描
for (int i = 0; i < n; i++)//进行循环
      {
            让p与指针s[i]指向同一空间
            while (*p != NULL)//开始扫描这一行字符
                  {
                        每扫描一个字符计数器count1加一
                        同时p向前移动一位
                  }
           扫描完第一个指针指向的字符串,将字符数存入count0
            if
                  {
                        当后面扫描到更长的字符串,替换count0的值
                  {
             count1 = 0; //每一轮扫描结束,指向下一个字符串时count1从0开始计数
      }
 return count0; //最后返回count0,就是已扫描到的最长的字符串的个数
}

2.1.2 代码截图

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

2.2 合并两个有序数组

2.2.1 伪代码

void merge(int* a, int m, int* b, int n)
{
 int i = m-1, j = n-1, k = m+n-1;//定义三个变量i,j,k,分别指向a,b,和混合数组的末尾
 while (i >= 0 && j >= 0) 
{
      if a[i]>b[j]        说明要把a[i]先加入混合数组的末尾,加入后i和k都要自减1
      else 即a[i]<b[j]    就把b[j]加入混合数组的末尾,之后j和k也要自减1
}
 while(j>=0)   //循环结束后,有可能i和j还大于等于0,若j大于等于0,那么需要继续循环
{
      将b中的数字继续拷入a
}
//若是i大于等于0,那么就不用管,因为混合数组本身就a中
}

2.2.2 代码截图

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

同学代码

同学代码把数组数组放入新数组中,并找到最大值,然后再进行输出
我的代码把数组从后往前记录,依次放入对应最大值

2.3 说反话-加强版

2.3.1 伪代码

 char string[500001];
 char* p;//定义字符串数组和指针
 p = gets(string);    //将字符串的首地址赋给p
 p = p + strlen(string) - 1;    //将p移动到最后一位
while (*p == ' ')//去除结尾的空格
while (p > string)//由后往前输出
{
       if (*(p - 1) == ' ')  遇到空格输出p
      {
             while (*(p - 1) == ' ')  删除多个空格
             if (p != string)  输出空格
      }
 p--
}

打印第一个单词

2.3.2 代码截图


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

借鉴了超星视频的做法,用我的理解做了一遍,省略了len,但调试了很多次,代码阅读上比较复杂,超星视频对代码的解读更详细清楚
附上超星视频伪代码:

posted @ 2020-12-27 21:32  山无垢山城  阅读(172)  评论(0编辑  收藏  举报