C语言
1.概述
C语言是一门通用计算机编程语言,广泛应用于底层开发。
C语言的设计目标是提供一种能以简易的方式编译、处理低级存储器、产生少量的机器码以及不需要任何运行环境支持便能运行的编程语言。尽管C语言提供了许多低级处理的功能,但仍然保持着良好跨平台的特性,以一个标准规格写出的C语言程序可在许多电脑平台上进行编译,甚至包含一些嵌入式处理器(单片机或称MCU)以及超级电脑等作业平台。
二十世纪八十年代,为了避免各开发厂商用的C语言语法产生差异,由美国国家标准局为C语言制定了一套完整的美国国家标准语法,称为ANSI C,作为C语言最初的标准。
[1] 目前2011年12月8日,国际标准化组织(ISO)和国际电工委员会(IEC)发布的C11标准是C语言的第三个官方标准,也是C语言的最新标准,该标准更好的支持了汉字函数名和汉字标识符,一定程度上实现了汉字编程。C语言是一门面向过程的计算机编程语言,与C++,Java等面向对象的编程语言有所不同。其编译器主要有Clang、GCC、WIN-TC、SUBLIME、MSVC、Turbo C等。
2.数据类型
-
char 字符型 1字节
-
short 短整型 2字节
-
int 整形 4字节
-
long 长整型 4字节
-
long long 更长的整形 8字节
-
float 单精度浮点型 4字节
-
double 双精度浮点型 8字节
3.变量
变量分为2种:局部变量 VS 全局变量
3.1变量的定义规则
3.2.变量的作用域以及生命周期
-
局部变量的作用域:变量所在的局部范围
-
局部变量的生命周期:局部变量的生命周期:在局部范围内,创建后生命开始,跳出范围后销毁。
-
全局变量的作用域:整个工程,需要声明才能使用。(使用extern关键字)
-
全局变量的生命周期:整个工程。
4.常量
C语言中的常量分为4种
- 字面常量
- (#define) 定义的标识符常量
- 枚举常量
4.字符串
字符串:由一对双引号引起来的内容
int main()
{
//字符串
"welcome to China";
return 0;
}
字符串的长度:\0为结束的标志,计算\0之前的字符但不包括\0。
tips:字符串数组结尾有\0,而字符数组没有。
#include <string.h>
#include <windows.h>
#include <stdio.h>
int main()
{
//字符数组
//\0:字符串的结束标志
char arr1[] = "xz";//字符串数组在结尾的位置隐藏了一个[\0]的字符(内存中)。
char arr2[] = {'x','z',};//字符数组
char arr3[] = {'x','z','\0'}; //字符数组,本身不包含\0,需手动添加。arr3=arr1
//打印字符串,
printf("%s\n",arr1);//xz
printf("%s\n",arr2);//xzxz 此输出值:在输出完arr2后输出其后的值直到寻找到结束标志(\0),arr1包含结束标志。
//stren(str):返回此字符串的长度,遇见\0结束计算,但不计算\0
//需要引用头文件#include <string.h>,否则会警告
int length = strlen("clearlove");//String length
printf("%d\n",length);//9
printf("%d\n",strlen(arr1));//2
printf("%d\n",strlen(arr2));//随机值,即寻找其后的结束标志
//这里是4的原因可能为arr2在内存空间中排在arr1的前面,在输出完arr2的值后往后寻找结束标志(\0),arr1含有(\0)
system("pause");
return 0;
}
5.转义字符
//打印一个磁盘地址?
int main()
{
printf("C:\mingw64\bin");//打印信息:C:mingw6in
printf("\n");
//转义字符:(\)
printf("C:\\mingw64\\bin");//C:\mingw64\bin
printf("are you undergraduate??)");// ??) --> ] =三字母词
//(are you undergraduate]-->高版本不会出现此情况
system("pause");
return 0;
}
int main()
{
//\ddd:8进制数字转为10进制数字
printf("%c\n",'\130');//字符X=88 ASCII码表X=88 8进制的130转为10进制=88
printf("%c\n",'\101');//字符A=65 65的8进制=101
//\dd:16进制数字转为10进制数字
printf("%c\n",'\x30');//字符0=45
char arr1[] ="C:\mingw64\bin\126";
printf("%d\n",strlen(arr1));//13
system("pause");
return 0;
}
6.注释
注释的两种方式:
- / 注释内容/(C++)
- //注释内容©
int main()
{
//打印一个Hello World
printf("Hello World");
/*
打印一个自定义的信息
printf("meiko");
tips:不能嵌套使用,出现第一个结束标志,即结束后面不再是注释内容
*/
printf("Clearlove");
}
7.关键字
关键字又称为保留字,就是已被C语言本身使用,不能作其它用途使用的字,关键字不能用作变量名、函数名等
关键字分类:
- 数据类型关键字(12个)
- 存储类型关键字(4个)
- 控制语句关键字(12个)
- 其他关键字
8.操作符
操作符分类:
- 算术操作符
- 移位操作符
- 位操作符
- 赋值操作符
- 单目操作符
- 关系操作符
- 逻辑操作符
- 条件操作符
- 逗号表达式
8.1算数操作符
int main()
{
/*
算数操作符 + - * / %
/:两个数都为整数,结果为整数,两个数其中有有一个数为浮点数,结果为浮点数。
%:两个操作数必须为整数。被取模数为负数时(左边的操作数),结果为负数。
*/
int minius = 5 - 6;
int sum = -1 + 8;
int a = 3 / 5;//0
int b = 6 / 5;//1
float c = 6 / 5;//1
float d = 6.0 / 5;//1.2
float e = 6 / 5.0; //1.2
int f = 7 % 3;
int g = -7 % 3;//-1
int h = 7 % -3;//1
// int g = 7 % 3.0;//错误写法
printf("minius = %d\n",minius);
printf("sum = %d\n",sum);
printf("a = %d\n",a);
printf("b = %d\n",b);
printf("c = %f\n",c);
printf("d = %f\n",d);
printf("e = %f\n",e);
printf("f = %d\n",f);
printf("g = %d\n",g);
printf("h = %d\n",h);
system("pause");
return 0;
}
8.2移位操作符
/*
移位操作符
<< 左移操作符:移动几位,则左边丢弃几位0,后边补0
>> 右移操作符:移动几位,右边丢弃几位,左边补0
右移操作符分位两种计算方式:
1.算术右移,左边补原符号位
2.逻辑右移,左边补0
tips:这里使用的算术右移。以-1举例
注:移位操作符的操作数只能是整数。
*/
int main()
{
/*
将a的二进制位,2的二进制整体向左移动一位,余位补0
int a = 2 -> 00000000 00000000 00000000 00000010
int b = 4 -> 00000000 00000000 00000000 00000100
*/
int a = 2;
int b = a << 1; // 4
printf("b = %d\n", b);
/*
将c的二进制位也就是8的二进制整体向右移动2位,前方补0,后面的两位丢弃。
int c = 00000000 00000000 00000000 00001000 = 8
int d = 00000000 00000000 00000000 00000010 = 2
*/
int c = 8;
int d = c >> 2;
printf("d= %d\n", d); // 2
// c向右移2位
// e的二进制 = 0000000 00000000 00000000 00010110 = 22
// 22右移2位后 = 0000000 00000000 00000000 00000101 = 5
int e = 22 >> 2;
printf("e= %d\n", e); // 5
/*
负数: -1
负数存放在内存中是二进制的补码
整数的二进制表示形式:3种
原码:直接根据数值写出的二进制序列就是原码
反码:原码的符号位不变,其他位按位取反就是反码
补码:反码+1就是补码
正数(原码、反码、补码)简称三码合一
-1的原码:10000000 00000000 00000000 00000001
-1的反码:11111111 11111111 11111111 11111110
-1的补码:11111111 11111111 11111111 11111111
*/
int f = -1;
int g = -1 >> 1;//算术右移,若使用逻辑位移则会变成较大的正数。
int h = -1 << 2;//如若是逻辑位移结果应该为4,这里使用左移结果为-4,依旧使用的算术位移。
printf("g= %d\n", g); // -1
printf("h= %d\n", h); // -4
system("pause");
return 0;
}
8.3位操作符
/*
位操作符
1. & 按位与
2. | 按位或
3. ^ 按位异或
注:他们的操作数必须是整数。
*/
int main(){
int n = 3; //00000011
int m = 5; //00000101
int nm = n & m; //00000001
int a = 2;
int b = 5;
int c = a & b;//0
// & 按位与 有0则0 全1则1
// int a = 2 = 00000000 00000000 00000000 00000010
// int b = 5 = 00000000 00000000 00000000 00000101
// int c = 2 & 5 = 00000000 00000000 00000000 00000000
printf("nm = %d\n",nm);//1
printf("c = %d\n",c);//0
// | 按位或 有1则1,全0则0
int e =5;//00000101
int f =9;//00001001
int ef = 5 | 9 ;//00001101
printf("ef = %d\n",ef);//13
// ^ 按位异或 相同为0,不同为1
int g = 5;// 00000101 = 5
int h = 9;// 00001001 = 9
int i = 12;// 00001100 = 12
int gi = g ^ i;// 00001001 = 9
int gh = g ^ h;// 00001100 = 12
int hi = h ^ i;// 00000101 = 5
printf("gi = %d\n",gi);//9
printf("gh = %d\n",gh);//12
printf("hi = %d\n",hi);//5
system("pause");
return 0;
}
8.4赋值操作符
int main()
{
/*
赋值操作符
+= -= /= *= %=
>>= <<=
&= ^= |=
语法规则一样,只是添加了一个赋值操作
tips:一个等号(=)是赋值操作,两个等号(==)判断操作。
*/
int a = 10;
a = 100;
a = a+100;
a += 100;
//这里不举例,简单的赋值操作
printf("a = %d\n",a);
system("pause");
return 0;
}
8.4单目操作符
int main()
{
/*
单目操作符
! -> 逻辑取反操作
- -> 负数
+ -> 正数
& -> 取地址
sizeof -> 操作数的类型所占空间的大小(以字节为单位)
~ -> 对一个二进制按位取反
-- -> 前置、后置减减
++ -> 前置、后置加加
* -> 间接访问操作符(解引用操作符)
(类型) -> 强制类型转换
*/
// ! 逻辑取反 真->假 假->真
// 非0为真,0为假
int symbol = 0;
// symbol为0,则打印假,否则打印symbol的值
if (!symbol)
{
printf("symbol=0,假\n");
}
else
{
printf("symbol = %d\n", symbol);
}
int a = 17;
a = -a;
printf("a = %d\n", a); //-17
// sizeof
char arr[10] = {0};
int intArr[10] = {0};
printf("%d\n", sizeof(a)); // 4
printf("%d\n", sizeof(int)); // 4
// sizeof 计算变量的时候可以省略括号,一般不省略 证明sizeof是一个操作符
printf("%d\n", sizeof a); // 4
printf("%d\n", sizeof(arr)); // 10
printf("%d\n", sizeof(intArr)); // 40
//-1的原码:10000000 00000000 00000000 00000001
//-1的反码:11111111 11111111 11111111 11111110
//-1的补码:11111111 11111111 11111111 11111111
int b = -1;
// ~ 对一个数的二进制位进行按位取反
// 00000000 00000000 00000000 00000000
int newB = ~b; // 0
printf("b = %d\n", b);
printf("\n");
printf("\n");
// -- ++
int m = 6;
int m1 = m++; // 后++ 先使用(赋值),后计算
printf("m = %d\n", m); // 7
printf("m1 = %d\n", m1); // 6
printf("\n");
int newm = 25;
int m2 = ++newm; // 前++ 先计算,后使用(赋值)
printf("newm = %d\n", newm); // 26
printf("m2 = %d\n", m2); // 26
printf("\n");
int n = 20;
int n1 = n--; // 后-- 先使用(赋值),后计算
printf("n = %d\n", n); // 19
printf("n1 = %d\n", n1); // 20
printf("\n");
int newn = 88;
int n2 = --newn; // 前-- 先计算,后使用(赋值)
printf("newn = %d\n", newn); // 87
printf("n2 = %d\n", n2); // 87
int a 10;
&a;//& 取地址操作符
printf("%p",&a);//
int *pa = &;//pa 用来存放地址的 - pa就是一个指针变量
*pa = 20;// * ->解引用操作符 ->间接引用操作符
printf("%d\n",a);//20
system("pause");
return 0;
}
8.5关系、逻辑、条件(三目)操作符
#include <stdio.h>
#include <windows.h>
#include <String.h>
int main()
{
// 强制类型转换
int imposeA = (int)3.14;
/*
关系操作符
> < <= >= != ==
==不能用来两个字符串
*/
int n = 10;
int m = 20;
if (n != m)
{
printf("欢迎来到武汉!\n");
}
if (n == m)
{
printf("欢迎来到湖北\n");
}
/*
逻辑操作符
逻辑与 && 一假则假,全真则真
逻辑或 || 全假则假,一真则真
短路与 && 第一个表达式为假,则短路,即后面表达式不会运算;第一个表达式为真,继续后面表达式的运算,全真则真,碰见假则短路
短路或 || 第一个表达式为假,继续后面的运算,直到碰见真表达式,则短路,整个表达式为真,其后的表达式不会运算。
*/
int x = 0, y = 1,z = 2;
// x = x++ && y++ && z++;
x = x++ || y++ || z++;
printf("x = %d\n", x);
printf("y = %d\n", y);
printf("z = %d\n", z);
printf("\n\n");
/*
三目操作符
x = exp1? exp2:exp3;
exp1表达式为真,则将exp2表达式的结果赋值给x,否则将exp3表达式的结果赋值给x
tips:exp1表达式为真,exp2计算,exp3不计算,同理,exp1表达式为假,exp2不计算,只计算exp3。
*/
int three = (n >5 ? 1 : -1);
/*
逗号表达式
*/
int a = 3;
int b = 5;
int c = 0;
//逗号表达式 -> 从左往右依次计算,整个表达式的结果是最后一次表达式(最后一次计算)的结果
int d = (c = 5,a = c + 3,b = a - 4,a +=5);
printf("d = %d \n",d);
system("pause");
return 0;
}
8.6其他的细节问题
#include <stdio.h>
#include <windows.h>
#include <String.h>
int main()
{
/*
隐式类型转换
C的整型算术运算总是至少以缺省整型类型的精度来进行的。
为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型
提升。
整型提升的意义:
表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度
一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长
度。
通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令
中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转
换为int或unsigned int,然后才能送入CPU去执行运算。
char a, b, c;
a = b + c;// b 和 c 升级为整形参与运算,结果被截断,赋值给a
*/
char a =3;
//00000000 0000000 0000000 0000011
//00000011 a
char b =127;
//00000000 00000000 00000000 01111111
//01111111 b
char c = a+b;
//00000000 00000000 00000000 00000011
//00000000 00000000 00000000 01111111
//00000000 00000000 00000000 10000010
//10000010 -c
//11111111 11111111 11111111 10000010 -补码
//11111111 11111111 11111111 10000001 -反码
//10000000 00000000 00000000 01111100 -原码
// -126
//发现a 和 b都是char类型,都没有达到一个int的大小
//这里会发生整型提升
printf("%d\n",c);//-126
/*
复杂表达式的求值有三个影响的因素。
1. 操作符的优先级
2. 操作符的结合性
3. 是否控制求值顺序。
这里优先级的问题百度即可,实际应用中碰见就查阅资料
*/
int a = 4;
int b = 5;
int c = a + b*7;//优先级决定了计算顺序
int c = a + b + 7;//优先级不起作用,结合性决定了顺序
system("pause");
return 0;
}
9.分支与循环语句
C语言是结构化的程序设计语言
- 顺序结构
- 选择结构
- 循环结构
什么是语句? -> C语言中由一个(;)隔开的就是一条语句
int a =10;//一条语句
int b =20;//
printf("这是一条语句");
9.1.分支语句
- if
- switch
9.1.1 if语句
//if语句语法结构(第一种)
//C语言中, 非0为真,0为假
if(表达式){
//表达式为真则执行语句1
语句1;
}else{
//表达式为假则执行语句2
语句2;
}
//if语句语法结构(第二种)
if(表达式1){
//表达式1为真,则执行语句1
语句1;
}else if(表达式2){
//表达式2为真,则执行语句2
语句2;
}else{
//表达式1为假,表达式也为假,则执行语句3
语句3;
}
//总结:
1.有大括号的时候 条件满足的情况执行所有括号内语句
2.无大括号的时候 条件满足默认只能控制一条语句,其后语句也可执行。
3.else在无括号时,会匹配最近的if语句。
//if语法结构(第一种)
int age = 20;
if(age >= 18)
printf("成年\n");
else
printf("未成年\n");
printf("具有更强的法律保护力度\n");
//语法符合规则,逻辑出现错误
//(18 <= age) 为真,结果为1 [(18 <= age)=1 < 26]为真,不单独计算age
int age1 = 60;
if(age1<18){
printf("少年\n");
}else if(18 <= age1 < 26){
printf("青年\n");
}
//if语法结构(第二种)
int score = 85;
if(score == 100){
printf("最优秀的学生\n");
}else if(score >= 90 && score < 100){
printf("成绩良好\n");
}else if(score >= 80 && score <90){
printf("成绩优秀\n");
}else if(score >= 60 && score <80){
printf("成绩一般\n");
}else if(score >= 40 && score <60){
printf("加油\n");
}else{
printf("哥们你没睡醒吧\n");
}
//代码不规范
//悬空else:else匹配最近的if语句
//最终执行结果是:无任何输出结果
int main()
{
int a = 0;
int b = 2;
if(a == 1)
if(b == 2)
printf("hehe\n");
else
printf("haha\n");
}
//真实结构
if(a == 1)
if(b == 2)
printf("hehe\n");
else
printf("haha\n");
else
无执行语句;//无任何输出结果
9.1.2 switch语句
/*
//switch语句语法结构
//switch在不使用break时,会在匹配case后执行其后所有的case语句,包括default语句。
//break关键字:结束循环体,用于结束循环
//default语句:
在使用break的情况下,在switch语句中所有的case都不匹配时,执行此语句,否则不执行。位置不受限制。
switch(整形表达式)
{
case 整形常量表达式:
执行语句;
case ...:
...
........
default:
执行语句;
}
*/
int day = 0;
scanf("%d",&day);
switch(day)
{
case 1:printf("星期一\n");
case 2:printf("星期二\n");
case 3:printf("星期三\n");
case 4:printf("星期四\n");
case 5:printf("星期五\n");
case 6:printf("星期六\n");
case 7:printf("星期天\n");
default:printf("我忘记今天是星期几了\n");
}
int day = 0;
scanf("%d",&day);
switch(day)
{
case 1:printf("星期一\n");
break;
case 2:printf("星期二\n");
break;
case 3:printf("星期三\n");
break;
case 4:printf("星期四\n");
break;
case 5:printf("星期五\n");
break;
case 6:printf("星期六\n");
break;
case 7: printf("星期天\n");
break;
default:printf("我问问其他人\n");
}
9.2.循环语句
- while
- for
- do while
9.2.1 while循环语句
int main()
{
//while
//EOF:end of file 文件结束标志 = -1
int i = 1;
while(i <= 10)
{
// //break:结束整个循环体
// if(i == 5){
// printf("break关键字\n");
// break;
// }
//打印结果:1 2 3 4
//continue:跳过过本次循环,其后的代码不执行,进入下一次循环判定。
if(i == 5){
//电脑在发烧
printf("continue关键字\n");
continue;
}
//i一直为5,死循环
//打印结果:1 2 3 4
printf("%d ",i);
i++;
}
system("pause");
return 0;
}
9.2.2 for循环
/*
for(表达式1;判断表达式2;表达式3){
循环语句4;
}
表达式可省略,省略代表恒成立
执行顺序:1243 243 243 243 | 当判断表达式不成立,循环结束
*/
int main()
{
for(int i = 1;i<=10;i++){
printf("%d ",i);
}
int i = 0;
int k = 0;
//0为假,非0为真
for(i = 0,k = 0;k = 0;i++,k++){
k++;
}
system("pause");
return 0;
}
9.2.3 do while循环
/*
do while循环语法结构:
do
{
循环语句;
}while(判断条件表达式);
特点:循环体至少执行一次
*/
int main()
{
int i = 1;
do
{
printf("%d " ,i);
i++;
}while(i <= 10);
printf("\n");
//计算n的阶乘
int sum = 1,n = 0;
scanf("%d",&n);
for(int i = 1;i <= n;i++){
sum *= i;
}
printf("%d\n",sum);
system("pause");
return 0;
}
9.3 goto语句
#include <stdio.h>
#include <windows.h>
#include <string.h>
int main()
{
/*
goto语句:调整程序的执行顺序。
一般不使用goto语句,goto语句的实际应用场景
goto语句的应用场景:break只能跳出当前循环。goto可以直接跳出整个嵌套循环体
for(...)
for(...)
for(...)
if(....)
goto wrong;
wrong:
printf("跳出嵌套循环\n");
*/
flag:
printf("艾斯、萨博、路飞\n");
goto flag;//死循环
system("pause");
return 0;
}
int main()
{
char input[20] = {0};
system("shutdown -s -t 60");//system - 引入头文件-stdlib.h
again:
printf("请输入:艾斯,否则你的电脑将在1分钟后关机!\n");
scanf("%s",input);
//strcmp():比较两个字符串的内容是否一样,相等返回1
//两个字符串不可直接使用==进行比较,应该使用strcmp() string compare
//错误写法:if("艾斯" == input)
if(strcmp(input,"艾斯") == 0)
{
system("shutdown -a");
}else{
goto again;
}
system("pause");
return 0;
}
10.函数
官方库函数的使用:www.cplusplus.com;
10.1自定义函数
/*
自定义函数语法格式:
返回值类型 函数名(参数列表)
{
函数体;
}
eg:返回两个数中的最大值
int get_max(int x,int y)
{
return (x>y)?(x):(y);
}
int:返回值类型
get_max:函数名
int x,int y:参数列表
{ content }:函数体,content代表函数体里的内容。
*/
//函数的定义(自定义函数)
//返回两个数中的最大值
int get_max(int x,int y)
{
return (x>y)?(x):(y);
}
//返回两个数中的最大值
int maxNum(int x,int y)
{
if(x > y)
{
return x;
}
return y;
}
/*
形参和实参
实参:实际传递的参数,是一个具体的值,即实参可以是常量、变量、表达式、带返回值的函数等等。
形参:形式参数,定义函数时,括号里的参数称为形参
C语言——值传递机制:除了数组以外,所有的类型作为实参传递都是值传递。
*/
int main()
{
int a = 17;
int b = 23;
printf("交换前:a = %d b = %d \n",a,b);
swap(a,b);//17,23
printf("交换后:a = %d b = %d \n",a,b);
printf("\n");
printf("交换前:a = %d b = %d \n",a,b);
swap_new(&a,&b);//23,17
printf("交换后:a = %d b = %d \n",a,b);
system("pause");
return 0;
}
// //实际的底层操作
// int main()
// {
// int a = 17;//内存中开辟了4个字节的空间
// //&:地址符
// int* pa=&a;//取a的地址赋值给数据类型为int的指针变量pa;
// //*:解引用操作
// *pa=20;//通过pa指向的地址,得到a的值,赋值20给a.
// printf("%d\n",a);
// system("pause");
// return 0;
// }
//交换两个数的值(错误写法) -->值传递机制
void swap(int x, int y){
//int a,b在内存中开辟了空间,这里函数体的形参也开辟了空间,
//以下的赋值操作仅对x,y影响,而不影响实参a,b
//这里存在的问题是,交换的是形参的值,没有影响实参。
int z = 0;
z = x;
x = y;
y = z;
}
//交换两个数的值。
void swap_new(int* pa,int* pb){
int z = 0;
z = *pa;
*pa = *pb;
*pb = z;
}
11.数组
1.一维数组声明与初始化
#include <stdio.h>
#include <windows.h>
#include <String.h>
/*
数组:相同元素的集合
数组的语法格式:
数据类型 数组名(变量名)[数组长度]
*/
int main()
{
//数组的声明
int arr1[9];
char ch[5];
//tips:[]一般情况下是常量表达式,根据版本可填变量。
//数组的初始化。
int b;//变量的声明
b = 10;//变量的初始化
int a = 10;//变量的声明与初始化
int arr2[10] = {1,2,3,4,5,6,7,8,9,10};//完全初始化
int arr3[10] = {1,2,3,4,5};//不完全初始化。元素个数小于数组长度
int arr4[] = {1,2,3,4,5};//根据初始化的内容确定数组长度。
//字符数组
//tips: "\0" 字符数组结束的标志。
char ch1[9] ={'c','l','e','a','r','l','o','v','e'};
char ch2[] ={'x','z'};
char ch3[] ="clearlove";
char newch3[] ="clearlove";
char ch4[] = "xz";
char str1[5] = "wuhan";//w u h a n
char str2[7] = "wuhan";//w u h a n \0 0
int final = 0;
for(int i = 0;i<5;i++){
final = str1[i];
}
int last = 0;
int lastBefore=17;
for(int i = 0;i<7;i++){
if(i == 6)
{
lastBefore = str2[i];
}
last = str2[i];
}
//字符数组长度 vs 字符串数组的长度
//tips:ch1这个数组长度是9,但是求字符串长度
printf("%d\n",strlen(ch1));//随机值
printf("%d\n",strlen(ch3));//9
printf("%d\n",strlen(ch2));//随机值
printf("%d\n",strlen(ch4));//2
printf("%d\n",final);//110 ASCII n -> 110
printf("%d\n",last);//0
printf("%d\n",lastBefore);//0
system( "pause");
return 0;
}
2.一维数组的访问
#include <stdio.h>
#include <windows.h>
#include <String.h>
/*
数组的访问
数组是通过索引(下标)找个数组中的元素,其中索引是从0开始
访问索引(下标)的操作符: []
*/
int main()
{
int arr[10] = {0};//初始化第一个元素为0,其余默认初始化为0.
//访问数组的元素的操作符: []
int i = 0;
arr[4] = 5;
printf("%d\n",sizeof(arr));//40
printf("%d\n",sizeof(arr[0]));//4
//数组的总字节数 / 一个字节的所占用的字节数 = 字节的个数(数组的长度)
int size = sizeof(arr) / sizeof(arr[0]);//40/4 = 10
for(i = 0;i<size;i++)
{
printf("%d ",arr[i]);
}
system("pause");
return 0;
}
3.一维数组的存储
#include <stdio.h>
#include <windows.h>
#include <String.h>
/*
一维数组在内存中的存储
1.一维数组在内存中是连续存储的
2.随着索引增大,地址是由低变高的变化的。
*/
int main()
{
int arr[10] = {0};
int i = 0 ;
for(i;i<10;i++)
{
// %P -> 按照地址的格式(16进制)打印
printf("&arr[%d] = %p\n",i,&arr[i]);
}
system("pause");
return 0;
}
// int main()
// {
// int arr[10] = {1,2,3,4,5,6,7,8,9,10};
// arr;//数组名是数组首元素的地址
// int *p = arr;
// int i = 0;
// for(i;i<10;i++){
// printf("%d\n ",*p);
// p++;
// }
// system("pause");
// return 0;
// }
4.二维数组声明与初始化
#include <stdio.h>
#include <windows.h>
#include <String.h>
/*
二维数组
二维数组的创建(声明)与初始化
*/
int main()
{
//二维数组的创建(声明)
int orginal[3][4];
char arr1[3][5];
double arr2[4][5];
//二维数组的初始化
//第一种方式
int arr3[][4]={1,2,3,4,5,6,7,8,9,10,11,12,13,14};
int arr4[3][4]={1,2,3,4,5,6,7,8,9};//不完全初始化 -补0 字符数组 ->补\0
int gengerallyArr[3][4]={{1,2,3,4},{5,6,7,8},{9,10,11,12}};
//不可省略第二个括号里的长度
int arr5[][4] = {1,2,3,4,5,6,7,8,9,10,11,12};
system("pause");
return 0;
}
5.二维数组的访问与存储
#include <stdio.h>
#include <windows.h>
#include <String.h>
//冒泡排序
void bubble_sort(int arr[],int size)
{
int i = 0;
for(i;i<size - 1;i++)
{
int j = 0;
for(j = 0;j<size-1-i;j++)
{
if(arr[j] > arr[j+1])
{
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
int main()
{
//二维数组的初始化
//第一种方式
int arr3[][4]={1,2,3,4,5,6,7,8,9,10,11,12,13,14};
int arr4[3][4]={1,2,3,4,5,6,7,8,9};//不完全初始化 -补0 字符数组 ->补\0
//打印二维数组的元素
for(int i = 0; i< 3;i++){
for(int j = 0;j<4;j++){
printf("%d ",arr3[i][j]);
}
printf("\n");
}
printf("\n");
/*
二维数组在内存中也是连续存储
*/
//打印二维数组在内存中的存储
for(int i = 0; i< 3;i++){
for(int j = 0;j<4;j++){
printf("&arr3[%d][%d] = %p ",i,j,&arr3[i][j]);
}
printf("\n");
}
printf("\n");
int *p = &arr3[0][0];
for(int i = 0;i<12;i++)
{
printf("%d ",*p);
p++;
}
printf("\n");
int arr[]={10,6,7,8,12,9,3,2,1};
//计算数组元素的个输
int size = sizeof(arr) / sizeof(arr[0]);
bubble_sort(arr,size);//数组传递参数,传递的其实是首元素的地址。
for(int i = 0;i<9;i++){
printf("%d ",arr[i]);
}
printf("\n");
/*
数组名是什么?
数组名是数组首元素的地址
例外:
1.sizeof(数组名) -> 数组名表示整个数组 -> 计算的是整个数组的大小,单位是字节
2.&数组名 -> 数组名表示整个数组 ->取出的是整个数组的地址
数组的地址与数组首元素的地址地址一样但是意义不一样。
一个代表整个数组,一个代表单个元素。
*/
int arr10[10] = {0};
int sz = sizeof(arr10);
printf("%d\n",sz);
printf("%p\n",&arr[0]);
printf("%p\n",arr);//数组名是首元素的地址
system("pause");
return 0;
}
12.指针
内存是电脑上特别重要的存储器,计算机中程序的运行都是在内存中进行的 。
所以为了有效的使用内存,就把内存划分成一个个小的内存单元,每个内存单元的大小是1个字节。
为了能够有效的访问到内存的每个单元,就给内存单元进行了编号,这些编号被称为该内存单元的地
址。
1比特(bit)=0.125字节(Byte),1B(byte)就是1个字节;
Bit、KB、Byte、MB、GB之间的关系是:
Bit——比特 计算机内存的基本单位
Byte ——字节 1 Byte = 8 bit
KB——千字节 1 KB = 1024 B·
MB——兆字节 1 MB = 1024 KB
GB——吉字节 1 GM = 1024 MB
1.初识指针
int main()
{
//指针:
int a =10;//a在内存中要分配的空间 -4个字节
// &a;//取出 a 的地址,取第一个字节的地址。
printf("%p\n", &a);// %p:专门打印地址值
// * 说明 pa是指针变量
// 如何确定指针变量的类型?被取地址的变量的类型就是指针变量的类型
//int 说明pa执行的对象是int类型的
int* pa=&a;//pa用来存放地址值,在c语言中叫指针变量
printf("%p\n", pa);// %p:专门打印地址值
char ch = 'x';
char* pc = &ch;
printf("%p\n", pc);// %p:专门打印地址值
//指针变量的使用
*pa = 20;//* 解引用操作 *pa:通过pa指向的地址,找到变量a,修改a中的内容。
printf("%d\n",a);//20
//指针变量的大小
//指针变量是用来存储地址的,指针变量的大小,取决于地址的存储的多少
//32位机器,一个地址就是32位的二进制序列组成。同理.64位机器=64位二进制序列
//32位 -32bit的二进制序列作为地址 = 4byte 32位机器上,指针变量大小=4字节
//64位 -64bit的二进制序列作为地址 = 8byte 64位机器上,指针变量大小=8字节
printf("%d\n",sizeof(char*));//4字节
printf("%d\n",sizeof(short*));//4
printf("%d\n",sizeof(int*));//4
printf("%d\n",sizeof(long*));//4
printf("%d\n",sizeof(long long*));//4
printf("%d\n",sizeof(float*));//4
printf("%d\n",sizeof(double*));//4
system("pause");
return 0;
}
2.指针类型
#include <stdio.h>
#include <windows.h>
#include <String.h>
int main()
{
/*
char 1字节
short 2字节
int 4字节
float 4字节
double 8字节
.......
指针、指针类型
1.指针类型决定了,指针变量解引用后操作的范围的大小(根据数据类型的存储的大小而定)
2.指针类型决定了,指针变量运算的范围是其指针变量的类型决定(根据数据类型的存储的大小而定)
*/
int arr[10] = {0};
int *p =arr;
char *pc = arr;//
printf("%p\n",p);
printf("%p\n",p+1);//int类型的指针+1,会跳过4字节(int类型的变量在内存中占用的大小)
printf("\n");
printf("%p\n",pc);//
printf("%p\n",pc+1);//char类型的指针+1,就会跳过1个字节(char类型的变量在内存中占用的大小)
system("pause");
return 0;
}
//tips:效果图最好使用查看内存的方式更直接
3.野指针
#include <stdio.h>
#include <windows.h>
#include <String.h>
int main()
{
/*
野指针
概念:野指针指针指向的位置是未知的(随机的、错误的、没有限制)
*/
// p -> 野指针
int *p ; // p 是一个局部的指针变量,局部变量未被初始化,默认是随机值
*p = 20; // 非法访问内存
int arr[10] = {0};
int *p = arr;
int i = 0;
for (int i = 0; i <= 10; i++)
{
//当指针指向的范围超出数组arr的范围时,即下标越界,那么p是一个野指针。
*p = i++;
p++;
}
system("pause");
return 0;
}
#include <stdio.h>
#include <windows.h>
#include <String.h>
/*
如何避免野指针
1.指针初始化
2.小心指针越界
3.指针指向空间释放及时置null;
4.指针使用之前检查有效性
*/
int main()
{
// 当不知道p指针不知道初始化为什么地址的时候,直接初始化为 NULL(注意大写。) (相当于0)
int *p = NULL; // 可以查看定义:#define NULL ((void *)0) 即将0转化为指针类型,含义为0
// 明确知道初始化的值
int a = 10;
int *pa = &a;
// c语言本身不会检查数组的越界行为
int *m = NULL; // 空指针(置NULL),不可使用
*m = 10;//非法访问,报错
// 检查指针的有效性
if (p != NULL)
{
*p = 10;
}
system("pause");
return 0;
}
4.指针运算
#include <stdio.h>
#include <windows.h>
#include <String.h>
/*
指针的运算
1.指针 +- 整数
2.指针 - 指针
3.指针的关系运算(比较大小)
*/
#define N_VALUES 5
int main()
{
float values[N_VALUES];
float *vp;
// 指针+-整数;指针的关系运算
for (vp = &values[0]; vp < &values[N_VALUES];)
{
// *(表达式) 代表一个整体
// vp++ = 上方括号里的表达式
*vp++ = 0;
}
/*
前提:两个指针指向同一块空间
指针 - 指针 = 两个指针之间的元素个数
*/
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
char c[5];
printf("%d\n",&arr[9] - &c[0]);// error ->
?
printf("%d\n",&arr[9] - &arr[0]);//9
printf("\n");
system("pause");
return 0;
}
5.指针与数组
#include <stdio.h>
#include <windows.h>
#include <String.h>
/*
指针与数组
数组名是什么?
数组名是数组首元素的地址
在某特定情况下会改变
*/
int main()
{
int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
// 数组名是数组首元素的地址
// 理解:因为数组是连续存储,根据数据类型进行叠加,所以有个数组的首元素的地址就能找到数组所有元素的地址.
printf("%p\n", &arr); // 000000000061FDF0
printf("%p\n", &arr[0]); // 000000000061FDF0
printf("\n");
int *p = arr;
int i = 0;
for (int i = 0; i < 10; i++)
{
// p+i 就是数组下标i的地址,*(p+i)就是数字下标(p+i)所代su表的元素,将i赋值给下标所在的元素
*(p + i) = i;
printf("%p <==> %p\n", &arr[i], p + i);
}
printf("\n");
for (i = 0; i < 10; i++)
{
printf("%d ", *(p + i));
}
system("pause");
return 0;
}
int main()
{
int arr[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int *p = arr; // 数组名
// [] 是一个操作符, arr和2是两个操作数
printf("%d\n", 2 [arr]); // 3
// arr[2] 编译为--> *(arr+2) --> *(2+arr) -->2[arr]
printf("%d\n", arr[2]); // 3
// p[2] --> *(p+2)
printf("%d\n", p[2]); // 3
// arr[2] <=> *(arr+2) <=> *(p+2) <=> *(2+p) <=> *(2+arr) == 2[arr]
// 2[arr] <=> *(2+arr)
system("pause");
return 0;
}
7.二级指针
#include <stdio.h>
#include <windows.h>
#include <String.h>
/*
二级指针
指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里?
这就是 二级指针 。
*/
int main()
{
int a = 10;
int *pa = &a; // pa是指针变量,一级指针
&pa; // pa是个变量,&pa取出pa在内存中的起始地址
/*
ppa是一个二级指针变量
int* *ppa的含义是:
靠近ppa的那颗星(*ppa)的含义是:ppa是一个指针,指向pa
int*的含义是:pa的类型整体是int *,所以结合起来就是 int* *ppa = &pa;
取一继指针变量pa的地址(&pa)存放到二级指针变量ppa中的书写格式 *ppa = &pa;
因为一级指针变量pa的类型是int * 所以整体格式为 int* *ppa = &pa;
*ppa = pa
*pa = a
**ppa = a
tips:两个星中间有无空格,靠近哪边 根据个人习惯而定,无特定要求
*/
int **ppa =&pa;
printf("%p\n",*ppa);//pa在内存中的地址值
printf("%d\n",**ppa);//a的值
**ppa = 30;//对a的值进行操作
printf("%d\n",**ppa);//30
system("pause");
return 0;
}
8.指针数组
#include <stdio.h>
#include <windows.h>
#include <String.h>
/*
指针数组 - 数组
*/
int main()
{
int arr[10];//整形数组 ->存放整形的数组
char ch[5];//字符数组 ->存放字符的数组
//指针数组 -> 存放指针的数组
int* parr[5];//存放整形指针的数组
char* pch[5];//存放字符的数组
system("pause");
return 0;
}
13.结构体
结构体是C语言中特别重要的知识点,结构体使得C语言有能力描述复杂类型
1.结构体的声明、初始化、使用
#include <stdio.h>
#include <windows.h>
#include <String.h>
/*
结构体
定义规则:
struct 变量名
{
这里的变量称为结构体的成员变量
这里的变量可以是标量、数组、指针、甚至是其他结构体
数据类型 变量名;
};
tips:注意{}结尾有个分号;
*/
struct B
{
char a;
short b;
int c;
};
struct Student
{
// 成员变量
struct B sb;
char name[20]; // 学生姓名
int age; // 学生年龄
} s1, s2; // s1和s2也是结构体变量,但是s1和s2是全局变量
int main()
{
// 结构体的声明 s是局部变量
struct Student s1; // 创建对象
// 结构体的声明与初始化
struct Student s2 = {{'w', 7, 17}, "艾斯", 20}; // 对象
// 结构体的使用 通过操作符(.) 结构体变量.结构体的成员变量
printf("姓名=%s 年龄=%d\n", s2.name, s2.age); //
printf("%c", s2.sb.a);//w
printf("\n");
// 结合指针的使用 操作符->的使用
struct Student *ps = &s2;
printf("%c\n", (*ps).sb.a); // w
printf("%c\n", ps->sb.a);
system("pause");
return 0;
}
2.结构体的传参
void print1(struct Student st)
{
printf("%c %d %d %s %d\n",st.sb.a,st.sb.b,st.sb.c,st.name,st.age);
}
void print2(struct Student* pst)
{
printf("%c %d %d %s %d\n",pst->sb.a,pst->sb.b,pst->sb.c,pst->name,pst->age);
}
int main()
{
/*
函数传参的时候,参数是需要压栈的。
如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的
下降。
结论:结构体传参的时候,要传结构体的地址。
*/
struct Student s2 = {{'w', 7, 17}, "艾斯", 20}; // 对象
print1(s2);//传值调用
print2(&s2);//传址调用
system("pause");
return 0;
}