Bootstrap

C语言复习笔记

C语言复习笔记

一,数据类型

1)标识符

a. 概念: 编程时给变量或者函数起的名字就是标识符

b. C 语言规定,标识符可以是字母(A~Z,a~z)数字(0~9)下划线_组成的字符串,并且第一个字符必须是字母或下划线

重点细节:

  • 标识符的长度最好不要超过8位,因为在某些版本的C中规定标识符前8位有效,当两个标识符前8位相同时,则被认为是同一个标识符。
  • 标识符是严格区分大小写的。
  • 标识符最好选择有意义的英文单词组成。
  • 标识符不能是C语言的关键字

2)基本数据类型

imgimgimg

img

3)格式化输出语句

格式为:printf(“输出格式符”,输出项)

C语言中的常用格式化符:

img

4)不可改变的常量

概念: 在程序执行过程中,值不发生改变的量称为常量。C语言的常量可以分为直接常量和符号常量**。**

  • 整型常量:13、0、-13;
  • 实型常量:13.33、-24.4;
  • 字符常量:‘a’、‘M’
  • 字符串常量:”I love imooc!”

在C语言中,可以用一个标识符来表示一个常量,称之为符号常量。符号常量在使用之前必须先定义,其一般形式为:

#define 标识符 常量值

#include <stdio.h>
#define POCKETMONEY 10    //定义常量及常量值
int main()
{
    printf("小明今天又得到%d元零花钱\n", POCKETMONEY);
    return 0;  
}

5)自动类型转换

注意:自动转换发生在不同数据类型运算时,在编译的时候自动完成

下图表示了类型自动转换的规则:

img

  • 注: char类型数据转换为int类型数据遵循ASCII码中的对应值
  • 字节小的可以向字节大的自动转换但字节大的不能向字节小的自动转换

6)强制转换类型

强制类型转换是通过定义类型转换运算来实现的。其一般形式为:

(数据类型) (表达式)

img

输入结果:

img

注意:

  • 数据类型和表达式都必须加括号
  • 转换后不会改变原数据的类型及变量值,只在本次运算中临时性转换
  • 强制转换后的运算结果不遵循四舍五入原则

二,C中的运算符

1)运算符的种类

※ 算术运算符

※ 赋值运算符

※ 关系运算符

※ 逻辑运算符

※ 三目运算符

2)算数运算符

img

除法运算中注意:

如果相除的两个数都是整数的话,则结果也为整数,小数部分省略,如8/3 = 2;而两数中有一个为小数结果则为小数,如:9.0/2 = 4.500000。

取余运算中注意:

该运算只适合用两个整数进行取余运算,如:10%3 = 1;而10.0%3则是错误的;运算后的符号取决于被模数的符号,如(-10)%3 = -1;而10%(-3) = 1。

3)自增与自减运算符

img

4)赋值运算符

C语言中赋值运算符分为简单赋值运算符复合赋值运算符

  • 简单赋值运算符“=”
  • 复合赋值运算符就是在简单赋值符“=”之前加上其它运算符构成,例如+=、-=、*=、/=、%=。

注意:复合运算符中运算符和等号之间是不存在空格的。

5)关系运算符

img

关系表达式的值是“真”和“假”,在C程序用整数1和0表示

注意:>=,<=,==,!=这种符号之间不能存在空格。

6)逻辑运算符

img

7)三目运算符

格式为:

表达式1 ? 表达式2 : 表达式3;

执行过程是:

先判断表达式1的值是否为真,如果是真的话执行表达式2;如果是假的话执行表达式3。

#include <stdio.h>
int main()
{
    //定义小编兜里的钱
    double money = 12     ; 
    //定义打车回家的费用
    double cost =  11.5     ;  
    printf("小编能不能打车回家呢:"); 
    //输出y小编就打车回家了,输出n小编就不能打车回家
    printf("%c\n",money>=cost? 'y' : 'n'                      );
    return 0;
}

8)运算符大比拼之优先级比较

img

三,C程序结构语句

1)基础语句种类

  • if
  • if-else
  • while
  • do-while
  • for
  • switch
  • goto
  • break
  • continue

