欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

C_数据结构与算法(一):C语言基础 - Rudolph_Browne

程序员文章站 2022-05-01 17:51:48
c_数据结构与算法(一):c语言基础 致初学者的我:一切都是由浅入深。 每种语言都有每种语言的特性,基本的特性是相同的,下面依照惯例写hello world,相关编译后面再介绍。  ...

c_数据结构与算法(一):c语言基础

致初学者的我:一切都是由浅入深。

C_数据结构与算法(一):C语言基础 - Rudolph_Browne


每种语言都有每种语言的特性,基本的特性是相同的,下面依照惯例写hello world,相关编译后面再介绍。


 

// c语言用“//”和“/**/”来写注释,注释、空格等不影响语义(程序的意思)的符号会在程序编译阶段会被删除。
#include               //#include    预编译处理,这里把stdio的c函数库载入进程序;
main()                     //main()     主程序入口;
{
    printf("hello world!");         //printf()    stdio函数库中的函数,表示格式化输出;
    return 0;                 //return 0    返回状态0,表示程序正常结束。
}

  

part1 基本数据类型及表达式


 

变量可以视为一个无限大的黑箱,你可以往里面装你想装的东西,三个要素:
定义(或声明),根据数据类型在内存中申请空间,如int age;
赋值:修改变量的值,如 age = 30;
使用:将变量的值从内存中读出来进行使用,如 printf("%d",age),或运算 age = age + 1;
    int            age            =                23
  数据类型       变量名                       赋予变量的值

 

