求长方体的长宽高求它的体积以及三个面的面积。

分析:体积V=lengthwidthheight,三个面的面积分别为s1=lengthwidth、s2=widthheight、s3=length*height
C语言中的函数只能有一个返回值,我们只能将其中的一份数据,也就是体积 v 放到返回值中,而将面积 s1、s2、s3 设置为全局变量。

#include <stdio.h>

int s1,s2,s3;//定义三个全局变量用来接收面积
int vs(int a,int b,int c);//声明一下函数

int main(int argc, const char * argv[]) {
    int v,length,width,height;
    printf("Input length, width and height: ");
    scanf("%d,%d,%d",&length,&width,&height);
    v = vs(length,width,height);
    printf("v=%d,s1=%d,s2=%d,s3=%d\n",v,s1,s2,s3);
    return 0;
}

int vs(int a,int b,int c){
    int v;
    v = a*b*c;//体积
    s1 = a*b;
    s2 = a*c;
    s3 = b*c;
    return v;
}

求两个整数的最大公约数。

分析:最大公因数,也称最大公约数、最大公因子,指两个或多个整数共有约数中最大的一个。例如:求24和60的最大公约数,先分解质因数,得24=2×2×2×3,60=2×2×3×5,24与60的全部公有的质因数是2、2、3,它们的积是2×2×3=12,所以,(24,60)=12。

#include <stdio.h>

int gcd(int a,int b);

int main(int argc,const char * argv[]){
    int a,b;
    scanf("%d,%d",&a,&b);
    printf("GCD: A=>%d, B=>%d (A,B)=%d\n",a,b,gcd(a,b));
    return 0;
}
int gcd(int a,int b){
    /*
     * 最大公约数的递归:
     * 1、若a可以整除b,则最大公约数是b
     * 2、如果1不成立,最大公约数便是b与a%b的最大公约数
     * 示例:求(140,21)
     * 140%21 = 14
     * 21%14 = 7
     * 14%7 = 0
     * 返回7
     * 示例:求(21,140)
     * 21%140 = 21
     * 140%21 = 14
     * ...
     * 返回7
     * */
    if(a%b==0){
        return b;
    }else{
        return gcd(b, a%b);
    }
}

查看给定的字符是否位于某个字符串中。

分析:遍历给的字符串中有字符和输入的字符相等

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

int strchar(char *str, char c);
int main(){
    char url[] = "zhangkai";
    char letter = 'a';
    if(strchar(url, letter) >= 0){
        printf("The letter is in the string.\n");
    }else{
        printf("The letter is not in the string.\n");
    }
    return 0;
}
int strchar(char *str, char c){
    for(int i=0,len = strlen(str); i<len; i++){  //i和len都是块级变量
        if(str[i] == c){
            return i;
        }
    }
    return -1;
}

字符串反转(逆置)

分析:

  • 非递归:遍历字符串,交换前后两个相应位置的字符;
  • 递归:将第一个字符保存在tmp中,将最后一个字符赋给第一个字符,递归调用。每次调用函数,都会把字符串的第 0 个字符保存到 ctemp 变量,并把最后一个字符填充到第 0 个字符的位置,同时用'0'来填充最后一个字符的位置。
#include <stdio.h>
#include <string.h>
char *reverse(char *str);
int main(int argc,const char * argv[]){
    char str[]="abcdefg";
    printf("%s\n",reverse(str));
    return 0;
}
char *reverse(char *str) {
    int len = strlen(str);//计算字符串长度,用来查找最后一个字符
    if (len > 1) {
        char ctemp = str[0];//将第一个字符保存临时变量里
        str[0] = str[len - 1];//将最后一个字符赋值给第一个字符
        str[len - 1] = '\0'; //交换后指针指向下一个字符,最后一个字符赋为’\0’
        reverse(str + 1);  //递归调用
        str[len - 1] = ctemp;//将保存的tmp值赋给左后一个字符
    }
    return str;
}

求菲波那契数

分析:菲波那契数就是一个数列,数列中每个数的值就是它前面两个数的和,这种关系常常用以下形式进行描述:
F0=0,F1=1,Fn=Fn-1+Fn-2(n>=2,n∈N*)。

