写在前面的话:本文是学习C语言基础的笔记,如果是纯新手,请先在B站看相关课程学习了解后或者看相关C语言的书籍在看本文的知识点笔记,由于本文的性质是笔记,也只是记录了我本人的对于知识点的不熟悉以及一些重点,而记录并梳理的知识点。如果想学习C语言基础,您可以先看看B站的相关视频或者看C Primer Plus这本书对您帮助也许会很大
本文使用的软件主要是由Linux中去运行代码
1 数据类型(及相关知识点)
1.1 32个关键字
关键字:系统预定义好的,有特定含义的,全部都是小写且不能被重新定义。
数据类型:char,double,int,float.short.long,struct,union.enum.void
控制语句:if.else.while.do.for.switch.goto.default.break.continue.case
数据类型:auto.register.static.entern
return:返回函数
const:只读
signed:有符号数
unsigned:无符号数
sizeof:计算所占内存大小
typedef:给一个已有的类型取别名
volatile:防止编译器优化(简略学习,存储器部分学习)
标识符:程序员自己定义,一般用来定义变量名,函数名,类别名。
命名规范(简单易懂字面意思)
- 有数字,字母,下划线组成
- 第一个是字母不能是数字
- 不能和关键字重名
1.2 基本的数据类型
字符型:char(1byte)
整形:int(4byte)、long(4byte)、short(2byte)
浮点型:float(4byte)、double(8byte)
相对于32位操作系统 long的长度是4byte,而在64位操作系统中long长度是8byte。
PS:每一种数据类型所占内存大小不一样,数据类型主要是让我们合理分配内存。
有符号数与无符号数
PS:计算机内默认是符号数
signed:数值有正负之分,以补码的形式存储,最高位是符号位,正数的符号位是0,反之为1。
unsigned:只有正数。
1.2.1 字符型
值域范围: char(1byte) 1byte = 8bit
unsigned:0000 0000 ~ 1111 1111 0 ~ 255
signed:1000 0000 ~ 0111 1111 -128 ~ 127(如果全是1111 1111 那原码就是-1,并不是最小)
如果输入值超出范围会转圈去计算值
unsigned char c =260;
printf("%d\n",c); // 4
signed char a = 130;
printf("%d\n",a); // -126
1.2.2 整形家族
1.2.3浮点型家族
浮点型存储方式和正数的存储方式是不一样的
浮点型的存储方式决定了他不能够准确的表示一个数,只能是一个近似的值
double(8byte)双精度浮点型数,有效数字的位数一般是15-16位(%lf)
%f和%lf默认输出小数点后6位
printf(”格式控制串“,输出表);
格式控制串:原样输出的内容+格式化符(%d,%f)
输出表:要输出的对象
1.3 转义字符
1.4 常量
在程序运行期间,其数值不会被改变的量
1.4.1 字符常量
‘a’ ‘b’ ‘c’
1.4.2 整形常量
二进制:1010
八进制:453
十进制:99
十六进制:0x153
注意:默认情况下,整型常量都是有符号的!
无符号的int型数:66U
长整型:66L
无符号的长整型:66UL
1.4.3 浮点型常量
小数:1.23 0.11111 100000(浮点型常量包含整型常量)
#include<stdio.h>
int main(void)
{
flost a = 0.00001;
printf("%g\n",a);//选择小数和指数比较合适的一种方式
return 0;
}
小数 : 1.23 0.000001 10000(浮点型常量也包含整型常量)
指数形式:(1e-5)(1e+5)
案例:%g打印去切换打印小数与指数
1.4.4 字符串常量
PS:字符串以‘、0’作为结束符
1.4.5 标识常量(宏)
#define 宏名 表达式
注意:
-
宏名一般用大写,小写也可以,因为为了和变量区分,所以用大写
-
宏后面没有分号
只是单纯的替换
example: #define PI 3.14
宏函数:(既有函数的特点,又有宏的特点)
#define 函数名(形参) 函数体
注意:宏只是单纯的替换,不会考虑运算的优先级问题,所以需要给每个形参加括号,整个表达式也加括号。
案例:用预处理的方式去查看代码是如何运行公式的。
1.5 变量
1.5.1 局部变量与全局变量
局部变量:定义在函数体内部的变量(任何函数体)
全局变量:定义在函数体外的变量。
1.5.2 存储类型
存储类型:auto,extern。static,register
auto:修饰的变量存储在栈区,只能修饰局部变量。
auto 存储类是所有局部变量默认的存储类。
定义在函数中的变量默认为 auto 存储类,这意味着它们在函数开始时被创建,在函数结束时被销毁。
extern:修饰的变量存储在静态区(.bss与.data统称为静态区),只能修饰全局变量。
extern 存储类用于定义在其他文件中声明的全局变量或函数。当使用 extern 关键字时,不会为变量分配任何存储空间,而只是指示编译器该变量在其他文件中定义。
extern 存储类用于提供一个全局变量的引用,全局变量对所有的程序文件都是可见的。当您使用 extern 时,对于无法初始化的变量,会把变量名指向一个之前定义过的存储位置。
当您有多个文件且定义了一个可以在其他文件中使用的全局变量或函数时,可以在其他文件中使用 extern 来得到已定义的变量或函数的引用。可以这么理解,extern 是用来在另一个文件中声明一个全局变量或函数。
static:修饰的变量存储在静态区,局部变量和全局变量都可以修饰。
static 存储类指示编译器在程序的生命周期内保持局部变量的存在,而不需要在每次它进入和离开作用域时进行创建和销毁。因此,使用 static 修饰局部变量可以在函数调用之间保持局部变量的值。
static 修饰符也可以应用于全局变量。当 static 修饰全局变量时,会使变量的作用域限制在声明它的文件内。
全局声明的一个 static 变量或方法可以被任何函数或方法调用,只要这些方法出现在跟 static 变量或方法同一个文件中。
静态变量在程序中只被初始化一次,即使函数被调用多次,该变量的值也不会重置。
register:修饰的变量存储在寄存器中,只能修饰局部变量。
register 存储类用于定义存储在寄存器中而不是 RAM 中的局部变量。这意味着变量的最大尺寸等于寄存器的大小(通常是一个字),且不能对它应用一元的 ‘&’ 运算符(因为它没有内存位置)。
register 存储类定义存储在寄存器,所以变量的访问速度更快,但是它不能直接取地址,因为它不是存储在 RAM 中的。在需要频繁访问的变量上使用 register 存储类可以提高程序的运行速度。
寄存器只用于需要快速访问的变量,比如计数器。还应注意的是,定义 ‘register’ 并不意味着变量将被存储在寄存器中,它意味着变量可能存储在寄存器中,这取决于硬件和实现的限制。
小结:
- 除了static与register修饰的局部变量外,其他的都存在栈区。
- 全局变量存储在静态区。
- 静态变量存储在静态区。(static修饰的变量就是静态变量)
1.5.3 初始化
定义:定义变量时就对变量赋值。
int a = 10; //初始化
(在一行里面就是初始化)int = a; a=10; //赋值
(分成两行就属于是赋值)
举例:
小结:
- 全局变量没有初始化其默认值为0.
- 局部变量没有初始化其默认值为随机值(64位操作系统优化为0)
auto:修饰局部变量,存储在栈区。
register:修饰局部变量,存储在寄存器,建议把变量存储在寄存器,可以提高程序的运行速度,最终是否存储在寄存器中取决于编译器,如果没有寄存器则存储在栈区。
extern:修饰全局变量。存储在静态区。
#include<stdio.h>
//告诉编译器,该变量在其他文件已经定义过了。
extern int a;
int main(void)
{
printf("a=%d\n",a);
return 0;
}
**作用:**告诉编译器,这个全局变量已经在其他文件定义过了。
static:修饰的变量存储在静态区,即可以修饰全局变量也可以修饰局部变量。
- static修饰局部变量时,延长了局部变量的生命周期,如果局部变量没有初始化,其值为0,如果初始化,只能初始化一次
- 修饰全局变量,只能在本文件内使用(限制了全局变量的作用域)
1.5.4 生命周期
定义:从什么时候开辟空间等到什么时候释放空间。
使用的范围:
局部变量:
生命周期:从定义开始,到模块(大括号)结束
作用域:大括号内
static修饰的局部变量:
生命周期:从定义开始,到程序结束
作用域:大括号内
全局变量:
生命周期:从定义开始,到程序结束
作用域:整个程序
static修饰的全局变量:
生命周期:从定义开始,到程序结束
作用域:本文件内
2 运算符与输入输出
2.1 数据类型转换
2.1.1 强制类型转换(手动转换)
PS:括号里面为强制转换的类型,括号后面为转换对象
2.1.2 隐式类型转换(编译器自己转的)
横向箭头:不管有没有进行混合运算,都势必进行转换。
注意:char,short使用的时候编译器都是自动转换成int类型。
注意:float使用的时候自动转换成double来使用
注意:竖向箭头只有进行混合运算时才会进行转换
example:(只是转换了数据类型,但是表达式的值还是继续进行混合运算。)
2.2 运算符
口诀:单算移关与,异或逻条赋。
运算符的优先级:
2.2.1 单目运算符
单目运算符是只接受一个操作数的运算符,主要包括:
!、++、- -、&、sizeof
~、–、 +、* 、(类型)
sizeof运算符以字节为单位,返回运算对象的大小。(计算所占内存大小)
求补运算符 ~ :对整形操作数执行求补操作,操作数中原来所有为1的位变为0,为0的位变为1。
取址操作符 & :产生它的操作数的地址。
间接访问操作符 * :与指针一起使用,用于访问指针多指向的值
强制类型转换符 “(类型)” :显式的把表达式的值转换为另外的类型。
2.2.2 算术运算符
+、- 、* 、 / 、% 、 ++、 –
注意:
- %不能用于浮点数
- ++a 先自加,后赋值 a++ 先赋值,后自加
2.2.3 左移右移
<< 左移 >> 右移
int a = 0011 1101 1010;
int b = -4;//1111111111111100;
a = a << 2;//a = 1111 0110 1000;
a = a >> 2;//a = 0000 1111 0110;
b = b >> 2;//1111111111111111;
PS:有符号数,低位丢弃,正数高位补0,负数高位补1
2.2.4 关系运算符
< , > , >= , <= , != , ==
example:
2.2.5 与 异 或 取反
按位与 & 按位异或 ^ 按位或 |
PS:按位异或 ^ 全0或全1出 0,不同时出1
按位与 & :全1出1,见0出0 按位或 | :见1出1 ,全0出0 取反 ~ :1变0,0变1
example:
int a = 1010 1100 1111;
int b = 1111 0101 1010;
a & b = 1010 0100 1010;
a | b = 1111 1101 1111;
a ^ b = 0101 1011 0101;
~a = 0101 0011 0000;
2.2.6 逻辑运算符
2.2.7 条件运算符(三目运算符)
条件运算符,是一种三元运算符(三目运算符)。顾名思义,"三元"指的是一个表达式含有三个操作对象;在Java中,条件运算符主要作用就是用来简化 if…else 语句。
//通过 ? 和 : 运算符判断 ? 前的语句是true还是false,并根据结果返回 : 前后的值。
int a = 10;
b = (a == 1) ? (20): (30);
printf("b = %d\n",b);
判断表达式1的值是否成立。若成立,表达式2为整个表达式结果;否则表达式3为结果。
每一个参数都需要加括号。
2.2.8 赋值
2.2.9 逗号运算符
逗号运算符:从左到右依次进行,最后一个表达式的结果是整个表达式的结果。
#include<stdio.h>
int main(void)
{
int a = 3, b = 5;
int d = (a++, ++b);
printf("%d %d %d\n",a, b, c);
return 0;
}
PS:逗号运算符的优先级是最低的,所以使用时需要加括号。
2.3 输入输出
函数:有独立功能的模块
标准的输入输出函数:scanf,printf(对变量的类型没有限制)
输入:从键盘拷贝数据到内存中
输出:从内存拷贝数据到显示屏
2.3.1 输出
printf(“格式控制串”,输出表)
格式控制串:原样输出的内容(可省)+格式化符
输出表:要输出的对象
整型:
%d:十进制整数
%o:八进制整数
%x,%X:十六进制整数
#:自动在八进制和十六进制前加前缀
%u:无符号整型
%hd:short类型
%ld:long类型
%lld:longlong类型
字符型: %c
浮点型:
%f:float
%lf:double
%e:指数
%g:选择小数和指数合适的一种
.n:保留n个小数
m:指定我们输出的域宽,默认是右对齐,m的值大于数据的实际长度,左边补空格,否则,原样输出
**注意:**m前加 - 需要补充的空格会补在右端
float a = 3.1415;
printf("%-8.2f", a);
2.3.2 输入
scanf(“格式控制串”,地址表)
地址表:&+变量名
注意:
- scanf格式控制串,不要加修饰语,如果要加,原样输入(例如%d后面加\n \t 等)
scanf("%d", &a);
- 如果输入“%d%d”时要给多个变量赋值,在格式控制符直接没有间隔,那在输入的时候,以空格,回车,tab作为一个变量的间隔
scanf("%d%d%d",&a,&b,&c);
-
输入结束,必须以回车作为结束符
-
如果是“%c%c”,在输入的时候,不能有空格,回车,tab,因为空格,回车,tab也是字符
解决办法 :
- 在“%c%c”之间加个空格,逗号(输入时原样输入)解决办法
- 加%*c,’ * ‘代表是抑制符
scanf("%c,%c",&a,&b);
scanf("%c*%c",&a,&b);
2.3.3 字符的输入输出
int getchar();
返回值:从键盘得到的ASCII码
putchar ( );
参数:你要输出的ASCII码
char a;
a = getchar();
putchar();
2.3.4 ASCII
只需要记住重要的几个:‘A’ = 65 ‘a’ = 97 空格 = 32 ‘\0’ = 0 ‘0’ = 48
贪心算法: 从左往右分配
c = a +++++ b;// c= (((a++)++)+b)
printf运算规则:右结合,从右到左计算
example:
首先:a++,第四位输出定格在2,a=3,
其次,a,第三位输出不能确定a=3
然后:++a,此时a=4,但第二位还不能确定
最后:第一个赋值确定为4,a=5,第二位第三位为5可以确定
总结:先赋值的第一次就可以确定,后赋值的要等运算完成,才能确定。
首先,++a等于7第一个
其次,第二个++a,影响到了前面的值,第二个等于8,所以第一个也为8
最后,后面的自加运算不受影响。
总结:只有第一个受到了第二个的影响。其余的就算有第三个第四个五个表达式都是不受影响的。
3 控制语句
三大结构:顺序结构、选择结构、循环结构。
3.1 顺序结构
按照实物本身特性,必须一个接着一个来完成。
3.2 选择结构
3.2.1 单分支if语句
if(表达式)
{
语句1;
}
先判断表达式的值,如果表达式的值为真,则执行语句
3.2.2 双分支if语句
if(表达式)
{
语句1;
}
else
{
语句2;
}
先判断表达式的值,如果表达式的值为真,执行语句1,否则执行语句2
example:输入一个年份,判断该年是平年还是闰年
#include<stdio.h>
int main(void)
{
int year;
printf("please input year:\n");
scanf("%d", &year);
if(year % 4 == 0 && year % 100 != 0 || year % 400 == 0)
{
printf("%d is leap year\n",year);
}
else
{
printf("%d is not leap year\n",year);
}
return 0;
}
3.2.3 多分支if语句
if(表达式1)
{
语句1;
}
else if(表达式2)
{
语句2;
}
。。。。。
else if(表达式n)
{
语句n;
}
else
{
语句n+1;
}
从上到下,依次判断表达式的值,如果成立,则执行对应的语句。
example:输入一个成绩,判断成绩的等级
#include<stdio.h>
int main(void)
{
int score;
printf("please input score:\n");
scanf("%d",&score);
if(score < 60)
{
printf("E\n");
}
if(score < 70)
{
printf("D\n");
}
if(score < 80)
{
printf("C\n");
}
if(score < 90)
{
printf("B\n");
}
else
{
printf("A\n");
}
return 0;
}
3.2.4 switch语句
switch(表达式)
{
case 标号1 :语句1;
case 标号2:语句2;
......
case 标号n:语句n;
default:语句n+1;
}
注意:
- switch后的表达式不能是float类型。
- 标号必须是常量。
- 表达式==标号时,执行冒号后面的语句,直到switch,case语句结束,或者碰到break语句结束。
特殊例题:
表达式的值是标号几,便会从标号几开始,直到遇见break结束
案例:
输入年份,月份,输出该年该月有多少天?
3.3 循环结构
重复的去做某一件事
循环的三要素:循环的起始条件,循环的终止条件,循环变量的变化
3.3.1 for循环
for(表达式1;表达式2;表达式3)
{
循环体;
}
表达式1:循环的起始条件
表达式2:循环的终止条件
表达式3:循环变量的变化
先执行表达式1的值,再执行表达式2的值,如果表达式2的值为真,执行循环体,然后执行表达式3的值,如此反复,直到表达式2的值为假,跳出循环
注意:表达式2最好不要省去,会缺少判断条件,导致死循环。
表达式1,2,3都是可以省去的,但是不可以省略分号“ ; ”
案例:打印图案
1.3.2 while语句
while(表达式)
{
循环体;
}
判断表达式的值是否成立,如果成立,执行循环体,否则,跳出循环
1.3.3 do while语句
do
{
循环体;
}while(表达式);
小结:while和do while区别
while先判断,再执行,语句至少执行0次
do while先执行,后判断,语句至少执行1次
1.3.4 goto语句
无条件跳转语句,一般格式为goto语句标号
语句标号:按照标识符命名规范
3.3.5 break与continue语句
break:1,跳出switch语句 2,跳出循环
Continue:结束本次循环,开始下一次循环
使用continue需要注意的是:特殊例题
没有第一个i++就是死循环
1.3.6 死循环
for(;1;)
{
语句;
}
while(1)
{
语句;
}
4 数组
4.1 概念
一组数据类型相同的元素的集合
特点:(1)数据类型相同(2)地址连续
打印地址%p
4.2 定义
存储类型 数据类型 变量名;
int a;//定义了一个整型变量
存储类型 数据类型 数组名[元素的个数];
int a[5];//定义了一个整型数组
存储类型:auto,static,extern,register
数据类型:数组中每一个元素的数据类型
数组的数据类型:数据类型 [元素个数]
数据类型:去掉变量名就是数据类型
数组所占内存空间=sizeof(数据类型)*元素个数
数组元素个数不同,数据类型也不同
注意:
-
数组名代表整个数组
-
数组名也代表数组首元素的地址
注意:元素个数必须是一个确定的数
4.3 初始化
4.3.1 部分初始化
在进行部分初始化的时候,未初始化部分的值为0,因此,利用这一特点可以给数组中元素清零。
int arr[100]= {0};
4.3.2 全部初始化
int a[5] = {1,2,3,4,5};
int a[ ] = {1,2,3};//在进行全部初始化的时候,数组元素的个数是可以省略的,由后面赋值的具体个数来决定
4.3.3 未初始化
总结:
-
数组定义在函数体内,没有初始化,其值为随机值
-
数组定义在函数体外,没有初始化,其值为0
-
如果数组被static修饰,没有初始化,其值为0
4.4 数组的访问
数组名[下标];
注意:下标是从0开始
特例:
- int a[5]; a[5] = {1,2,3,4,5};//error
- int a[5]; a[] = {1,2,3,4,5};//error,数组名代表数组首元素地址
- int a[ ] = {0};//表示你这个数组中只有一个元素,没有意义,但是正确的
- int a[ ] ;//数组的元素个数必须是一个确定的数
5. 排序
5.1 冒泡排序(C语言中唯一的算法)
思想:从左到右边,两两依次比较,如果前一个数比后一个数大,则交换(每一趟把最大的数放在最后)
example:
6 字符数组
字符数组的本质:其实就是字符串,以’\0’作为结束符
存储类型 数据类型 数组名[元素个数];
char str[10];//定义了一个字符数组,名字叫str,有十个元素,每一个元素都是char型
char str[6] = {‘h’,’e’,’l’,’l’,’o’,’q’};//error 最多存放五个元素,留一个位置给‘\0’
char str[6] = “hello”; //如果是标号是6,那么只能由五个元素,字符串最后默认为‘\0’
char str[6] = {‘\0’};//字符数组清零(整型数组的清零 int a[7] = {0})
6.1 字符数组的输出函数
%s:字符串
puts(数组名);
功能:将数组中的内容输出打印到终端,并且自动加换行
注意:遇到’\0’结束
6.2 字符串输入函数
gets(数组名);
功能:将键盘接收到的字符串存放在数组中,并在末尾自动加’\0’
注意:不会越界检查,所以输入时不要越界
example:
回车被直接使用
6.3 小结
6.3.1(scanf与gets的区别)
字符串gets是以回车作为结束符,但是scanf以回车,table,空格作为结束符
缓冲区:
-
gets:当完成字符串的输入后,会自动清空缓冲区内容
-
scanf:当完成字符串的输入后,缓冲区会遗留空格,回车,tab
注意:
-
gets首先会检查缓冲区有没有内容,如果有,直接拿来使用,否则输入
-
scanf是标准的输入函数,只能通过键入方式处理
6.3.2 printf与puts的区别
puts会自动添加换行,而printf不会
7 字符串处理函数
strlen, strcpy, strcat, strcmp
头文件:#include<string.h>
7.1 求字符串长度
strlen(数组名);
功能:求字符串的长度
返回值:求得的字符串的实际长度,不包含’\0’
strlen和sizeof的区别:
- strlen是求得的字符串的实际长度,不包含’\0’,而sizeof求得的是整个空间的大小
- sizeof是运算符,strlen是库函数
案例:求字符串的长度,不使用库函数
7.2 字符拷贝函数
strcpy(数组1,数组2/字符串);
功能:将数组2的内容拷贝到数组1,包含’\0’,相当于完全拷贝
注意:数组1的容量大于数组2
strncpy(数组1,数组2/字符串2,n);
功能:将数组2的前n个字符拷贝到数组1中
注意:拷贝的字符不包含’\0’
案例:实现字符串的拷贝,不使用库函数
7.3 字符串连接函数
strcat(数组1,数组2/字符串2);
功能:将数组2或者字符串2的内容连接到数组1中,数组1的’\0’会被覆盖
注意:数组1的容量大于数组2
strncat(数组1,数组2/字符串2,n);
功能:将数组2的前n个字符,连接到数组1中
案例:实现字符串的连接,不使用库函数
7.4 字符串的比较函数
strcmp(数组1/字符串1,数组2/字符串2);
功能:比较字符串1和字符串2的大小
返回值:
大于0:字符串1 > 字符串2
等于0:字符串1 == 字符串2
小于0:字符串1 < 字符串2
比较规则:
从左到右依次对字符串的ASCII码进行比较,直到遇到不同的ASCII码或者遇到’\0’结束
8 二维数组
数组:一组数据类型相同的元素的集合
整型数组:一组int型的数集合在一起
字符数组:一组char型的字符集合在一起
二维数组:一组(数组类型的)元素集合在一起
8.1 概念
本质:表示元素为一维数组的数组(数组的数组)
数组特点:(1)数据类型相同(2)地址连续
8.2 定义
存储类型 数据类型 数组名[元素个数];
存储类型 数据类型 数组名[行数][列数];
行数:有几个一维数组
列数:一维数组中有几个元素个数
int a[2][3];//定义了一个二维数组,数组的长度为2,每一个元素又是长度为3的int型数组
8.3 数组的初始化
8.3.1 部分初始化
int a[2][3]= {1,2};
8.3.2 全部初始化
int a[2][3] = {1,2,3,4,5,6};
int a[2][3] = {{1,2,3},{4,5,6}};
//注意:二维数组的行数可以省略,列数不能省
int a[ ][3] = {1,2,3,4,5,6,7,8};//表示有三个一维数组,每一个一维数组有三个元素
int a[2][ ] = {1,2,3,4,5,6};//error有俩个一维数组,但每一个一维数组中有几个元素,不能确定
8.4 数组的访问
8.5 二维字符数组
char dtr[30] = “hello”;
scanf(“%s”,dtr);
printf(“%d\n”,dtr);
存储类型 数据类型 数组名[行数][列数];
行数:(一维字符数组的个数)字符串的个数
列数:(一维字符数组中字符的个数)每一个字符串最多存放几个字符
char str[3][20];//定义了一个长度为3的数组,每一个元素都是长度为20的字符数组
案例:实现字符串的输入输出
9 函数
定义:一个独立的功能模块。
原则:先定义,后使用
9.1 为什么要使用函数
- 为了使程序变得模块化。
2. 提高代码的复用率
9.2 函数的分类
9.2.1 库函数
例如:printf 、scanf 、strlen 等等
需引入头文件:
#include<stdio.h>
#include<string.h>
调用函数:
strlen(str)
函数名:(实际参数列表):
注意:参数有多少个,数据类型是什么,返回值。
9.2.2自定义函数
9.2.2.1函数定义
存储类型 数据类型 函数名 (形式参数列表)
{
函数体;
返回值;
}
存储类型: auto ,static, extern, register
数据类型: 函数返回值得数据类型。
函数名:见明知意
形式参数列表:要实现功能所需要的参数,需要调用者传入(1.需要几个参数 2.每个参数需要什么类型)
函数体:具体实现的功能。
返回值:如果没有返回值,可以省略,不需要写return,数据类型void。如果有(有且只有一个)
9.2.2.2调用函数
函数名(实际参数列表)
注意:
- 需要将实参的值传递给形参,实参的个数和数据类型必须和形参一致
- 实参可以是变量,常量,表达式,但必须是一个确定的值。
- 实参和形参是两块独立的内存空间。
- 传参实际上是将实参的值拷贝给形参。
- 形参是局部变量,在函数调用的时候被定义,函数调用结束后,释放空间。
案例:
在调用时:
调用结束后:
9.2.3函数的声明
声明的一般形式:
类型标识符 函数名([类型名 形式参数 [,类型名 形式参数2] … …]);
如果函数没有在main函数之前,就需要在main函数之前进行声明
主调函数(main)在前,被调函数在后
声明:将函数头直接拷贝至main函数之前,然后加分号
未加声明之前:
加上声明之后:
对被调函数进行声明的目的是:为了使编译系统知道被调函数的函数返回值类型,函数名以及函数参数等基本信息,以便在主调函数中做相应的处理。
声明的作用:帮助编译器做语法检查。
如果在函数内部声明函数原型,那么从声明起
案例:封装函数实现+ - * /
- 输入
- 选择
- 调用模块
- 打印
10.指针
10.1什么是指针
指针是一种数据类型,是一种保存地址的数据类型
int a;//int:用来保存整型数的数据类型
char c;//char:用来保存字符的数据类型
float b;//float:用来保存小数的数据类型
指针 d;//指针:用来保存地址的数据类型
10.2什么是地址
内存分配的最小单位是字节,每一个字节都有一个编号,我们把这个编号叫做地址
地址的本质:内存单元的编号
指针:指针就是地址
指针的本质:内存单元的编号
10.3指针变量
int a;//整型变量
float b;//浮点型变量
char c://字符型变量
指针变量:专门用来保存地址(内存单元的编号)的变量
10.4指针的定义
存储类型 数据类型 *指针变量名;
int *p;
存储类型:auto,extern,static,register
数据类型:指针所指向的数据类型(去掉*和变量名,剩下的就是指针所指向的数据类型)
指针的数据类型:数据类型 *//int *
注意:
在所有32os,所有的指针占4个字节
在所有64os,所有的指针占8个字节
案例:
10.5赋值
char *p = &a;
char *p = NULL;
p = &a;
注意:对指针赋值时,一定要注意数据类型的匹配
思考?
- 什么是指针
是一种数据类型,保存地址的数据类型
- 地址是什么
字节(内存单元)的编号
- 什么是指针变量
专门用来保存地址(内存单元的编号)的变量
- 指针变量如何定义
存储类型 数据类型 *指针变量名
int *p;
- 指针变量赋值后能干什么
指针指向谁,就是保存谁的地址,赋值之后可以操作地址里面的元素。
10.6空指针
没有指向的指针(值为0的指针,就认为该指针没有指向)
注意:0号地址,禁止操作
要操作的话就改变指针指向
10.7野指针
不知道指向哪里的指针
局部变量没有初始化,其值是随机值
局部指针变量没有初始化,就是野指针
如何避免野指针的出现?初始化为NULL
int *p = NULL;
未初始化的指针很危险!!!!!!
11.值传递
案例:用子函数的形式,实现两个整数的交换。
12.地址传递
13.二级指针
13.1概念
指向指针的指针
二级指针的内存空间存放的是一级指针的地址
13.2定义
定义一级指针:存储类型 数据类型 * 指针变量名
数据类型 :指针指向的数据类型
存储类型:数据类型 * *指针变量名
总结:
- 指针的数据类型,去掉变量名剩下的就是
int *p;//int *
int **pp;//int **
int ***ppp;//int ***
- 指针指向的数据类型,去掉一个*和变量名就是所指向的数据类型
int *p;//int
int **pp;//int *
int ***ppp;//int **
- 指针所能访问的空间大小,由指针所指向的数据类型决定
(1)int a;
int *p = &a;//*p所能访问的空间大小是4个字节
(2)char b;
char *p = &b;//*p所能访问的空间大小是1个字节
(3) int*p;
int **pp = &p;//**pp所能访问的空间大小是4个字节
13.3 指针和一维数组
13.3.1指针的算数运算
总结:
p + n:p+n相当于p向地址增大的方向移动了n个数据。
实际的变化:p+sizeof(指向的数据类型)*n
p - n:p-n相当于p向地址减小的方向移动了n个数据。
实际的变化:p-sizeof(指向的数据类型)*n
p++ :p向地址增大的方向移动了一个数据。
p- - :p向地址增大的方向移动了一个数据。
p - q:(p和q的数据类型必须一致)这两个指针直接相隔的个数
实际的变化: (p - q)/sizeof(指向的数据类型)
注意:
-
指针的算术运算只有在操作连续的内存空间时才有意义。
-
p是指针变量,以上的方法也适用于常量,但是++, --除外。
(数组名:1.数组首元素地址(指针常量))
13.3.2通过指针常量来访问
数组名:指针常量,不能++,–
13.3.3通过指针变量来访问
访问数组:
总结:数组名和指针变量的本质区别:数组名是指针常量
案例1:
printf:右结合
案例2:
void input(int *p,int count)
14 冒泡排序
14.1指针指向没有发生变化
14.2指针指向发生改变
15.指针和二维数组
int a[2][3] = {0};
&a a &a[0] a[0] a[0][0]
他们的值相同但是意义不相同。
&a :a当作整个数组的首地址。
a : 代表首元素的地址。<---->a[0]的地址
&a[0] :二维数组首元素的地址。
a[0] :一维数组中首元素的地址,a[0] [0]的地址。
&a [0] [0] :第一个一维数组的第一个元素的地址。
a,&a[0],&a[0][0]
的值是一样的,但是意义不一样
a:int (*)[3]
:指向一维数组的指针 a[0]:int *
:指向一个整型变量 a[0][0]:int
类型
- 为什么a不是
int **
?
a+1移动了一个数组(12byte)如果是int **
,加1,移动4byte
- a指向
a[0],a[0]
又是一个一维数组,所以说a指向了一个一维数组
案例:
数组的输入,输出,排序,倒置,求最大值。(封装函数)一维数组
16.数组指针与指针数组
16.1数组指针
16.1.1概念
指向数组的指针,首先这个变量是一个指针,其次,”数组”修饰这个指针,意思是说这个指针存放着一个数组的首地址,或者说这个指针指向一个数组的首地址。
16.1.2定义
存储类型 数据类型 (*指针变量名)[元素个数];
存储类型:auto,register,static,extern
数据类型:数组指针指向的数组中元素的数据类型
指针的数据类型:数据类型 (*)[元素个数]
元素个数:指针所指向的数组中元素的个数
int a;//数据类型 int
int *p = NULL;//数据类型 int *
p = &a;//一个指向整型变量的指针
int a[5] = {0};//数据类型 int [5]
int (*p)[5] = NULL;//int (*)[5] 数组指针指向整个数组
p = &a;//a相当于整个数组
16.1.3数组指针与一维数组
注意:数组指针几乎不操作一维数组,更多的操作二维数组,因为指针访问连续的内存空间才有意义,如果是一维数组,p+1就会越界
16.1.4数组指针与二维数组
int a[2][3] = {0};
int (*p)[3] = NULL;(p=&a[0],&a[0]就是二维数组的数组名)
p = a;(&a[0])(a就是二维数组首元素地址)
//之前 int a[5];int *p = a; a,指向首元素的地址,定义了一个指针也指向首元素地址
//现在,首元素为一维数组,a指向首元素地址,首元素一维数组,a就是数组指针,
定义一个数组指针,也指向二维数组的首元素,首元素是一维数组
16.2指针数组
16.2.1概念
元素为指针的数组。首先这个变量是一个数组,其次,”指针”修饰这个数组,意思是说这个数组的所有元素都是指针类型,在32位系统中,指针占四个字节。
16.2.2定义
存储类型 数据类型 *数组名[元素个数];
数据类型 *:数组中元素的数据类型
int *arr[3];
定义了一个数组,数组名叫arr,有三个元素,每一个都是int *
16.2 指针数组与二维数组
17 const修饰的指针
const:只读
const用来修饰变量,修饰的变量只能读,不能写
案例:const是否在常量区
const修饰的指针:
总结:
-
const int *p = &a;//const修饰的是值,*p不能被改变
-
int *const p = &a;//const修饰的是地址,p不能被改变
-
const int *const p =&a;//const修饰值和地址,*p和p都不能被改
int const *p;
左值右址
const在*左边:*p不能被改变
const在*右边:p不能被改变