2)switch语句

img

#include <stdio.h>
int main() 
{
    int score = 87; //考试分数为87分 
    score /=10;
    switch(score)
    {
        /*想想以下代码为什么用这种方式;
          这种方式的好处?*/
        case 10:
        case 9:
            printf("等级A");
            break;
        case 8:
            printf("等级B");
            break; #include <stdio.h>
int main() 
{
    int score = 87; //考试分数为87分 
    score /=10;
    switch(score)
    {
        /*想想以下代码为什么用这种方式;
          这种方式的好处?*/
        case 10:
        case 9:
            printf("等级A");
            break;
        case 8:
            printf("等级B");
            break;                //这里是不是缺点什么?            
        case 7:
        case 6: 
            printf("等级C");
            break;                //这里是不是缺点什么?  
        default:
            printf("等级D");
            break;    
    }
    return 0;
}              

特别注意!!!

  1. 在case后的各常量表达式的值不能相同,否则会出现错误。
  2. 在case子句后如果没有break;会一直往后执行一直到遇到break;才会跳出switch语句。

default(违约)一定要会拼写!!!

3)for语句

img

注意:

  • for循环中的“表达式1、2、3”均可可以缺省,但分号(;)不能缺省
  • 省略“表达式1(循环变量赋初值)”,表示不对循环变量赋初始值
  • 省略“表达式2(循环条件)”,不做其它处理,循环一直执行(死循环)
  • 省略“表达式3(循环变量增量)”,不做其他处理,循环一直执行(死循环)
  • 表达式1可以是设置循环变量的初值的赋值表达式,也可以是其他表达式
  • 表达式2一般是关系表达式或逻辑表达式,但也可是数值表达式或字符表达式,只要其值非零,就执行循环体。
  • 各表达式中的变量一定要在for循环之前定义。(在 表达式1 中 int 或者 在for循环之前 int)

4)语句综合练习 99乘法表

#include <stdio.h>

int main() 

{ 
    // 定义相乘数字i,j以及结果result
    int i, j, result;
    for(i=9;i>=1;i--)
    {
         for(j=1;j<=i;j++)
         {
             result=i*j;
            printf("%d*%d=%d\t",i,j,result);
         }
        printf("\n");
    }
   return 0;
}

四,函数练习

1)自定义函数

注意:

先在main函数前 声明自定义函数 再将自定义函数代码写在main函数后

2)形参与实参

img

3)函数的返回值

概念:

函数的返回值是指函数被调用之后,执行函数体中的程序段所取得的并返回给主调函数的值。

例:

imgimgimg

没有返回值的函数,返回类型为void

注意:void函数中可以有执行代码块,但是不能有返回值,另void函数中如果有return语句,该语句只能起到结束函数运行的功能。其格式为:return;

4)!!!递归函数!!!

例题一:

任务

小明为了学好英语,需要每天记单词,第一天记1个,第二天记2个依次类推,到第10天的时候小明一共记了多少个单词?

请用代码完成,算出小明第10天开始的时候会了多少个单词?

第10行根据注释提示,填写代码

输出结果为:img

#include <stdio.h>
/* 定义获取单词数量的函数 */
int getWordNumber(int n)
{   
    if(n == 1)
    {
        return 1;    //第一天只会1个单词
    }
    else{
    int words=    getWordNumber (n-1)+n;
        return words ;       //到第天会的单词数量
    }
}
int main()
{
    int num = getWordNumber(10);     //获取会了的单词数量
    printf("小明第10天记了:%d个单词。\n", num);
    return 0;
}

例题二:

计算N的阶乘:

img

5)局部与全局

  1. 局部变量也称为内部变量。局部变量是在函数内作定义说明的。其作用域仅限于函数内。
  2. 全局变量也称为外部变量,它是在函数外部定义的变量。它不属于哪一个函数,它属于一个源程序文件。其作用域是整个源程序

6)<目前还不了解原理,需要导师讲解>变量存储类别(艰涩难懂)

A. C语言根据变量的生存周期来划分,可以分为静态存储方式动态存储方式