#include <stdio.h>
//递归计算斐波那契数
//双层递归的调用关系和数据结构中二叉树的结构完全吻合,所以双层递归常用于二叉树的遍历。
long fib(int n) {
    if (n <= 2) {
        return 1;
    }
    else {
        return fib(n - 1) + fib(n - 2);
    }
}
int main() {
    int a;
    printf("Input a number: ");
    scanf("%d", &a);
    printf("Fib(%d) = %ld\n", a, fib(a));
    return 0;
}

C语言源文件要经过编译、链接才能生成可执行程序:

  1. 编译(Compile)会将源文件(.c文件)转换为目标文件。对于 VC/VS,目标文件后缀为.obj;对于GCC,目标文件后缀为.o。
  2. 链接(Link)是针对多个文件的,它会将编译生成的多个目标文件以及系统中的库、组件等合并成一个可执行程序。

预处理主要是处理以#开头的命令

#include <stdio.h>

//不同的平台下引入不同的头文件
#if _WIN32 //Widows平台
#include <windows.h>
#elif __linux__  //识别linux平台
#include <unistd.h>
#endif

int main(int argc, const char * argv[]) {
    //不同的平台下调用不同的函数
    #if _WIN32 //widows
    Sleep(5000); //Windows 平台下的暂停函数的原型是void Sleep(DWORD dwMilliseconds)(注意 S 是大写的),参数的单位是“毫秒”
    #elif __linux__ //linux
    sleep(5); //Linux 平台下暂停函数的原型是unsigned int sleep (unsigned int seconds),参数的单位是“秒”,
    #endif
    return 0;
}

include用法

#include 叫做文件包含命令,用来引入对应的头文件(.h文件)。用法有两种:
1.#include <stdHeader.h>
2.#include "myHeader.h"

使用尖括号< >和双引号" "的区别在于头文件的搜索路径不同:

  • 使用尖括号< >,编译器会到系统路径下查找头文件;
  • 而使用双引号" ",编译器首先在当前目录下查找头文件,如果没有找到,再到系统路径下查找。
  • 一般习惯是使用尖括号来引入标准头文件,使用双引号来引入自定义头文件(自己编写的头文件),这样一眼就能看出头文件的区别。

define

宏定义命令,就是用一个标识符来表示一个字符串,如果在后面的代码中出现了该标识符,那么就全部替换成指定的字符串。

#define  宏名  字符串
  • 宏定义是用宏名来表示一个字符串,在宏展开时又以该字符串取代宏名,这只是一种简单粗暴的替换。字符串中可以含任何字符,它可以是常数、表达式、if 语句、函数等,预处理程序对它不作任何检查,如有错误,只能在编译已被宏展开后的源程序时发现。
  • 宏定义不是说明或语句,在行末不必加分号,如加上分号则连分号也一起替换。
  • 宏定义必须写在函数之外,其作用域为宏定义命令起到源程序结束。如要终止其作用域可使用#undef命令。
  • 代码中的宏名如果被引号包围,那么预处理程序不对其作宏代替
  • 宏定义允许嵌套,在宏定义的字符串中可以使用已经定义的宏名,在宏展开时由预处理程序层层代换。
  • 习惯上宏名用大写字母表示,以便于与变量区别。但也允许用小写字母。
  • 可用宏定义表示数据类型,使书写方便

带参数的宏

#define 宏名(形参列表) 字符串
例:
#define M(y) y*y+3*y  //宏定义
k=M(5);  //宏调用
//在宏展开时,用实参 5 去代替形参 y,经预处理程序展开后的语句为k=5*5+3*5。
  • 带参宏定义中,形参之间可以出现空格,但是宏名和形参列表之间不能有空格出现。
  • 在带参宏定义中,不会为形式参数分配内存,因此不必指明数据类型。而在宏调用中,实参包含了具体的数据,要用它们去替换形参,因此实参必须要指明数据类型。这一点和函数是不同的:在函数中,形参和实参是两个不同的变量,都有自己的作用域,调用时要把实参的值传递给形参;而在带参数的宏中,只是符号的替换,不存在值传递的问题。
  • 在宏定义中,字符串内的形参通常要用括号括起来以避免出错。
    注:带参数的宏和函数很相似,但有本质上的区别:宏展开仅仅是字符串的替换,不会对表达式进行计算;宏在编译之前就被处理掉了,它没有机会参与编译,也不会占用内存。而函数是一段可以重复使用的代码,会被编译,会给它分配内存,每次调用函数,就是执行这块内存中的代码。

