C数组与指针
# C运算符
# 赋值运算符
赋值运算符即=
号,用于将值存储到某个内存位置。赋值运算符的左边又称为左值,左值需要引用一个存储位置,引用存储位置的最常见方法就是变量名。赋值运算符的右边又称为右值,指需要存储的值。
用于存储值的数据存储区域统称为数据对象。而变量名是标识对象的一种方法。
例如:
// 2赋值给i变量
int i = 2;
// 链式赋值,结果是三者都等于5
int a, b, c;
a = b = c = 5;
// i + 1的结果赋值给i变量
int i = i + 1;
2
3
4
5
6
7
# 二元运算符
为二元运算符,即运算符需要两个运算对象才能完成操作。
// + 加法运算符
i = 1 + 1;
// - 减法运算符
i = 1 - 1;
// * 乘法运算符
i = 2 * 2;
// "/" 除法运算符
i = 2 / 2;
// % 求模运算符(取余)
i = 3 / 2;
// ++ 递增运算符,变量自增1,但区分前缀形式和后缀形式
// 后缀形式:会返回变量的当前值,然后再加1。
i = 1;
a = i++; // 该行执行完后,a为1、i为2
// 前缀形式:会先加1,然后再返回变量值。
i = 10;
a = ++i; // 该行执行完后,a为11、i为11
// -- 递减运算符,同递增,只不过是自减1。
i--;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
另外还有一元运算符(+、-),写在值前面,用来表示正负数,例如:-(2 - 1) = -1
# 表达式
表达式由运算符和运算对象组成。最简单的表达式是一个单独的运算对象,以此为基础可以建立复杂的表达式,最后每个表达式都有一个值。表达式例如:
4
-6
4+21
a*(b + c/d)/20
q = 5*2
x = ++q % 3
q > 3
2
3
4
5
6
7
运算对象可以是常量、变量或二者的组合。一些表达式由子表达式组成。
# 强制类型转换运算符
在需要进行精确的类型转换时,我们可以使用强制类型转换,来避免自动类型转换,尤其是类型降级。
使用强制类型转换只需要在某个量的前面放置用圆括号括起来的类型名即可,该类型名即是希望转换成的目标类型。圆括号和它括起来的类型名构成了强制类型转换运算符,其格式是:(目标数据类型)量
例如:
// 此表达式会将3转化float类型,所以表达式结果为9.0
(float)9
// 此处会先进行运算1.6 + 1.7 = 3.3,然后再将结果转化为int类型,所以最终结果为3
a = (int)(1.6 + 1.7);
// 此处会将1.6和1.7先转化int类型再进行运算,所以最终结果为1 + 1 = 2
a = (int)1.6 + (int)1.7;
2
3
4
5
6
# 关系运算符
关系运算符用于判断两个表达式之间的关系,返回值为1或0,条件成立返回1代表真,条件不成立返回0代表假。
所有的非零值都视为真,只有0被视为假。从C99开始提供专门用于保存真假的_Bool类型,只用来存储1(真)和0(假)。
<
小于
<=
小于或等于
==
等于
>=
大于或等于
>
大于
!=
不等于
# 逻辑运算符
&&
与
||
或
!
非
# 三元运算符
三元运算符是if else的一种便捷写法。
格式:条件表达式 ? 成立返回的表达式值 : 不成立返回的表达式值
例如:
// 此处执行完后a = 1
int a = 3 > 2 ? 1 : 0;
// 同等于如下
int a;
if(3 > 2){
a = 1;
} else {
a = 0;
}
2
3
4
5
6
7
8
9
# 其他赋值运算符
+=、-=、*=、/=、%=
这些表达式会将左测的值与运算符右侧的值进行对应二元符号的运算。
例如:
int i = 1;
// 此处同等于i = i * 2;
i *= 2;
// 同样的同等于i = i + i;
i += i;
2
3
4
5
# C函数
# 定义
函数是一组代码的集合,能够减少代码冗余,并方便我们对代码进行复用,较为规范的C语言函数定义需要在源代码开头声明函数原型,然后再去定义函数。
格式:
// 无返回值或无参数则指定void即可
返回值类型 函数名(形参类型 形参名1, 形参类型 形参名2...) {
代码块...
// 如果无返回值则写return;即可
return 返回值;
}
2
3
4
5
6
例如:
#include <stdio.h> // 导入标准输入输出库
void hello(void); // 声明函数原型
// 定义函数
void hello(void) {
printf("Hello, world!");
return;
}
int main(void) {
hello();
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
如果没有声明函数原型,则main函数需要放到它所调用的函数的下面,才能正常调用。
# 参数
在函数定义过程中声明的参数被称为形式参数,在函数调用过程中传入的参数被称为实际参数。
形式参数
形参在函数名后面的括号中写好形参定义即可。
例如:
void hello(int age, int score){
printf("you age is: %d, score is: %d", age, score);
}
2
3
实际参数
实际参数即调用函数时传入的参数,传入的参数数量需要和形参数量匹配,如果传入的实参和形参的数据类型不匹配,则会将实参转化为形参指定的类型。
例如:
int a = 18;
hello(a, 90);
2
# C循环
# 循环辅助
break;
关键字用于跳出当前循环。
continue;
关键字用于跳过本轮循环,直接进入下一轮循环。循环体代码执行到continue时,本次循环体剩余的代码都将被跳过。
# while循环
while循环用于根据条件判断循环执行某段代码,这段代码需要使用花括号括起来,花括号以及被花括号括起来的部分被称为块。 程序执行到while时会先进行判断,如果条件成立则会执行代码块,执行完再进行一次判断,如果仍然成立则继续循环,直到条件不成立或者在代码块中碰到break关键字为止。
格式:
while(条件表达式){
代码块...
}
2
3
例如:
int i = 1;
while(i < 10){
printf("i is: %d", i);
i++;
}
2
3
4
5
# for循环
C语言的for循环就是能将初始化表达式、条件表达式和更新表达式组合在一处的循环写法,在某些时候比while循环更简洁。另外for循环初始化表达式中声明的变量,仅作用于该循环体内,仅在该循环体内有效。
格式:
// 初始化表达式只会在开始循环前执行一次。可有多个,用逗号隔开。
// 循环条件表达式在开始和每次循环结束都会进行一次判断,成立则继续循环。
// 更新表达式会在每次循环代码块执行完的最后执行一次,一般用来进行自增或自减。可有多个,用逗号隔开。
for(初始化表,循环条件,更新){
代码块...
}
2
3
4
5
6
例如:
#include <stdio.h>
int main(void) {
for(int i = 0; i < 10; i++){
printf("%d\n", i);
}
for (int i = 0, n = 10; i < n; i++, n--) {
printf("%d %d\n", i, n);
}
return 0;
}
2
3
4
5
6
7
8
9
10
11
12
# do while循环
while和for循环都是入口条件循环,也就是在循环前会先判断条件是否要进入循环。而do while则是出口条件循环,先执行一遍循环体的代码,再判断条件是否要继续循环。
格式:
do{
代码块...
}while(条件表达式);
2
3
例如:
int i = 1;
do{
printf("i is: %d", i);
i++;
}while(i < 10);
2
3
4
5
# C流程控制
# if语句
if用于根据条件判断来执行哪些代码,可以进行单分支、双分支、多分支流程控制。要执行的代码可以是一条简单语句、或则是用花括号扩起的复合语句。
格式:
// 单分支,如果条件成立则执行
if(条件表达式){
代码块...
}
// 双分支,如果条件成立则执行代码块1,否则执行代码块2
if(条件表达式){
代码块1...
} else {
代码块2...
}
// 多分支,多分支同样可以使用else,也可以不使用
if(条件表达式1){
代码块1...
}else if(条件表达式2){
代码块2...
}else if(条件表达式3){
代码块3...
}else{
代码块4...
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
如果语句没有被花括号括起来,则else会与离它最近的if相匹配,所以为了提高代码可读写,建议都使用花括号。
# switch语句
switch用于根据给定的表达式的值(一般直接使用变量),匹配其值对应的代码块进行执行。
格式:
// 基本写法
switch(表达式){
// 如果表达式值为值1,则执行该代码块
case 值1:
代码块...
// 执行完后需要手动break,否则会继续向下判断,浪费性能
break;
// 也可以指定多个case来判断多个值
case 值2:
代码块...
break;
default:
// default会匹配所有值,它是可选的,一般用于在前面都不匹配时则执行该代码块
// 需要注意的是如果前面匹配的代码块最后没有使用break,则匹配到default时,还会执行一遍default的代码块
代码块...
}
// 多重标签写法
switch(表达式){
// 可指定多个标签进行匹配,此处表示匹配到值1或值2都执行该代码块
case 值1:
case 值2:
代码块...
break;
default:
代码块...
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
例如:
int choice;
printf("1.hello 2.what?");
scanf("%d", &choice);
switch(choice){
case 1:
printf("you choice hello");
break;
case 2:
case 3:
case 4:
printf("you choice what?");
break;
default:
printf("you choice is not 1 range 4!");
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# C指针
# 介绍
指针是一个值为内存地址的变量(或数据对象)。正如char类型变量的值是字符,int类型变量的值是整数,指针类型变量存储的值是地址。
# 地址运算符
&
是C语言中的取地址运算符。我们在变量名前面加上&
即可获取变量的内存地址,我们可以将该内存地址赋值给指针变量。
例如:ptr = &a;
# 间接运算符
*
是C语言中的间接运算符又称解引用运算符。是用来获取内存地址对应的值的,我们在指针变量前面加上*
即可获取指针所存储的内存地址的值。
可有多个解引用运算符,表示多次解引用,在多维数组中可能会用到。
例如:a = *ptr;
# 声明指针类型
声明指针变量时必须指定指针所指向变量的类型,因为不同的变量类型占用不同的存储空间,一些指针操作要求知道操作对象的大小。
另外,程序也必须知道存储在指定地址上的数据类型,比如long和float可能占用相同的存储空间,但它们存储的数字大相径庭,如果解引用时我们将本该是float类型的值存储到了long变量上,得到的结果可能就是错的。
格式:所指向数据的类型 * 变量名
将指针类型作为函数参数时,也是一样的格式。*和指针名之间的空格可有可无。通常我们在声明时使用空格,在解引用变量时省略空格。
另外还需要注意,虽然我们指定了所指向数据的类型,但我们声明的实际还是指针类型,无法进行所指向的数据类型的运算操作。
例如:
int num = 10;
int * numPtr = #
printf("%d %p", numPtr, *numPtr);
2
3
# 指针操作
赋值:可以把地址赋给指针。
例如:用数组名、带&运算符的变量名、另一个指针进行赋值。
int a = 10;
int * aptr = &a;
2
解引用:*运算符给出指针所指向地址上存储的值。
例如:
int a = 10;
int * aptr = &a;
printf("%d", *aptr);
2
3
取址:和所有变量一样,指针变量也有自己的地址和值。对指针而言,&运算符给出指针本身的地址。
int a = 10;
int * aptr = &a;
printf("%p", &aptr);
2
3
指针与整数相加相减:可以使用+运算符把指针与整数相加。整数会和指针所指向类型的大小(以字节为单位)相乘,然后把相乘的结果与初始地址相加。相减同理。
这种运算也被称之为偏移,在操作数组地址加一时可以得到下一个的元素的地址。如果相加的结果超出了初始指针指向的数组范围,计算结果则是未定义的。
例如:
int numArr[5] = {10, 20, 30, 40, 50};
int * arrOnePtr = &numArr[1];
// 此处arrOnePtr+1同等于numArr[2],因为数组是固定类型且连续的,元素1的地址加了一个元素类型大小后刚好就是元素2的地址
printf("%d", *(arrOnePtr + 1));
2
3
4
递增递减指针:参考指针与整数相加,只不过每次固定自增1个数据类型大小。自减同理。
例如:
int numArr[5] = { 10, 20, 30, 40, 50 };
int* arrOnePtr = &numArr[1];
// ++在前后不同的区别,在这里也适用
arrOnePtr++;
printf("%d", *arrOnePtr);
2
3
4
5
指针求差:可以计算两个指针的差值。求差一般用于计算同一数组的不同元素的指针之间的距离。差值的单位与数组类型的单位相同。
例如:
int numArr[5] = { 10, 20, 30, 40, 50 };
int* arrPtr1 = &numArr[0];
int* arrPtr2 = &numArr[3];
// 打印3,表示arrPtr1和arrPtr2的地址相差3个int类型大小
printf("%td", arrPtr2 - arrPtr1);
2
3
4
5
比较:使用关系运算符可以比较两个指针的值,前提是两个指针都指向相同类型的对象。
# 指针传递函数
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int function1(){
printf("I am function1\n");
return 0;
}
int function2() {
printf("I am function2\n");
return 0;
}
int main(void) {
char(*functions_n[]) = { "功能1", "功能2" };
int (*functions_p[])() = { function1, function2 };
int functionArrSize = sizeof(functions_n) / sizeof(functions_n[0]);
int choiceCode;
for (int i = 0; i < functionArrSize; i++) {
printf("%d.%s\n", i + 1, functions_n[i]);
}
printf("请输出想要执行的功能:");
if (scanf("%d", &choiceCode) && choiceCode > 0 && choiceCode <= functionArrSize) {
int res = functions_p[choiceCode - 1]();
printf("函数返回值:%d", res);
}
else {
printf("你输出了错误的编号");
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
# C数组
# 介绍
只存储单个值的变量有时也称为标量变量,而数组则是用于存储多个值的变量。
数组(array)由连续的存储单元组成,在C语言中用于存储固定大小的同类型元素,数组元素下标从0开始。
另外数组变量的值是数组本身的首指针。
# 声明数组
定义数组只需要在变量名后添加[]并指定数组大小,初始化则使用花括号将元素值扩起,多个元素值用逗号隔开即可。
格式:元素类型 数组名[数组长度] = {元素值1, 元素值2, ...};
数组长度必须是大于0的整数,可以通过变量传入。如果不指定数组长度,则编译器会自动将数组大小设为足够装得下初始化值的大小。
使用数组前必须先初始化它,且初始化的元素数量必须小于数组长度,否则会报错。如果在声明时有未初始化的元素,编译器会自动把它们的值设置为0。
指定下标的初始化格式:元素类型 数组名[数组长度] = {元素值1, [下标]=元素值2, ...};
# 给指定元素赋值
格式:数组名[下标] = 值;
# 函数中保护数组
如果函数函数并不需要修改数组数据,则应当在形参定义时,使用const限定符,表示传入的数组在函数内只读,例如:int sum(const int ar[])
。
# 复合字面量
复合字面量类似于数组,也是固定类型和长度的连续存储单元,只不过因为它是字面量所以是匿名的。
格式:(数据类型 [长度]){元素值1, 元素值2, ...}
因为复合字面量是匿名的,所以必须在创建的同时使用它。初始化有数组名的数组时可以省略数组大小,复合字面量也可以省略大小,编译器会自动计算数组当前的元素个数。
例如:
int * ptr = (int []){10, 20};
printf("%d %d", ptr[0], ptr[1]);
2
# 命令行参数
如果我们在运行程序时,需要给程序传入参数,就需要在main函数中指定两个参数。
第1个参数需要指定为int
整数类型,一般名称使用argc,表示参数数量。第2个参数需要指定为char *argv []
字符串数组类型,一般名称使用argv,存储传入的参数值。
argv的第1个参数固定为程序文件名本身,所以实际传入的参数数量为argc - 1。
例如:
#include <stdio.h>
int main(int argc, char *argv [])
{
int count;
printf("命令行有%d个参数:\n", argc - 1);
for (count = 1; count < argc; count++)
printf("%d: %s\n", count, argv[count]);
printf("\n");
return 0;
}
2
3
4
5
6
7
8
9
10
11