​ 静态存储方式:是指在程序运行期间分配固定的存储空间的方式。静态存储区中存放了在整个程序执行过程中都存在的变量,如全局变量。

​ 动态存储方式:是指在程序运行期间根据需要进行动态的分配存储空间的方式。动态存储区中存放的变量是根据程序运行的需要而建立和释放的,通常包括:函数形式参数;自动变量;函数调用时的现场保护和返回地址等。

​ C语言中存储类别又分为四类:自动(auto)、静态(static)、寄存器的(register)和外部的(extern)。

注: 用extern声明的的变量是外部变量,外部变量的意义是某函数可以调用在该函数之后定义的变量。如:

img

7)内部函数与外部函数

内部函数,内部函数由static关键字来定义,因此又被称谓静态函数,形式为:static [数据类型] 函数名([参数])

外部函数 ,外部函数由extern关键字来定义,形式为: extern [数据类型] 函数名([参数])

调用外部函数:#include “外部函数名称”

五,数组

1)数组基础知识

声明数组 :

​ 数据类型 数组名称[长度];

数组初始化:

  • 数据类型 数组名称[长度n] = {元素1,元素2…元素n};

  • 数据类型 数组名称[] = {元素1,元素2…元素n};

  • 数据类型 数组名称[长度n]; 数组名称[0] = 元素1; 数组名称[1] = 元素2; 数组名称[n-1] = 元素n;

注意:

1.数组的下标均以0开始;数组在初始化的时候,数组内元素的个数不能大于声明的数组长度;

2.如果采用第一种初始化方式,元素个数小于数组的长度时,多余的数组元素初始化为0;

3.在声明数组后没有进行初始化的时候,静态(static)和外部(extern)类型的数组元素初始化元素为0,自动(auto)类型的数组的元素初始化值不确定。

2)数组作为函数参数

例题:

#include <stdio.h>
void replaceMax(int arr[],int value)
{
    int max = arr[0];
    int index = 0;
    int i;
    for(i=1;i<5;i++)
    {
        if(arr[i]>max)
        {
            max=arr[i];         //将数组中较大的数赋值给max
            index = i;  //记录当前索引
        }                
    }  
    arr[index] = value;
}

int main()
{
    int arr1[] = {10,41,3,12,22};
    int arr2[] = {1,2,3,4,5};
    int i;
    replaceMax(arr1 ,arr2[0]   ); //将数组arr1和数组arr2的第一个元素传入函数中
    for(i=0;i<5;i++)
    {
        printf("%d ",arr1[i]);                
    }
    return 0;    
}

3)数组应用:冒泡排序

例题:

#include <stdio.h>
int main()
{
    double arr[]={1.78, 1.77, 1.82, 1.79, 1.85, 1.75, 1.86, 1.77, 1.81, 1.80};
    int i,j;
    printf("\n************排队前*************\n");
    for(i=0;i<10;i++)
    {
        if(i != 9)   
            printf("%.2f, ", arr[i]);  //%.2f表示小数点后精确到两位
        else
            printf("%.2f", arr[i]);    //%.2f表示小数点后精确到两位
    }
    for(i=8; i>=0; i--)
    {
        for(j=0;j<=i;j++)
        {
            if(     arr[j]>arr[j+1]     )      //当前面的数比后面的数大时
            {
                double temp;    //定义临时变量temp
                temp = arr[j];//将前面的数赋值给temp
                arr[j] = arr[j+1] ;              //前后之数颠倒位置
                arr[j+1]=temp;             //将较大的数放在后面    
            }                 
        }                
    }
    printf("\n************排队后*************\n");
    for(i=0;i<10;i++)
    {
        if(i != 9)   
            printf("%.2f, ", arr[i]);  //%.2f表示小数点后精确到两位     
        else
            printf("%.2f", arr[i]);    //%.2f表示小数点后精确到两位
    }
    return 0;    
}

4)数组应用:元素查询

#include <stdio.h>
int getIndex(int arr[5],int value)
{

    int i;
    int index;
    for(i=0;i<5;i++)
    {
       /* 请完善数组查询功能 */
        if( arr[i] == value ) { 
        index = i ;
        break;
          
       
       
       
    }
    else {
        index=-1;
        
    }}
    return index;
}