##的用法

##称为连接符,用来将宏参数或其他的串连接起来。

#define CON1(a, b) a##e##b
#define CON2(a, b) a##b##00
printf("%f\n", CON1(8.5, 2));
printf("%d\n", CON2(12, 34));
//展开为
printf("%f\n", 8.5e2);
printf("%d\n", 123400);

常用宏

ANSI C 规定了以下几个预定义宏,它们在各个编译器下都可以使用:

  • __LINE__:表示当前源代码的行号;
  • __FILE__:表示当前源文件的名称;
  • __DATE__:表示当前的编译日期;
  • __TIME__:表示当前的编译时间;
  • __STDC__:当要求程序严格遵循ANSI C标准时该标识被赋值为1;
  • __cplusplus:当编写C++程序时该标识符被定义。

在程序占用的整个内存中,有一块内存区域叫做栈(Stack),它是专门用来给函数分配内存的,每次调用函数,都会将相关数据压入栈中,包括局部变量、局部数组、形参、寄存器、冗余数据等。
栈是针对线程来说的,每个线程都拥有一个栈,如果一个程序包含了多个线程,那么它就拥有多个栈。
对每个线程来说,栈能使用的内存是有限的,一般是 1M~8M,这在编译时就已经决定了,程序运行期间不能再改变。如果程序使用的栈内存超出最大值,就会发生栈溢出(Stack Overflow)错误。(当然,我们也可以通过参数来修改栈内存的大小。)
递归函数内部嵌套了对自身的调用,除非等到最内层的函数调用结束,否则外层的所有函数都不会调用结束。通俗地讲,外层函数被卡主了,它要等待所有的内层函数调用完成后,它自己才能调用完成。
每一层的递归调用都会在栈上分配一块内存,有多少层递归调用就分配多少块相似的内存,所有内存加起来的总和是相当恐怖的,很容易超过栈内存的大小限制,这个时候就会导致程序崩溃。
例如,一个递归函数需要递归 10000 次,每次需要 1KB 的内存,那么最终就需要 10MB 的内存。

形参(形式参数)

在函数定义中出现的参数可以看做是一个占位符,它没有数据,只能等到函数被调用时接收传递进来的数据,所以称为形式参数,简称形参。

实参(实际参数)

函数被调用时给出的参数包含了实实在在的数据,会被函数内部的代码使用,所以称为实际参数,简称实参。

形参和实参的区别和联系

  1. 形参变量只有在函数被调用时才会分配内存,调用结束后,立刻释放内存,所以形参变量只有在函数内部有效,不能在函数外部使用。
  2. 实参可以是常量、变量、表达式、函数等,无论实参是何种类型的数据,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参,所以应该提前用赋值、输入等办法使实参获得确定值。
  3. 实参和形参在数量上、类型上、顺序上必须严格一致,否则会发生“类型不匹配”的错误。当然,如果能够进行自动类型转换,或者进行了强制类型转换,那么实参类型也可以不同于形参类型。
  4. 函数调用中发生的数据传递是单向的,只能把实参的值传递给形参,而不能把形参的值反向地传递给实参;换句话说,一旦完成数据的传递,实参和形参就再也没有瓜葛了,所以,在函数调用过程中,形参的值发生改变并不会影响实参。

缓冲区(Buffer)又称为缓存(Cache),是内存空间的一部分。也就是说,计算机在内存中预留了一定的存储空间,用来暂时保存输入或输出的数据,这部分预留的空间就叫做缓冲区(缓存)。

scanf函数如下图:

scanf流程.png

注:
当控制字符串不是以格式控制符 %d、%c、%f 等开头时,空白符就不能忽略了,它会参与匹配过程,如果匹配失败,就意味着 scanf() 读取失败了。

scanf("%*[^\n]"); scanf("%*c");  //清空缓冲区

scanf() 完整写法:%{*} {width} type

  • type表示读取什么类型的数据,例如 %d、%s、%[a-z]、%1 等;type 必须有。
  • width表示最大读取宽度,可有可无。
  • *表示丢弃读取到的数据,可有可无。
  • int、char、float 等类型的变量用于 scanf() 时都要在前面添加&,而数组或者字符串用于 scanf() 时不用添加&,它们本身就会转换为地址。

  1. n