c基本数据类型(计算机中位指的是'0'或‘1’其中之一,而八位称为一个字节,sizeof()函数可以查看数据类型具有多少个字节):
  • 整数型
    short n = 10;   //内存中占16位
    int n = 10;     //占32位
    long n = 10;    //占64位
    

      

    浮点数
    float f = 1.1;  //占32位,有效位数6~7
    double d = 1.1; //占64位,有效位数15~16  
    

      

    字符型
    char c = 'a'; //占8位  
    

      

    进制转换,计算机惯用的进制为:二、八、十、十六
  • 理解进制(以十进制138为例,可以用程序员计算器来帮助日常换算)
    二进制:    138 = 128(即1*2^7)  +  8(1*2^3)  +  2(1*2^1)  ----->10001010
    八进制:    138 = 128(2*8^2)     +  1(1*8^1)  +  2(2*8^0) ----->212
    十进制:    138 = 100(1*10^10) +30(3*10^1)+  8(8*10^0)   ----->138
    十六进制: 138 =  128(8*16^1)  +  10(10*16^0)             ----->8a
    //十六进制中 10-15 分别以a-f来表示

     

    自动类型转换
  • (unsigned ) char、 short、int、long、float、double等类型之间可以互相赋值,会进行自动类型转换:
    short s = -1; 
    unsigned short us = s;  // us = 65535
    
    short s2 = 65;
    char c = s2;   //c = 'a'
    

      

    精度损失:如果把存储大的类型赋值给小的类型,会面临精度丢失的问题。

    变量相关信息 命名规则:变量的命名规则可以让程序更容易理解,个人采用camel-case命名法,如:personalinformation 作用域:指包括此变量声明的最小代码块,在这个作用域中,变量声明后的所有代码行都可以访问此变量。变量在其作用域内被创建,离开其作用域时被撤销。
    {
       int age = 10;
       ...
    
       ....以上这些行都可以访问age
    }
    这里不能访问age

     

    常量
  • 一般我们在代码中通过符号来代表常量,这个符号被称为符号常量,通过#define或const定义,例如:
    const double pi = 3.14; //定义圆周率的符号常量
    #define pi 3.14; //用宏定义的方式,分不出数据类型
    printf("pi=%f\n",pi);
    //注:尽量使用const来定义符号常量
    

      

    注意:尽量避免使用魔法数,指的是在程序中多处出现的常量值,应该使用上述方法将其赋值给一个恒定的变量。

    表达式
  • 表达式由操作符和操作数构成,通过操作符将一个或多个操作数连接到一起,如:
    id = 3 //赋值表达式, =是赋值操作符
    1+2   //计算表达式,+是算术运算符
    ++i   //计算表达式,++是算术计算符
    2>1   //逻辑表达式,>是关系运算符符
    (double)a  //类型转换表达式
    1 && 0  //逻辑表达式,&&是逻辑运算符
    ...

     

    自增自减运算符
  • ++、–表示对变量执行加1(减1)操作,同时参与表达式运算。例如
    int a = i++ + 3; //先让i参与表达式运算,然后将i+1
    int a = ++i + 3; //先将i+1,然后让i参与运算
    

      

    位运算符
  • &:位与运算,将二进制每一位进行与运算,都是1则为1,如
    3  &  5   = 1
    0011 & 0101 = 0001
    |:位或运算,将二进制每一位进行或运算,都是0则为0,如
    3  |  5   = 7
    0011 | 0101 = 0111
    ^:位异或运算,将二进制每一位进行异或运算,不相同为1,相同为0,如
    3   ^  5     = 6
    0011 ^  0101  = 0110
    ~:按位取反,如
    ~3  = -4
    <<:左位移,将左端挤掉,右端补零,如
    3<<1  = 6
    011  --> 0110
    >>:右位移,对有符号数,最左边补原符号数,其它左边位补0,右边被挤掉,如
    3>>1 = 1
    011 -->01
    赋值表达式
  • 左值与右值:对一个变量(或表达式)来说,有两个值与其关联:
    char[] fullname = "rudolph_browne";//fullname为左值,rudolph_browne为右值
    1)数据:变量的数据值,存储在地址中,这个值被称之为右值(rvalue),r是read(可读)的意思。
    2)地址:存储数据值的内存地址,地址被称之为左值(lvalue),l是location(可寻址)的意思。

     

    “每一个表达式要么是lvalue,要么是rvalue”。左值指表达式结束之后依然存在的持久对象,而右值指表达式结束之后就不复存在的(临时)对象。

    其他表达式
  • sizeof运算符:返回变量或类型的字节数,如
    sizeof(float);  //4
    sizeof(double); //8
    int n;
    sizeof(n); //4
    

      

    条件表达式: 条件?值1:值2,条件成立时取值1,否则取值2,如:
    int a = 1?5:10;   //a为5
    int b = (a%2==0)?1:0; //a为偶数时,b=1,否则为0
    

      

    逗号表达式:由多个表达式以逗号连接而成,如
    a=3, b=a+1, c=b+1; 
    x = (a=3, b=6*3); //逗号表达式的值为最后一个表达式的值
    (a=3,b) = 5;   //最后表达式为左值时,逗号表达式也可为左值

    part2 程序结构


     

    基础输入输出函数
  • printf函数概述
    printf("格式控制字符串"[,输出列表]);
    注意:输出列表与格式控制字符串之间有一个逗号分隔(如果输出列表存在的话)。

     

    格式字符串:%[标志][宽度][.精度][h | l]<格式字符>,例如
    printf("%05hd\n",10);  //h表示短类型,l表示长类型
    输出短整型数据10,宽度为5位,前面补0  //00010

     

    格式字符
    d(i):十进制整数,负数带-号
    u:无符号十进制整数
    o:无符号八进制整数,无前导字母o
    x(x):无符号十六进制整数,无前导0x,x表示大写
    f:小数形式的浮点数,默认6位小数
    e(e):指数形式的浮点数
    g(g):自动选择e和f中宽度较小者,不打印无效的0
    c:单个字符
    s:字符串

     

    putchar函数 putchar函数用来将单个字符输出到标准输出设备,通常是显示器
    putchar(c); //c可以是字符或整型数字
    

      

    scanf函数 scanf函数用于接收输入,以回车作为一次函数调用结束,格式为
    scanf("格式控制字符串",地址列表);
    scanf("%d",&a);  //&a表示变量a的地址
    

      

    注意:通常在用c库函数输出提示语句时都会适当地缓存,要输出缓存时使用下面语句。

    setbuf(stdout,null); //这句代码是为了先输出提示语句
    

     

    getchar函数 getchar函数从键盘输入一个字符,返回该字符的ascii码。例如
    char c = getchar();  //等待键盘输入一个字符给变量c
    

      


    线性结构
  • 线性结构的程序没有太多的特殊性,程序按定流水线的方式被执行。 选择结构
  • if-else if-else结构
    if(条件1) //如果条件为真
    {        //那么
             //当条件成立时执行这里的代码
    }else if(条件2)
    {      //或者
           //当条件2成立时执行这里的代码
    }else
    {       //否则
           //执行这里的代码
    }

     

    switch结构(注意是等值判断:必须用int或char的整数值);注意每个case后面有一个break,忽略后面其它case。
    switch(rank){
    case 1:
        //奖励iphone;
        break;
    case 2:
        //奖励ipad;
        break;
    case 3:
        //奖励ipod
        break;
    default:
        //无视
    }
    

      

    循环结构
  • while语句不断判断循环条件,当循环条件为真(非0)的时候,就执行循环操作,一直到循环条件变为假(0)才跳出。
    while(循环条件){
      //循环操作
    }

     

    do-while先执行一遍操作,然后持续判断循环条件,如果条件为真则继续执行,直到条件为假退出。
    do{
      //执行循环操作
    }while(循环条件);  //注意有分号

     

    注意:while和do-while是在程序不知道具体停止位置而使用的,如果知道具体位置,请使用for。

    for循环一般用于循环次数确定的情况,它将条件的初始化、条件判断、条件变化抽取出来,和循环体分离。
    for(参数初始化;条件判断;参数更新){
      //循环操作
    }

     

    在while、do-while、for等循环语句中,break语句用来跳出循环从而执行循环后面的语句。
    while(...){
      ....
      break;
      //下面的语句执行不到了,因为break跳出了循环
      printf(...);
    }
    //break以后,执行这个语句

     

    continue用于跳过本次循环体剩余语句,进入下一次循环的条件判断。
    for(int i=0; i< 100; ++i){
       if(如果是奇数){
          continue;
       }
       sum += i;
    }

     

    用break可以跳出一层循环,如果想跳出多重循环,可以使用goto语句。例如
    #include 
    int main(){
      int i,j;
      for(i=0;i<4;++i){
        printf("%d\n",i);
        for(j=0;j<4;++j){
          if(i*j==4){
            goto l1;
          }
          printf("\t%d:\n",j);
        }
      }
      l1:
      printf("循环跳出,i=%d,j=%d\n",i,j);
      return 0;
    }
    //注:在c语言中,由于goto的灵活性,一般不提倡使用goto进行跳转。
    

      

    part3高级数据类型


     

     

    数组
  • 数组是一个变量,用来存储相同类型的一组数据。数组四要素:变量名称、元素、下标(从0开始)、类型,通过下标访问数组的元素。
    类型 变量名称
    int size = 3;
    int narray[3] = {1,2,4};
    int d1 = narray[0];//0是下标,narray[0]是下标为0的元素
    //注意下标不能超出数组的长度-1
    for(i=0;i <= size;i++){
    //循环体
    }

     

    数组的存储 数组在内存里顺序存放,每个元素存放的大小按数组元素类型而定(以字节为最小单位)
    int a[4]; //a[0](假设地址为2000h)、a[1]、a[2]、a[3]
    2000h   2004h  2008h  200ch
    a[0]     a[1]   a[2]   a[3]

     

    下标越界:数组在声明赋值的时候,编译器会检查是否越界,例如
    int a[4]={1,2,3,4,5}; //编译提示越界
    

      

    但在运行时访问下标的时候,编译器不会检查越界,此时程序员要自己注意。例如:

    int a[4]={1,2,3,4};
    printf("%d",a[4]); //a[4]指向2010h地址,内存的值未知
    a[4] = 100; //如果修改了a[4]的值,实际是修改了别人的数据,可能导致程序出错或崩溃
    

     

    局部数组变量声明后,并没有自动初始化,此时如果2000h~200ch内存地址里面已经有值,就会被拿来使用,导致程序运行混乱。所以局部数组变量需要进行初始化,例如:
    1)int a[4]={}; 
    2) #include 
       int a[4]; 
       memset(a, 0, 4*sizeof(int)); //以字节数为单位赋值
    3)for循环将每个元素设置成0

     

    注:static变量或全局变量自动会初始化成0。

    数组地址分配的规则,用&显示变量的地址,注意结果地址的增加字节数。
    #include 
    int main(){
      int i,a[4];
      for(i=0;i<4;++i){
        printf("a[%d]的地址是:%p\n",i,&a[i]);
      }
      printf("数组变量a的地址是:%p\n",&a);
      return 0;
    }
    

      

    输出结果为:

    =====运行开始======
    a[0]的地址是:0x7fff1d9e5ae0
    a[1]的地址是:0x7fff1d9e5ae4
    a[2]的地址是:0x7fff1d9e5ae8
    a[3]的地址是:0x7fff1d9e5aec
    数组变量a的地址是:0x7fff1d9e5ae0
    
    =====运行结束======

     

    数组值求解 最大最小值:循环数组,将每个元素和max变量比较,如果元素比max大,则将元素赋值给max,循环完成以后max就是全数组的最大值。
    int i, max=arr[0];
    for(i=1;i  

     

    均值:
    int i,sum=0;
    for(i=0;i

      

    查找:用下标来判断是否找到
    int value = 5; //要查找的数
    int i;
    for(i=0;i

      

    删除:删除指定下标的元素:将此下标后面的元素依次往前移动一位,最后一位设置为’\0’,例如删除下标为3的元素。
    for(i=3; i

      

    删除给定值的元素:先通过查找获得下标,然后执行1。

    int find=0;
      int removeindex;
      for(removeindex=0; removeindex

     

    在数组的指定位置插入一个新元素,前提是数组长度足够。 算法:将此位置及之后的元素都往后移动一位,然后将此位置的元素设置为新元素的值。例如,在下标2处插入一个10
    int a[6] = {1,2,3,4,5}; //注意数组长度要够
    for(i=len-1; i>2; --i){ //注意i从大到小
      a[i] = a[i-1]; //依次往后移动,空出a[2]
    }
    a[2] = 10;
    

      

    拷贝:循环赋值或用memcpy函数。
    for(i=0; i

      

    合并:同样可以用循环赋值或memcpy函数。

    int a[3]={1,2,3}, b[4]={4,5,6,7};
    int c[7];
    for(i=0; i<7; ++i){
       c[i] = (i<3)?a[i]:b[i-3];
    }
    
    //或:memcpy(c, a, 3*4);
    //memcpy(c+3, b, 4*4);
    

     

    二维数组a[i][j],可以把i视为行,把j视为列:
    int scores[2][4] = {{1,2},{2,3},{3,4,5,6}};
    for(int i=0; i<2; i++){
      for(int j=0; j<4; j++){
         printf("%d",scores[i][j]);
    }
      printf("\n");
    }
    

      

    结果为

    =====运行开始======
    1200
    2300
    //因为2行中赋值中有{1,2,(0,0)}和{2,3,(0,0)},即不足的以0来填补。
    =====运行结束======

     

     

     

    二维数组的存储与矩阵的简单表示 在逻辑上是二维的,其两个下标在两个方向变化,但实际的存储是连续的,c语言中,按照行排列的方式存储:存放完一行后,再存放下一行的元素,如:
    scores[3][5],三个行如下:
    scores[0] --> 一班的分数:5个元素的一维数组 
    scores[1] --> 二班的分数:5个元素的一维数组
    scores[2] --> 三班的分数:5个元素的一维数组
    
    共15个元素,按照顺序存储,如:
    2000h 2001h 2002h  2003h 2004h 
    00     01    02    03    04  
    2005h 2006h  2007h 2008h 2009h
    10     11    12    13    14
    2010h 2011h  2012h 2013h 2014h
    20     21    22    23    24

     

    矩阵算法可以使用二维数组进行运算,如a[3][3]:

    1 2 3  --> a[0]
    4 5 6  --> a[1]
    7 8 9  --> a[2]

     

     

     


    字符串
  • 概念:在c语言中,将字符串作为字符数组处理,以’\0’作为字符串的结束标志,’\0’占内存空间,但不计入字符串长度。
    "hello"  ---> {'h','e','l','l','o','\0'}
    //字符串长度为5,数组长度为6
    

      

    输出字符串数组变量时,遇到’\0’结束,’0’是ascii码的0,可以通过循环输出看到,例如

    char a[] = "hello";
      int len = sizeof(a)/sizeof(a[0]);  //6
      printf("%s\n",a); //hello
      for(int i=0; i 最后一个是 0
    

     

    字符数组的定义和赋值:

    char a[] = "hello";
    char a[] = {"hello"};
    char a[] = {'h','e','l','l','o','\0'}; 
    
    char a[];
    a = "hello";  //错误,定义后就是常量,不能再整句赋值
    

     

    字符串函数(c语言提供了专门处理字符串的函数库:#include 拷贝函数strcpy:strcpy(目标字符数组, 源字符数组),将源字符数组的内容(包括结束符\0)拷贝到目标字符数组中,函数返回目标字符数组。
    char s[10]
    strcpy(s, "hello");  //s-->hello\0
    

      

    strcpy的覆盖性:如果目标字符数组中已经存在内容,将被覆盖。

    char s[10]="123456789"
    strcpy(s, "hello");  //s-->hello\0789
    printf("%s",s)       //hello
    

     

    strncpy:strncpy(目标字符数组,源字符数组,拷贝长度),将源字符数组的前n个字符(不一定包括结束符\0)拷贝到目标字符数组中,函数返回目标字符数组。

    char s[10]="123456789"
    strncpy(s, "hello",3);  //s-->hel456789
    printf("%s",s);       //hel456789
    

     

    strcpy的溢出性:如果目标字符数组的长度比源字符数组的长度短,strcpy函数会导致溢出,即目标字符数组地址后面的内存地址会被写入多出的字符,产生不可预期的后果。

    char a = 'a';
    char s[3];
    strcpy(s,"hello"); //s-->hello\0 --> 可能会导致a被修改
    

     

    连接函数strcat(目标字符数组,源字符数组),将源字符数组的内容连接到目标字符数组后面,第一个连接过去的字符替代了目标字符数组的’\0’,函数返回目标字符数组。
    char s1[20] = "i am";
    char s2[] = "a boy";
    strcat(s1,s2); //s1-->i am a boy\0
    printf("%s",s1); //i am a boy
    

      

    比较函数:c语言中用strcmp函数对字符串进行比较:strcmp(字符数组1,字符数组2),返回值是:
    字符数组1 = 字符数组2, 返回值=0
    字符数组1 > 字符数组2, 返回值>0
    字符数组1 < 字符数组2, 返回值<0

     

    当需要判断两个字符串是否相等,例如判断密码是否为“137000”,代码如下:

    char pwd[] = "137000";
    int b = strcmp(pwd,"137000");  //b == 0
    

     

    长度计数函数strlen:strlen(字符数组)返回字符串的长度,计算到\0为止(不包括\0),如
    char s[20] = "hello";
    printf("%d",strlen(s));  //5
    

      

    strlen与sizeof的区别

    char s[10] = "hello";
    printf("%d",strlen(s));   //5
    printf("%d",sizeof(s));   //10
    

     

    1)sizeof是编译时计算,关注分配了多少空间,不关注实际存储的数据
    2)strlen是运行时计算,关注存储的数据内容,不关注分配的空间。

    函数
  • 概念:函数是程序的一个包含单元,用于完成某一特定的任务,比如一类计算、一种操作等等,使用函数对这些任务进行封装,减少重复编码,使程序更加模块化,增强可读性和可维护性。例如
    int main(){
       //打印三个*行
       int i;
       for(i=0; i<3; ++i)
         printstar();
    }
    

      

    函数的调用:函数是一个黑匣子,外部无需知道函数里面具体怎么执行的,只需要调用函数,获取返回的结果即可。

    函数定义的语法:返回值类型、函数名、形参列表、函数体,例如
    返回值类型  函数名(参数列表)
    void printstar(int n, char c){
      //在这里输出*号组成的行
    }
    

      

    函数需要先定义、后调用,即函数的定义应当在主调函数之前,如

    void printstar(int n, char c){}
    int main(){
      printstar(5,'*');
      ...
    }

     

    有时候我们想先调用函数,之后再定义函数,则需要在调用之前先声明,这和变量的声明、使用、赋值类似,如

    int main(){
       void printstar(int n, char c); //声明,注意没有函数体
       printstar(5,'*');
    }
    
    void printstar(int n, char c){//定义这里有函数体}
    

     

    调用系统库函数,要使用#include<库文件>,在中,库文件使用*.h文件,例如

    #include 
    #include   //使用库文件
    int main(){
        printf("%d的绝对值是%d", -3,abs(-3));  //输出绝对值
        return 0;
    }
    

     

    函数的执行过程 程序的执行入口是main函数,从main函数体的起始处开始执行,main函数体的所有代码都执行完成后会自动退出程序。 遇到对其它函数的调用,则暂停当前执行,保存当前的状态现场和返回地址,转到被调用函数的入口地址进行执行,当遇到return语句或函数执行结束时,恢复之前保存的现场,从返回地址处开始继续执行。例如
    #include 
    int add(int a, int b){
      printf("\t开始执行add函数,参数%d,%d\n",a,b);
      int sum = a + b;
      printf("\tadd执行完成,准备返回%d\n",sum);
      return sum;
    }
    
    int main(){
      printf("开始执行main函数\n");
      printf("调用add函数\n");
      int a = add(3,5);
      printf("调用add函数返回:%d\n",a);
      
      a = add(1+2,2*4);
      printf("调用add函数返回:%d\n",a);
    
      printf("main函数执行结束\n");
      return 0;
    }
    

      

    =====运行开始======
    开始执行main函数
    调用add函数
        开始执行add函数,参数3,5
        add执行完成,准备返回8
    调用add函数返回:8
        开始执行add函数,参数3,8
        add执行完成,准备返回11
    调用add函数返回:11
    main函数执行结束
    
    =====运行结束======
    

      

    参数列表 形参:在函数定义时,每个参数定义是“类型 变量名”的声明格式,由逗号分隔。
    int add(int a, int b){
       //在这里可以使用变量a和b
    }
    

      

    参数列表的定义相当于定义了局部变量,其作用域是函数体,在函数没有被调用时,形参没有内存分配,当函数调用时被分配内存,函数结束后,内存被释放。

    实参:在函数调用时,传入与形参类型一致、个数一致的参数列表,如果不相同会执行自动格式转换。调用时不需要在前面加变量类型。
    int main(){
       int sum = add(3.2, 5); //3.2传给a(自动转换),5传给b
    }
    

      

    值传递 值传递:实参传入数据时,传递给形参(函数内局部变量)的是实参的值而不是变量本身(地址),对形参的任何修改不会影响到实参。
    int change(int a){
       return ++a;
    }
    
    int main(){
       int a = 5;
       change(a);
       printf("%d",a); //a还是5,不会变成6
    }
    

      

    内存和存储类型 c语言中,程序占用的内存分为代码区、数据区(静态存储区)、动态存储区(又分为栈和堆)。
    1)静态存储区:主要存放全局变量、静态变量(static)、字面常量,其生存期是从程序开始到程序结束;
    2)动态存储区:主要存放局部变量、参数等,在程序运行期间动态分配,其中栈和堆的使用又有所不同。

     

    自动变量:说明符为auto,凡是函数内未加存储类型说明的都是auto类型,例如
    {
       int a;  --->  auto int a;
    }
    

      

    自动变量属于动态存储方式,当函数被调用时才分配存储,函数结束即自动释放。作用域是定义它的单元,如函数或代码块。

    静态变量:说明符为static,在函数中的static变量属于静态存储方式,程序启动时被分配,程序结束后释放。作用域取决于其定义的位置,函数内定义的作用域就是函数体,函数外定义的作用域就是本文件。例如
    {
       static int a;  //函数结束不会释放,下次还可以继续用
    }
    

      

    static的理解:具备静态存储特性,又具备函数的作用域。

    注:全局变量或函数前面加static,表示作用域在本文件内部,不能被其它文件引用。
    外部变量:说明符为extern,全局变量的默认方式,当文件中要引用另一文件中的全局变量,或者在全局变量定义之前要引用它时,可以用extern做说明。例如
    file1.cpp           file2.cpp
    int global;  <---  extern int global; //用到file1.cpp
    

      

    寄存器变量:说明符为register,建议将变量存储在cpu的寄存器中,效率非常高。例如
    int main(){
       register int i, sum=0;
    }
    

      

    函数的嵌套调用和递归调用 在函数定义中允许再调用其它函数,即函数可以嵌套调用。
    int sum(int a, int b){return a+b;}
    int avg(int a, in b){return sum(a,b)/2;}
    

      

    函数内调用自身称之为递归调用,这种函数称为递归函数。
    int sum(int a){return a+sum(a+1);}
    

      

    栈溢出:当调用函数时,当前状态现场、返回地址要被保存到“栈”(stack)里,等到函数调用结束时恢复现场,从返回地址继续执行,如果递归函数没有控制,一直递归下去,就会导致栈溢出(*exception),所以递归函数切记要考虑使用计数或状态来终止递归循环。
    递归的2个条件:
    1)有反复执行的过程(自己调自己)
    2)有跳出反复过程的条件(递归出口)
    

      

    把数组作为函数参数 数组名作为函数的参数,传递的是数组的地址,实参和形参指向同一个地址,所以形参元素被修改会影响到实参。如
    //由于传递的是地址,不需要指定个数,需要传入个数的参数
    void changearr(int a[], int len){
       a[0] = 100;
    }
    
    int main(){
       int a[5] = {1,2,3,4,5};
       changearr(a,5);  //传入的是数组名
       printf("%d",a[0]);
    }
    

      

    由于传入是地址,数组参数不需要指定个数,需要另外传入数组的长度参数(防止越界),如计算数组最大的数:
    int max(int a[], int len){
      int i, max = a[0];
      for(i=0; i  

     

    结构体 结构体概述 定义:将不同类型的数据组合在一起,构造出一种新的数据类型,这种形式称为“结构体”,声明结构体的语法关键字是struct:
    struct student //student是新定义的结构名
    {
      int no;    //学号
      char name[10];  //姓名
      int age;     //年龄
      char address[30];  //地址
    };  //注意分号
    

      

    结构体体现了数据的封装性,用结构体变量进行信息传递,一方面可以减少参数的数量,另一方面当业务逻辑有变化时,可以很方便的修改结构体的定义,不需要修改传递接口(函数定义)。例如:

    struct car {
     ...
    }
    
    //当汽车增加新成员字段时,不需要修改buycar的函数定义
    void buycar(struct car c){
       ....
    }
    

     

    结构体变量作为函数参数时,采取的是值传递的方式,即形参产生了和实参同样数据的一个内存拷贝。一方面,结构体数据多的时候内存开销大,另一方面,函数里面对数据的修改不会影响到实参。如

    void changecar(struct car c){
       c.price = 200;
    }
    
    struct car c = {"宝马","x5","3.0","红色",130};
    changecar(c);  //在函数里修改价格
    printf("%d",c.price); //价格还是130

     

    如果想让结构体变量在函数中被修改,跟其他类型一样,可以使用指针,如

    void change2(struct car *c){
      c->price = 200;  //指针引用成员用 -> 运算符
    }
    change2(&c);
    

     

    结构体作为返回值:当函数需要返回多个值时,可以用结构体变量。例如

    struct point {
       int x;
       int y;
    };
    struct point createpoint(){
      struct point p = {0,1};
      return p;
    }
    

     

    联合体 将不同的数据类型组合起来,并且共享同一内存,这样的数据类型称之为联合体(union),联合的定义声明方式和结构体(struct)类似,例如。
    union test {
       int i;
       char c;
       float f;
    } a, b, c;
    

      

    注意:一个联合体变量一次只能给一个成员赋值,后赋值会覆盖前面的赋值,只有最后赋值的成员才有意义。

    #include 
    union test{
      int i;
      char c;
      float f;
    };
    int main(){
      union test t ;
      printf("i=%d,c=%d,f=%f\n",t.i,t.c,t.f);
      t.i = 10;
      printf("i=%d,c=%d,f=%f\n",t.i,t.c,t.f);
      t.c = 'a';
      printf("i=%d,c=%d,f=%f\n",t.i,t.c,t.f);
      t.f = 1.0f;
      printf("i=%d,c=%d,f=%f\n",t.i,t.c,t.f);
      
      return 0;
    }

      

    =====运行开始======
    i=1314011120,c=-16,f=881720320.000000
    i=10,c=10,f=0.000000
    i=97,c=97,f=0.000000
    i=1065353216,c=0,f=1.000000
    
    =====运行结束======
    

      

    枚举:如果一个变量的值只有几种可能,可以将这些值列举出来,定义为枚举类型(enum)。例如:
    enum weekday {sun,mon,tue,wed,thu,fri,sat};
    enum weekday day1 = sun; //变量的值只能是枚举元素之一
    

      

    #include 
    enum weekday {sun,mon,tue, wed,thu,fri,sat};
    int main(){
      enum weekday day1 = sun, day2;
      printf("day1 = %d\n",day1);
      day2 = (enum weekday)(day1 + 3);
      printf("day2 = %d\n",day2);
      if(day2 == wed)
        printf("星期天过3天是星期三\n");
      
      return 0;
    }
    

      

    使用typedef可以为已有类型定义新的类型名,往往可以使用我们自己定义的结构,这样很好地具现化出真正的对象结构。如
    typedef struct student{
      int no;
      string name;
    } student;  //定义新的结构体类型student
    
    student zhangsan;

    指针 概述 变量的内存结构:类型、变量名、地址、数据:
    int n = 10;   
    2001h~2004h   ----> 10
    &n (int有4个字节)

     

    指针:变量的指针就是变量的地址(内存中的首地址)

    指针变量:指针变量是一种特殊的变量,它的数据是内存的一个地址(unsigned long int),通过这个内存可以找到此内存本身存储的数据。如:
    int a = 10
    int *p; //定义一个指针变量,指针的类型是int
    p = &a; //p是指针变量,p的数据是a的地址
    
     p     p的数据是地址  地址指向的值
    2005h ---> 2001h --> 10
    *p -->  a的地址里面的数据  --> 10
    

      

    *是“间接运算符”,用来定义一个指针变量,以及取指针变量指向的数据。赋给指针变量的值必须是地址常量或变量,不能为普通整数(可以为 0表示空指针),此地址中存放的数据类型必须与指针类型相符。如

    1)int *p;   //定义指针变量 p
    int *p = &a; ---> int *p;  p=&a
    2)printf("%d\n",*p);  //取指针变量p指向的数据
    *(&a)  --> a  //取&a这个地址(指针)指向的数据

     

    指针修改值
    *p = 5; //把指针的值修改为5,n == 5
    

      

    void类型的指针可以赋予任何类型对象的地址,如
    int n; char c;
    void *p;
    p = &n;
    p = &c;
    

      

    注意:void类型指针不能直接输出或运算,需要转换成指定类型。

    void *pv;
    int *pint, n;
    pv = &n;
    pint = (int*)pv;  //强制类型转换

     

    指针的算术运算 指针相减:指向同一个数组的两个指针相减,返回他们之间的数组元素个数。如
    int n[5];
    int *p1=n,*p2=&n[3];
    p1-p2 --> 3
    

      

    注:不是同一数组的指针相减没有意义。

     

    指针与整数的加减:指针变量与整数的加减是一个算术表达式,返回新的指针变量,指向的地址为原指针地址加减指针指向类型的整数倍,例如
    int n, *p=&n;  //int是4字节
    p+1  --> (n的地址 + 4 )的新指针
    

      

    自增自减:++和--可用于指针运算,、++、--的优先级相同,都是右结合性。例如:p++ –> 先计算*p,再将p指向p+1个类型长度的地址。
    int a=10,b=20,c=30;
    int *p=&b;
    *p --> 20
    *p++ --> 20,但此时p指向 p+1的 地址(4个字节)
    

      

    指针的关系运算 当两个指针p和q指向同一数组中的元素时,可以进行比较:
    pq p指向的元素在q指向的元素之后,返回1,否则0
    p==q 两者指向同一元素,返回1,否则0
    p!=q 不指向同一元素,返回1,否则0

     

    与null比较:
    p==null 空指针返回1,否则0
    p!=null 不是空指针返回1,否则0

     

    指针与常量 指向常量的指针:指针变量指向一个常量,此时指针所指向的值不能修改,但指针本身(存储的地址)可以指向另外的对象,例如
    int a = 3, b = 10;
    const int *p = &a;
    *p = 5;  //报错,不能修改
    p = &b; //可以修改
    

      

    指针常量:声明指针常量,则指针本身不能改变。例如
    int a=3, b=10;
    int *const p = &a;
    p = &b;  //报错,不能修改
    *p = 5;  //可以修改
    

      

    理解:看const后面是什么,如果是*p则表示指针的值不能修改,如果是p则表示指针不能更换指向。

    指针与结构体 结构体类型的指针变量,定义方式为
    struct 结构体类型 *变量名;
    
    struct student stu, *p;
    p = &stu;

     

    成员的引用:访问成员有以下三种方式:
    stu.name;
    (*p).name;
    p->name; //指针专用,指向运算符,优先级比单目运算符高
    p->age++; //先获得age成员,再++
    

      

    指针作为函数参数 函数参数是指针变量时,传递的是指针存储的地址,而变量做参数时传递的是具体的值(会产生形参的拷贝)。指针做参数时,地址(指针自己的值)不能被修改,但地址指向的值可以被修改。例如
    void change(int *p){
       *p = 20;
    }
    int main(){
       int a=5;
       change(&a);  //传入指针,在函数里a的值被修改
    }
    

      

    函数指针:回调函数,又称callback,是一种事件驱动的模式,将函数指针变量作为参数传递给另外一个函数,函数里面当某些事件满足时会调用此函数指针变量指向的函数,例如。
    void print(int n){  //回调函数,打印信息
      printf("回调函数调用:%d\n",n);
    }
    
    void loop(int max, int (*p)(int n)){ 
    //遍历1..n,如果7的倍数就打印信息
      int i;
      for(i=0;i

      

     

    
    ;++i){>
    ;>
  • ;> ;> ;> ;++i){> ;++i)> ;++i){>