int main()
{
    int arr[5]={3,12,9,8,6};
    int value = 8;
    int index = getIndex( arr , value );      //这里应该传什么参数呢?
    if(index!=-1)
    {
        printf("%d在数组中存在,下标为:%d\n",value,index);             
    }
    else
    {
        printf("%d在数组中不存在。\n",value);    
    }
    return 0;    
}

5) 字符串数组

定义格式:

1、char 字符串名称[长度] = “字符串值”;

2、char 字符串名称[长度] = {‘字符1’,‘字符2’,…,‘字符n’,‘\0’};

注意

1、[]中的长度是可以省略不写的;

2、采用第2种方式的时候最后一个元素必须是’\0’,'\0’表示字符串的结束标志;

3、采用第2种方式的时候在数组中不能写中文

在输出字符串的时候要使用:printf(“%s”,字符数组名字);或者puts(字符数组名字);。例如:

img

6)字符串函数

img

1.strlen()获取字符串的长度,在字符串长度中是不包括‘\0’而且汉字和字母的长度是不一样的。比如:

img

但是sizeof函数是包括 \0 的!

2.strcmp()在比较的时候会把字符串先转换成ASCII码再进行比较,返回的结果为0表示s1和s2的ASCII码相等,返回结果为1表示s1比s2的ASCII码大,返回结果为**-1表示s1比s2的ASCII码小**,例如:

img

3.strcpy()拷贝之后会覆盖原来字符串且不能对字符串常量进行拷贝

4.strcat在使用时s1与s2指的内存空间不能重叠,且s1要有足够的空间来容纳要复制的字符串

7)多维数组

相当于数组里继续存储数组!!!!

初始化方式:

1、数据类型 数组名称[常量表达式1][常量表达式2]…[常量表达式n] = {{值1,…,值n},{值1,…,值n},…,{值1,…,值n}};

2、数据类型 数组名称[常量表达式1][常量表达式2]…[常量表达式n]; 数组名称[下标1][下标2]…[下标n] = 值;

多维数组初始化要注意以下事项:

1、采用第一种始化时数组声明必须指定列的维数。因为系统会根据数组中元素的总个数来分配空间,当知道元素总个数以及列的维数后,会直接计算出行的维数

2、采用第二种初始化时数组声明必须同时指定行和列的维数。

六,指针

————————————————

借鉴声明:指针笔记参考此博主 讲的太细了 非常好!!!!

版权声明:本文为CSDN博主「ZackSock」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/ZackSock/article/details/101594794

1)变量和地址

&取地址符 and !!!

例:

int x;

int* px = &x;

代表px为 int* 类型的指针 ,为 变量x 的地址。

x为 int 类型的变量。

printf(“%d”,x); 与 printf(“%d”,*px); 输出结果相同

均为x的值!!!

2) “&”和“*”的结合方向

  1. “&”和“”都是右结合的。假设有变量 x = 10,则&x 的含义是,先获取变量 x 的地址,再获取地址中的内容。因为“&”和“*”互为逆运算,所以 x = *&x。

3)指针变量的初始化

一定进行初始化避免 野指针

指针变量与其它变量一样,在定义时可以赋值,即初始化。也可以赋值“NULL”或“0”,如果赋值“0”,此时的“0”含义并不是数字“0”,而是 NULL 的字符码值。

//利用取地址获取 x 的地址,在指针变量 px 定义时,赋值给 px
int x;
int *px = &x;
//定义指针变量,分别赋值“NULL”和“0”
int *p1= NULL, *p2 = 0;

4)指针运算

1.赋值运算

指针变量可以互相赋值,也可以赋值某个变量的地址,或者赋值一个具体的地址

int *px, *py, *pz, x = 10;
//赋予某个变量的地址
px = &x;
//相互赋值
py = px;
//赋值具体的地址
pz = 4000;
2.指针与整数的加减运算
  1. 指针变量的自增自减运算。指针加 1 或减 1 运算,表示指针向前或向后移动一个单元(不同类型的指针,单元长度不同)。这个在数组中非常常用。
  2. 指针变量加上或减去一个整形数。和第一条类似,具体加几就是向前移动几个单元,减几就是向后移动几个单元。
