C语言复习笔记
一,数据类型
1)标识符
a. 概念: 编程时给变量或者函数起的名字就是标识符
b. C 语言规定,标识符可以是字母(A~Z,a~z)、数字(0~9)、下划线_组成的字符串,并且第一个字符必须是字母或下划线。
重点细节:
- 标识符的长度最好不要超过8位,因为在某些版本的C中规定标识符前8位有效,当两个标识符前8位相同时,则被认为是同一个标识符。
- 标识符是严格区分大小写的。
- 标识符最好选择有意义的英文单词组成。
- 标识符不能是C语言的关键字。
2)基本数据类型
3)格式化输出语句
格式为:printf(“输出格式符”,输出项)
C语言中的常用格式化符:
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)自动类型转换
注意:自动转换发生在不同数据类型运算时,在编译的时候自动完成。
下图表示了类型自动转换的规则:
- 注: char类型数据转换为int类型数据遵循ASCII码中的对应值
- 注:字节小的可以向字节大的自动转换,但字节大的不能向字节小的自动转换
6)强制转换类型
强制类型转换是通过定义类型转换运算来实现的。其一般形式为:
(数据类型) (表达式)
输入结果:
注意:
- 数据类型和表达式都必须加括号
- 转换后不会改变原数据的类型及变量值,只在本次运算中临时性转换。
- 强制转换后的运算结果不遵循四舍五入原则。
二,C中的运算符
1)运算符的种类
※ 算术运算符
※ 赋值运算符
※ 关系运算符
※ 逻辑运算符
※ 三目运算符
2)算数运算符
除法运算中注意:
如果相除的两个数都是整数的话,则结果也为整数,小数部分省略,如8/3 = 2;而两数中有一个为小数,结果则为小数,如:9.0/2 = 4.500000。
取余运算中注意:
该运算只适合用两个整数进行取余运算,如:10%3 = 1;而10.0%3则是错误的;运算后的符号取决于被模数的符号,如(-10)%3 = -1;而10%(-3) = 1。
3)自增与自减运算符
4)赋值运算符
C语言中赋值运算符分为简单赋值运算符和复合赋值运算符
- 简单赋值运算符“=”
- 复合赋值运算符就是在简单赋值符“=”之前加上其它运算符构成,例如+=、-=、*=、/=、%=。
注意:复合运算符中运算符和等号之间是不存在空格的。
5)关系运算符
关系表达式的值是“真”和“假”,在C程序用整数1和0表示
注意:>=,<=,==,!=这种符号之间不能存在空格。
6)逻辑运算符
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)运算符大比拼之优先级比较
三,C程序结构语句
1)基础语句种类
- if
- if-else
- while
- do-while
- for
- switch
- goto
- break
- continue
2)switch语句
#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;
}
特别注意!!!
- 在case后的各常量表达式的值不能相同,否则会出现错误。
- 在case子句后如果没有break;会一直往后执行一直到遇到break;才会跳出switch语句。
default(违约)一定要会拼写!!!
3)for语句
注意:
- 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)形参与实参
3)函数的返回值
概念:
函数的返回值是指函数被调用之后,执行函数体中的程序段所取得的并返回给主调函数的值。
例:
没有返回值的函数,返回类型为void
注意:void函数中可以有执行代码块,但是不能有返回值,另void函数中如果有return语句,该语句只能起到结束函数运行的功能。其格式为:return;
4)!!!递归函数!!!
例题一:
任务
小明为了学好英语,需要每天记单词,第一天记1个,第二天记2个依次类推,到第10天的时候小明一共记了多少个单词?
请用代码完成,算出小明第10天开始的时候会了多少个单词?
第10行根据注释提示,填写代码
#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的阶乘:
5)局部与全局
- 局部变量也称为内部变量。局部变量是在函数内作定义说明的。其作用域仅限于函数内。
- 全局变量也称为外部变量,它是在函数外部定义的变量。它不属于哪一个函数,它属于一个源程序文件。其作用域是整个源程序。
6)<目前还不了解原理,需要导师讲解>变量存储类别(艰涩难懂)
A. C语言根据变量的生存周期来划分,可以分为静态存储方式和动态存储方式。
静态存储方式:是指在程序运行期间分配固定的存储空间的方式。静态存储区中存放了在整个程序执行过程中都存在的变量,如全局变量。
动态存储方式:是指在程序运行期间根据需要进行动态的分配存储空间的方式。动态存储区中存放的变量是根据程序运行的需要而建立和释放的,通常包括:函数形式参数;自动变量;函数调用时的现场保护和返回地址等。
C语言中存储类别又分为四类:自动(auto)、静态(static)、寄存器的(register)和外部的(extern)。
注: 用extern声明的的变量是外部变量,外部变量的意义是某函数可以调用在该函数之后定义的变量。如:
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(字符数组名字);。例如:
6)字符串函数
1.strlen()获取字符串的长度,在字符串长度中是不包括‘\0’而且汉字和字母的长度是不一样的。比如:
但是sizeof函数是包括 \0 的!
2.strcmp()在比较的时候会把字符串先转换成ASCII码再进行比较,返回的结果为0表示s1和s2的ASCII码相等,返回结果为1表示s1比s2的ASCII码大,返回结果为**-1表示s1比s2的ASCII码小**,例如:
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) “&”和“*”的结合方向
- “&”和“”都是右结合的。假设有变量 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 运算,表示指针向前或向后移动一个单元(不同类型的指针,单元长度不同)。这个在数组中非常常用。
- 指针变量加上或减去一个整形数。和第一条类似,具体加几就是向前移动几个单元,减几就是向后移动几个单元。
//定义三个变量,假设它们地址为连续的,分别为 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。
- px > py 表示 px 指向的存储地址是否大于 py 指向的地址
- px == py 表示 px 和 py 是否指向同一个存储单元
- 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 = π
//我们可以直接用二级指针做普通指针的操作
//获取 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);