《C语言学习:编程例题》C

写在前面:本笔记为个人学习各平台C语言系列课程所作,仅供交流学习,不得作他用。

1. 递归函数

这个例子比较简单,主要是熟悉递归函数写的形式。

cpp 复制代码
int sum( int n )
{
    // 基线出口:n≤0(非正整数)直接返回0
    if(n <= 0)
        return 0;
    // 递归公式:1+2+...+n = n + (1+2+...+(n-1))
    return n + sum(n - 1);
}

在return处会一直重复调用此函数,直到结果不满足条件。这就是递归。

2. 递归求二进制

cpp 复制代码
void dectobin( int n ){
    if(n==0){printf("0");}
    else if(n==1){printf("1");}
    else{
        dectobin(n/2);
        printf("%d",n%2);
    }
}

二进制有个程序化的求法,就是把数字不断/2,直到结果为0或者1,然后把所有余数倒着输出

这里注意三点:

(1)当n=0时,直接输出0然后结束程序,一种出口;

(2)当n=1时,直接输出1然后结束程序,一种出口;

(3)当n>=2时,需要先递归再输出,把n/2循环的最小值%2的余数先输出,最后输出n%2,实现倒着输出。

3. 指针赋值的区别

(1)定义一个指针char *p=NULL,有三种情况:

指向单个字符:*p='x';

指向字符串:p="xx";

指向空字符串:p="";,如果是等于NULL,打印时会把NULL打印出来,用这种方式就什么都打印不出来。

这两种情况不能混用!!!字符串本身就是地址,直接给地址p就可以。

(2)定义一个指针int *p,有四种情况:

int *p=0;,这不是让p指向0这个数字存放的地方,而是定义了一个空地址!!相当于没有初始化

int a;int *p=&a;*p=1;,这样写是正确的。必须先给指针一个真实变量所指的地址,再给指针(这个变量)赋值。

int a10; int *p=a;,这是让指针指向了这个数组(起始位置),数组本身是地址,不用&。这种写法等价于int *p=&a0;

int a10; int *p = &a1; *p = 100;,这是让指针指向了一个数组元素,需要&,同时用*p赋值。

(3)作为一个数组指针,比如char *s\[\],可以直接si调用元素。

4. 字符串连接

cpp 复制代码
char *str_cat( char *s, char *t ){
    int lens=strlen(s);int lent=strlen(t);
    for(int i=0;i<lent;i++){
        s[lens+i]=t[i];
    }
    s[lens+lent]='\0';
    return s;
}

不难,写好可以直接用。

5. 链表读入和建立

cpp 复制代码
struct ListNode *readlist() {
    struct ListNode *head = NULL, *tail = NULL;
    int x;

    while (scanf("%d", &x) == 1 && x != -1) {
        struct ListNode *p = (struct ListNode*)malloc(sizeof(struct ListNode));
        p->data = x;
        p->next = NULL;

        if (head == NULL) {
            head = tail = p;
        } else {
            tail->next = p;
            tail = p;
        }
    }
    return head;
}

struct ListNode *getodd(struct ListNode **L) {
    struct ListNode *oddHead = NULL, *oddTail = NULL;
    struct ListNode *evenHead = NULL, *evenTail = NULL;
    struct ListNode *p = *L;
    struct ListNode *next;

    while (p != NULL) {
        next = p->next; // 先保存下一个结点,防止断链

        if (p->data % 2 != 0) {
            // 奇数结点,加入新链表
            if (oddHead == NULL) {
                oddHead = oddTail = p;
            } else {
                oddTail->next = p;
                oddTail = p;
            }
            oddTail->next = NULL; // 尾结点置空,防止断链
        } else {
            // 偶数结点,留在原链表
            if (evenHead == NULL) {
                evenHead = evenTail = p;
            } else {
                evenTail->next = p;
                evenTail = p;
            }
            evenTail->next = NULL; // 尾结点置空,防止断链
        }

        p = next;
    }

    *L = evenHead;       // 修改原链表,使其只含偶数结点
    return oddHead;      // 返回奇数链表的头指针
}

**建立链表,**首先要准备结点结构(全局变量):

cpp 复制代码
struct ListNode {
    int data;
    ListNode *next;
};

第二步,设定两个结点变量,分别指向链表头head和尾tail。一般初始都设置为空(指向NULL)

第三步,开始读数。注意scanf()函数有返回值,==1时表示正常读入,可以用作判断条件。

第四步,在每一次读书循环里,都需要malloc给一个暂时指针p分配内存。写法参照:

cpp 复制代码
struct ListNode *p = (struct ListNode*)malloc(sizeof(struct ListNode));
//结构定义 *p=(结构定义 *)malloc(sizeof(结构定义));

把读入的值给p的值,NULL给p的NEXT。此时p是一个单独的结点,不在链表里,需要把它挂上。

第五步,判断当前头指针是否为空。

如果是空,说明此时的p是第一个结点,直接把p赋值给head和tail。

如果非空,说明此时链表里已经有结点,让tail.next=p(未更新的链表尾结点指向p,把p加入),再让tail=p(更新链表的尾结点为p)。

这里还让把一个单链表拆成两个链表,有几个注意点:

(1)传入函数的是**L,其中*L是链表,L是链表头结点地址。为了让自定义函数能够修改主函数里的变量,需要传入变量的地址,所以传入的是**L。

(2)在现有链表基础上拆分,不需要在每个循环里malloc给新结点p内存,直接定义p并且让p指向*L(头结点),然后再另外定义一个结点next。在循环里,先把p->next给next变量,结尾更新p=next,就能一个一个顺着处理结点。

(3)循环跳出条件是当前结点p为空。

(4)由于原先单链表里,每个结点(除尾结点外)的next都指向下一个结点,所以需要在建立新链表时让这些next置空,防止新链表最后一个结点指向原链表里的结点。

6. 变长数组

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>

int main(){
    int N;double a=0;double max;double min;
    scanf("%d",&N);
    int *p=(int*)malloc(N*sizeof(int));
    for(int i=0;i<=N-1;i++){
        scanf("%d",&p[i]);
    }
    max=p[0];min=p[0];
    for(int i=0;i<N;i++){
        a=a+p[i];
        if(p[i]>max){max=p[i];}
        if(p[i]<min){min=p[i];}
    }
    a=a/N;
    printf("average = %.2f\nmax = %.2f\nmin = %.2f",a,max,min);
    return 0;
}

这里可以不用数组方式,但通过申请动态分配内存本质上就是一个元素数为N而非数字的数组。