//定义三个变量,假设它们地址为连续的,分别为 4000、4004、4008
int x, y, z;

//定义一个指针,指向 x
int *px = &x;

//利用指针变量 px 加减整数,分别输出 x、y、z
printf("x = %d", *px);		//因为 px 指向 x,所以*px = x

//px + 1,表示,向前移动一个单元(从 4000 到 4004)
//这里要先(px + 1),再*(px + 1)获取内容,因为单目运算符“*”优先级高于双目运算符“+”
printf("y = %d", *(px + 1));		
printf("z = %d", *(px + 2));
3.关系运算

假设有指针变量 px、py。

  1. px > py 表示 px 指向的存储地址是否大于 py 指向的地址
  2. px == py 表示 px 和 py 是否指向同一个存储单元
  3. px == 0 和 px != 0 表示 px 是否为空指针

5)指针与数组

在数组中,数组名即为该数组的首地址,结合上面指针和整数的加减,就可以实现指针访问数组元素。

定义了一个数组 nums,在定义时分配了 10 个连续的int 内存空间。而一个数组的首地址即为数组名nums,或者第一个元素的首地址也是数组的首地址。那么有两种方式让指针变量 p 指向数组 nums:

int nums[10], *p;
//数组名即为数组的首地址
p = nums;
//数组第一个元素的地址也是数组的首地址
p = &nums[0];

上面两句是等价的

如下几个操作用指针操作数组

*p = 1,此操作为赋值操作,即将指针指向的存储空间赋值为 1。此时 p 指向数组 nums 的第一个元素,则此操作将 nums 第一个元素赋值为 0,即 nums[0] = 1。
p + 1,此操作为指针加整数操作,即向前移动一个单元。此时 p + 1 指向 nums[0]的下一个元素,即 nums[1]。通过p + 整数可以移动到想要操作的元素(此整数可以为负数)。
如上面,p(p + 0)指向 nums[0]、p + 1 指向 nums[1]、、、类推可得,p+i 指向 nums[i],由此可以准确操作指定位置的元素。
在 p + 整数的操作要考虑边界的问题,如一个数组长度为 2,p+3 的意义对于数组操作来说没有意义。

以上内容很重要!!!

下面写一段代码,用指针访问数组的元素:

//定义一个整形数组,并初始化
int nums[5] = {4, 5, 3, 2, 7};

//定义一个指针变量 p,将数组 nums 的首地址赋值给 p,也可以用p = &nums[0]赋值
int *p = nums, i;			//i 作为循环变量

//p 指向数组第一个元素(数组首地址),我们可以直接用间接寻址符,获取第一个元素的内容
printf("nums[0] = %d\n", *p);			//输出结果为 nums[0] = 4

//我们可以通过“p + 整数”来移动指针,要先移动地址,所以 p + 1 要扩起来
printf("nums[1] = %d\n", *(p + 1));		//输出结果为 nums[1] = 5

//由上面推导出*(p + i) = nums[i],所以我们可以通过 for 循环变量元素
for(i = 0; i < 5; i++){
	printf("nums[%d] = %d", i, *(p + i));
}

注:数组名不等价于指针变量,指针变量可以进行 p++和&操作,而这些操作对于数组名是非法的。数组名在编译时是确定的,在程序运行期间算一个常量。

6)多级指针

数据类型 **二级指针名;

和指针变量的定义类似,由于是右结合的,所以pp 相当于*(p)。在本次定义中,二级指针的变量名为 pp,而不是**p。多级指针的定义就是定义时使用多个“”号。

//定义普通变量和指针变量
int *pi, i = 10;
//定义二级指针变量
int **ppi;

//给指针变量赋初值
pi = &i;

//给二级指针变量赋初值
ppi = &pi;

//我们可以直接用二级指针做普通指针的操作
//获取 i 的内容
printf("i = %d", **ppi);
//获取 i 的地址
printf("i 的地址为%d", *ppi);

注:在初始化二级指针 ppi 时,不能直接 ppi = &&i,因为&i 获取的是一个具体的数值,而具体数字是没有指针的。

7)指针数组

指针变量和普通变量一样,也能组成数组,指针数组的具体定义如下:

数据类型 *数组名[指针数组长度];

//定义一个数组
int nums[5] = {2, 3, 4, 5, 2}, i;

//定义一个指针数组
int *p[5];

//定义一个二级指针
int **pp;

//循环给指针数组赋值
for(i = 0; i < 5; i++){
	p[i] = &nums[i];
}

//将指针数组的首地址赋值给 pp,数组 p 的数组名作为 p 的首地址,也作为 p 中第一个元素的地址。
//数组存放的内容为普通变量,则数组名为变量的指针;数组存放的内容为指针,则数组名为指针的指针。
pp = p;

//利用二级指针 pp 输出数组元素
for(i = 0; i < 5; i++){
	//pp == &p[0] == &&nums[0],nums[0] == *p[0] == **pp
	printf("%d", **pp);
	
	//指针变量+整数的操作,即移动指针至下一个单元
	pp++;
}

8)多维数组的指针

在学习指针与数组的时候,我们可以如下表示一个数组:

int nums[5] = {2, 4, 5, 6, 7};
int *p = nums;

//----------------------------(分界线)

//定义一个二维数组
int nums[2][2] = {
	{1, 2},
	{2, 3}
};

//此时 nums[0]、和 nums[1]各为一个数组
int *p[2] = {nums[0], nums[1]};

//我们可以用指针数组 p 操作一个二维数组

//p 为数组 p 的首地址,p[0] = nums[0] = *p,**p = nums[0][0]
printf("nums[0][0] = %d", **p);

//指针 + 整数形式,p+1 移动到 nums 的地址,*(p +1) = nums[1],则**(p + 1) = nums[1][0]
printf("nums[1][0] = %d", **(p + 1));

//先*p = nums[0],再*p + 1 = &nums[0][1],最后获取内容*(*p + 1)即为 nums[0][1]
printf("nums[0][1] = %d", *(*p + 1));

: 为什么*p + 1 = &nums[0] [1 ],而不是 nums[1]。p 获得的是一个一维数组,而 int 数组 + 1 的跨度只有 4 个字节,也就是一个单元。前面 p 是一维数组的指针,其跨度为一个数组。所以p + 1 = &nums[0 ] [ 1],而 p + 1 = nums[1]。

9)指针与函数

1.函数参数为指针

用来交换两个变量的内容

void swap(int *x, int *y);
void main(){
	int x = 20, y = 10;
	swap(&x, &y);
	printf("x = %d, y = %d", x ,y);
}
void swap(int *x, int *y){
	int t;
	t = *x;
	*x = *y;
	*y = t;
}

这里传入的参数为指针,所以调用 swap 方法后 x,y 的内容发生了交换。如果直接传入 x,y,那么交换只在 swap 中有效,在 main 中并没有交换。

2.函数的返回值为指针

数据类型 *函数名(参数列表){
函数体
}

//例如:
int s;
int *sum(int x, int y){
	s = x + y;
	return &s;
}
int s;
void mian(){
	int *r = sum(10, 9);
	printf("10 + 9 + %d", *r);
}
int *sum(int x, int y){
	s = x + y;
	return &s;
}

3.指向函数的指针

#include <string.h>
/**
*	定义一个方法,传入两个字符串和一个函数指针 p,用 p 对两个字符串进行操作
*/
void check(char *x, char *y, int (*p)());
void main(){
	//string.h 库中的函数,使用之前需要声明该函数。字符串比较函数
	int strcmp();
	char x[] = "Zack";
	char y[] = "Rudy";
	
	//定义一个函数指针
	int (*p)() = strcmp;

	check(x, y, p);
}
void check(char *x, char *y, int (*p)()){
	if(!(*p)(x, y)){
		printf("相等");
	}else{
		printf("不相等");
	}
}

利用函数指针调用方法具体操作如下:

(*p)(x, y);

;