目录
2.打开文件后,处于只读模式。按“i”后进入insert模式(插入模式),就可以输入了。
一、C语言基础
1.1C语言
C语言是世界上最流行、使用最广泛的高级程序设计语言之一,广泛用于系统与应用软件的开发。 具备很强的数据处理能力、高效、灵活、功能丰富、表达力强和较高的移植性等特点。
操作系统是计算机系统的核心,如 Unix、Linux 以及 Windows 的部分内核,都是使用 C 语言进行开发的。 C语言能够直接访问硬件资源,可以实现对硬件设备的驱动程序开发,使得计算机能够识别和使用各种硬件,如显卡、声卡、网卡等。
1.2 Linux系统
在Linux系统中,万物皆文件。
Terminal:终端,Linux系统中用于用户与系统进行交互的接口。以下的大部分操作都是在终端中完成。
1.2.1 文件操作命令
ls //列出当前目录下的文件
ls-a //列出所有目录、子文件、文件和隐藏文件
ls-R //列出从当前目录开始的所有子目录、文件并且一层层
ls-F //列出文件、目录名并显示出文件类型
ls-t //以修改时间为时间倒序列出文件、子目录
ls-l //以长列表格式显示文件、目录的详细信息
cd 文件名 //打开文件
cd xxx //直接切换到xxx文件夹
cd .. //切换到上级目录
cd //返回到默认目录
mkdir 文件名//创建文件夹
rmdir 文件名//删除文件夹
pwdvv //显示当前工作目录
file 文件名 //查看文件的文件类型
//创建文件
touch 文件名 //创建一个新的空文件
//创建目录
mkdir -p 文件夹名//-p多级目录,父目录不存在时可以创建父目录。例如mkedir -p dir1/dir2/dir3
//删除文件
rm 文件名
rm -i //询问是否删除
rm -f //强制删除
//删除目录
rmdir 文件名 //删除空目录
rm -ri 文件名 //删除非空目录
1.2.2 文件颜色属性
蓝色:目录
绿色:可执行
浅蓝色:链接
红色:压缩
灰色:其他
2.1编写第一个C语言程序
1.创建一个拓展名为".c"的文件:hello.c
双击打开Termainal后输入
vi hello.c //创建一个名为hello.c的文件,如果已经存在,直接打开这个文件。
2.打开文件后,处于只读模式。按“i”后进入insert模式(插入模式),就可以输入了。
3.编写程序员的第一个C语言程序
#include<stdio.h>
//#--->预处理
//"stdio.h" 标准输入输出头文件
//<>表示这是个库文件
//引用头文件
int mian()
{
printf("hello world\n");
//printf 就是stdio.h库中的一个输出函数,他会原封不动的输出“”中的内容(一些特殊的除外,例如\n)。
// \n -->就是换行的意思。在打印的hello world结尾换行。
return 0;
}
编写完成后,按“ESC”键,点击冒号 :输入wq后回车,退出编写。
w--->written 写完了,也就是保存。
q--->quit 结束,也就是退出编写。
4.编译程序
gcc hello.c //gcc是一个编译器
编译通过后会生成一个绿色的可执行文件a.out
当然,你也可以自定义生成的可执行文件名
gcc -o 自定义可执行文件名 要编译的文件名
5.运行程序
./a.out
3.C语言的基础知识
3.1 注释
// 单行注释 /* 多行注释 */
3.2数据类型
基本数据类型:char、short、int、long、float、double
基本数据类型就是C语言规定好的,不需要你去定义。
3.2.1整型
用于表示整数的数据类型。
short(短整型)、int(整型)、long(长整型)。都是用于表示整数的,区别就是他们在内存控件中开辟的空间大小不一样大。
编译器中可以用sizeof关键字查看(单位,字节):
//注意:sizeof运算符的优先级与!同级,优先级高于 * / %
sizeof运算符的作用:求 变量 或 数据类型 所占内存空间的大小,以字节为单位
1kb = 1024 byte 字节
1byte = 8bit bit就是二进制位
#include<stdio.h>
int main()
{
printf("short:%d\n", sizeof(short)); // 2
printf("int:%d\n", sizeof(int)); // 4
printf("long:%d\n", sizeof(long)); //32位的系统 4,64位的系统 8
return 0;
}
3.2.2浮点型
浮点型用来表示小数,C语言中,依精度而分,产生了两种浮点型:
1、单精度浮点型(float),占4 byte
2、双精度浮点型(double),占8 byte
3.2.3字符型
字符型,用于表示字符,char ,占1 byte 。通过与ASCII码表进行对照将二进制数字转换成字符。
如果想要查看ascii码表,可以在中断中使用指令:
man ascii
3.3 变量与常量
常量,常-- >恒定也即是不会改变的量,变量,顾名思义就是会变化的量。就比如Π的值是固定的,约为3.14,是不会变化的,3。14就是常量,人的年龄是会变化的,那么年龄就是变量。
3.3.1创建一个变量:
数据类型 变量名;
int a;//创建一个名为a的整型变量,如果没初始化的话a的值是一个随机值
a = 10;//这是变量赋值
int b=0;//创一个个名字为b的整型变量,并且初始化为0 一般情况下不知道应该初始化为什么值,就给0
3.3.2变量的输出
#include <stdio.h>
int main()
{
int a=10;
float b=0;//f浮点型初始化为0可直接写0也可写0.0
b=3.14;//赋值改变b的值
char c='a';
printf("%d\n",a);// %d 是占位符(给整型占位),%d 位置显示的内容会被后面的a的值替换
printf("%f\n",b);// %f 也是占位符(给浮点型占位),%f 位置显示的内容会被后面的b的值替换,默认保留小数点后六位。
printf("%c\n",c);//%c,给单个字符占位,%c的位置显示的内容会被后面的c的值代替。
return 0;
}
修改保留的小数点后位数:
%0.2f 保留小数点后两位,(也可写成%.2f)
%02d 保持两位数,只有一位的话前面补零
%5d 保持五位,不够的在前面补空格
以此类推可以选择想要保存的位数
3.3.3自加、自减
b=++a ->前++,先自加后赋值(或者其他操作)
a++ ->后++,先赋值(或者其他操作)后自加
++a;和a++;的意思是 a = a + 1;
无论 ++a (++在前) 还是 a++ (++在后),执行完之后,a自身必然增加1
因此无论是 ++a还是a++ 都等价为:a+=1和a=a+1
另外:其他的算数运算符也有类似 +=的写法
a -= 3; <=====> a = a - 3;
a *= 3; <=====> a = a * 3;
a += b; <=====> a = a + b;
a += b + 3; <=====> a = a + (b + 3);
a -= 5 / 2; <=====> a = a - (5 / 2)
a %= 3 <=====> a = a % 3;
3.3.4关系运算符
(1) < 小于 (2) <= 小于等于 (3) > 大于 (4) >= 大于等于 //(1)(2)(3)(4)优先级相同(高) (5) == 等于 两个人的身高是否相等 (6) != 不等于 //(5)(6)优先级相同(低)
3.3.5逻辑运算
&& 逻辑与
|| 逻辑或
&&短路运算:一假即假 有三个条件,当条件1不成立的时候,后面的所有条件不执行,因为一假即假 ||短路运算:一真即真 有三个条件,当条件1成立的时候,后面的所有条件不执行,因为一真即真
3.3.6三目运算符
? : 表达式 条件 ? 表达式1 : 表达式2 条件为真,执行表达式1 条件为假,执行表达式2
3.4条件分支语句
3.4.1 if 语句
if(表示式) //表达式成立结果为真,进入if下面的{ },将{ }里面的语句1和语句2都执行一遍
{
语句1;
语句2;
}
//if 语句可以嵌套
if(表达式1) height > 180
{
if(表达式2) eye == 2
{
if(表达式3) money > 10000
{
语句1; //当表达式1和表达式2和表达式3全部成立,才会执行语句1
}
}
}
3.4.2if-else语句
/*
二分支语句
单项必选题 ,进入if,就不可能进入else,进入else,就不可能进入if, 二选一
*/
if(表达式)
{
语句1;//if条件成立,执行语句1
}
else//否则
{
语句2;//if条件不成立,执行语句2
}
3.4.3if-else if语句
if(表达式1)
{
语句1;//表达式1成立,执行语句1
}
else if(表达式2)
{
语句2;//表达式1不成立,并且表达式2成立,执行语句2
}
else//if表达式1不成立,并且表达式2成立,执行语句3
{
语句3;
}
3.4.4switch语句
switch(num)要求num变量的类型必须是 整数 char short int long 枚举,不能是浮点型
{
case 1: //switch语句可以用if-esle平替 这里相当于 if(num==1)
语句1;
break; //如果没写break,并且进入switch语句中了,会一直往下执行直到遇到break或者结束的部分才会结束执行
case a: //a不能是变量,只能是常量
语句2;
break;
case b: //a与b和1的值不能相等
语句3;
break;
default: //default可写可不写
语句3;
break;
}
3.5循环语句
3.5.1while语句
//当一段代码出现重复的时候,优先想到用循环替代
while(条件) //条件为真,进入循环体,将里面的所有语句都执行一遍,然后再进行条件判断,如果还是真,继续进入循环,如果为假,循环结束
{//循环体开始
执行语句1;
执行语句2;
执行语句3;
}//循环体结束
while(1)//1恒为真这是个死循环
{
}
3.5.2for语句
for(表达式1; 条件; 表达式2)//注意 () 里面必须有 ; ;
{
执行语句1;
执行语句2;
}
for循环在执行的时候,表达式1,只执行1次
条件,for循环的结束条件,理解方式 同while
表达式2,通常放的是循环变量自加或自减
条件省略默认为真即
for( int 1=0; ;i++ )
{
}//是死循环
3.5.3 d0-while语句
do
{//循环体
执行语句
}
while(条件);//注意此处,必须加;号
执行语句最少被执行一次。因为这个循环语句是先执行后判断
3.5.4 break与continue
break 关键字作用: break 关键字可以用来结束 switch语句 和 循环(while、for、do-while) break 放入在循环体,一但执行break,整个循环就结束了 continue(继续) 关键字作用: 在循环体中,用来结束一次循环(结束本次循环),然后循环继续执行 。 这俩都得在循环里面用,在循环外面使用会报错。
3.6进制转换
二进制:逢二进一 0 1 八进制:逢八进一 0 1 2 3 4 5 6 7 //在C语言中八进制数以 0开头 056 八进制数56 十六进制:逢十六进一 0 1 2 3 4 5 6 7 8 9 A B C D E F ///在C语言中十六进制以0x开头 计算过程: 先%再/, /到0结束 十进制: 23 二进制: 10111 八进制: 027 十六进制:0x17 十进制: 255 二进制: 1111111 1*2^7 + 1*2^6 + 1*2^5 + 1*2^4 + 1*2^3 + 1*2^2 + 1*2^1 + 1*2^0 = 255 八进制: 0x377 3*8^2 + 7*8^1 + 7*8^0 = 255 十六进制:0xFF 15*16^1 + 15*16^0 = 255 八进制转二进制:一位八进制数对应三位二进制数 十六进制转二进制:一位十六进对应四位二进制
3.7类型转换
在一个表达式中,有两个不同类型的数参加运算,编译器把不同类型的数转换成同一类型,然后运算。
3.7.1隐式转换
gcc编译器自动转换------->隐式转换
//优先级由低到高
//char short int long float double
#include<stdio.h>
int main()
{
int a=5,b=3;
float c=a/b;
printf("%f\n",c);
return 0;
}
// 结果:1.000000
当 = 两边数据类型不匹配,以左边为基准,没有四舍五入。
3.72强制类型转换(显示转换)
程序员自己来转换,把数据转成他想要的类型
把想要转换的数据类型写在变量左边: (数据类型)变量名
#include<stdio.h>
int main()
{
int a=5,b=3;
float c=(float)a/(float)b;
printf("%f\n",c);
return 0;
}
//a和b两个只要有一个显示转换就行,以为另外一个会被隐式转换
//结果:1.666667
3.8输入输出
输入:input
输出:output
3.8.1putchar和puts
#include<stdio.h>
int main()
{
char a='s';
//putchar 输出一个字符到外界
putchar(a);
putchar('\n');
//puts 输出一个字符串到外界
puts("hello world");//自动换行
return 0;
}
//printf()和putchar()函数都是标准输入输出库函数。
//printf() 输出一个字符串
//putchar() 输出一个字符
3.8.2格式化输入输出
一些占位符
%d //十进制整数
%c //一个字符
%s //一个字符串
%f //一个浮点数
%x //一个十六进制数
#include <stdio.h>
int main()
{
int a=0;
scanf("a is %d",%a);//这样写需要按照格式输入,例如:a is 9 这样输入比较麻烦,一般避免这样写
return 0;
}
/*
scanf()函数接收输入数据时,遇以下情况结束一个数据的输入:(不是结束该scanf函数,scanf函数仅在每一个数据域均有数据,并按回车后结束)。
① 遇空格、“回车”、“跳格”键。
② 遇宽度结束。
③ 遇非法输入。
*/
3.8.3 getchar()与scanf()
getchar函数每次只能读取一个字符。如果你输入了多个字符,getchar函数只会读取第一个字符,而将其他字符留在输入缓冲区中。
getchar()接受一个字符输出,输入完毕后徐娅按下回车表示输入完毕。
scanf()在接收一个输入时,会把输入的内容存放在内存中,因此需要在变量名前加上&符号(& 取地址符)来完成输入。
3.9数组
数组用于存放多个相同类型元素,在内存空间中是连续存放的。可以同过数组的角标来实现对数组的随机访问。
3.9.1一维数组
int arr[5];//定义了一个整型数组,arr是数组名也是数组的首地址,5代表数组里面有5个元素
//arr[0]、arr[1]、arr[2]arr[3]、arr[4]
//数组名是从0开始的,对于arr[5]数组而言,没有arr[5]这个元素!
整型数组: int a[10];
字符型数组: char b[6];
浮点型数组: float c[2];
int d[]={10,20,30,40,50};//元素个数可以不写,编译器会自己计算个数
int e[2]={1,2,3,4};//err元素个数草果数组范围,报错!最好避免数组越界访问。虽然C语言没有检查数组越界的机制,但是无法保障结果正确。所以最好不要越界。
//C语言中的数组长度一经声明,长度就是固定的,无法改变,并且C语言不提供计算数组长度的方法
int a[];//报错!不能这样写,编译器不知道要开辟多大空间。
所占空间大小:元素类型所占大小*元素个数
字符数组和字符串的区别在于:
字符数组的长度是固定的,而字符串的长度是固定的加上一个空字符。
字符数组可以是可写的,也可以是不可写的,而字符串是只读的。
字符数组的存储位置可以在数据段、栈和堆中,而字符串只能在数据段中。
字符数组可以存储任意字节的数据,而字符串是一系列以空字符结尾的字符数组。
3.9.2一维数组元素的赋值
#include<stdio.h>
int main()
{
int arr[5];
arr[0]=10;//给数组arr第一个元素赋值
printf("%d",arr[0]);//输出第一个元素的值
return 0;
}
#include<stdio.h>
int main()
{
int arr[5]={0,1,2,3,4};//完全初始化
int a[5]="hello";//不是完全初始化,因为要给字符串的结束符'\0'留位置。应该写成 int a[6]="hello";
int i=0;
for(i=0;i<5;i++)
{
printf("%d\n",arr[i]);//循环输出所有元素的值
//[]里面允许写变量
}
return 0;
}
#include<stdio.h>
int main()
{
int arr[5]={0};//部分初始化,没赋值的元素默认为0
int i=0;
printf("请输入五个整数:\n");
// 循环输入五个数,分别依次赋值给arr数组的5个元素
for(i=0;i<5;i++)
{
scanf("%d",&arr[i]);
}
//输出这五个数
for(i=0;i<5;i++)
{
printf("%d\n",arr[i]);//循环输出所有元素的值
//[]里面允许写变量
}
return 0;
}
#include<stdio.h>
int main()
{
int a[5]={1,2,3,4,5};
int b[5]={0};
int i=0;
int lenth=sizeof(a)/sizeof(a[0]);//计算数组长度
for(i=0;i<lenth;i++)
{
b[i]=a[i]; //数组拷贝
printf("%d\n",b[i]);
}
return 0;
}
//b=a 这是错误的写法
//数组一经定义,无法整体赋值
3.9.3 冒泡排序法
//冒泡排序:从第一个元素开始,比较相邻两个元素的大小,如果第一个元素大于第二个元素则交换两个元素的位置;如果第一个元素的值不大于第二个元素,则往后比较第二个元素与第三个元素值的大小。直到与最后宇哥元素相比,后重复这一过程。
#include<stdio.h>
int main()
{
int arr[]={12,87,23,9,56,2};
int lenth=sizeof(arr)/sizeof(arr[0]);//计算数组长度
int i=0,j=0,temp=0;
for(i=0;i<lenth-1;i++)
{
for(j=0;j<lenth-1-i;j++)
{
if(arr[j]>arr[j+1])//相邻两个元素开始比较,如果第一个大于第二个则交换位置
{
temp=arr[j];
arr[j]=arr[j+1];
arr[j+1]=temp;
}
}
}
for(i=0;i<lenth;i++)//输出排序后的数组
{
printf("%d ",arr[i]);
}
return 0;
}
3.9.4二维数组
二维数组用来存储一个 矩阵, 有行和列。
创建一个二维数组:
int a[3][4] = {{11,22,33,44},{55,66,77,88},{10,20,30,40}};//定义一个二维数组,3行4列,共12个整型变量
//二维数组的行列下标都是从0开始的
//12个整型变量的名字分别叫什么??
//二维数组的元素下标是先行后列
a[0][0] a[0][1] a[0][2] a[0][3]
a[1][0] a[1][1] a[1][2] a[1][3]
a[2][0] a[2][1] a[2][2] a[2][3]
3.9.5二维数组的输出
#include <stdio.h>
int main()
{
int a[3][4] = {{11,22,33,44},{55,66,77,88},{10,20,30,40}};
int i,j;
for(i = 0; i < 3; i++)
{
for(j = 0; j < 4; j++)
{
printf("%d ",a[i][j]);
// printf("a[%d][%d] == %d ",i,j,a[i][j]);
}
printf("\n");
}
return 0;
}
3.10字符与字符串
'a' '\n' 单引号引起来的单个字符
字符串"hello"是由字符:'h' 'e' 'l' 'l' 'o' '\0'组成的。其中'\0'是字符串结束的标识符 ascii码值是0
字符串"a"由字符:'a'和'\0'组成。只要是双引号引起来的,末尾都有个'\0'。以'\0'为结束的的称为标准字符串。
char a []="hello";//标准字符串
char b []={'h','e','l','l','o'};//不是标准字符串
char c [10]={'h','e','l','l','o'};//是 因为部分初始化时未被初始化的部分会被默认初始化为0,而0是'\0'的ascii码值。
char d []={'h','e','l','l','o','\0'};//是标准字符串
char e []={'h','e','l','l','o',0};//是
//字符数组
char f[10];//定义了一个字符数组
f[10]={'h','e','l','l','o'};//字符数组的部分初始化
g[]="hello";//字符串,系统会默认补'\0'
//'\0'需要占用内存空间,'\0'计入元素个数,即计入长度
//字符数串的长度不计入'\0'。字符串所见即所得,看得见的才要计入。
"hello world"占12字节
字符数组保存"hello world"要至少12个元素
printf("%s\n",g);//数组名就是数组的首地址
//%s 输出字符串 从字符串的首地址开始输出,直到遇到'\0'结束
//而%c遇见'\0'不会结束,他会给'\0'输出,但是'\0'输出看不见什么效果
//计算字符串长度
#include<stdio.h>
int main()
{
char a[100]="hello world";
int i=0,count=0;
while(a[i])
{
count++;//记录元素个数
i++;
}
return 0;
}
3.10.1区分 0
0 整型0
'\0' 字符杠零,整型0和字符杠零完全等价 ascii码值是0
'0' 字符零,ascii码值是48
3.11指针
指针其实就是内存单元的地址。内存以字节单位被均匀的分割,1 Byte=8bit。
指针变量:指针变量是存放一个内存地址的变量,不同于其他类型变量,它是专门用来存放内存地址的,也称为地址变量
野指针:没有指向任何一个有效地址
3.11.1指针的定义与使用
//指针也是一种数据类型,指针变量也是一种变量,指针变量指向谁,就把谁的地址赋值给指针变量。
//首先,要声明一个指针变量,需要使用星号"*"来表示该变量是一个指针。
//指针之间乘除加没什么意义,会报错,但是减有意义,指针之间相减得到的是相差的元素个数
数据类型*变量名;
int *a;//定义了一个指针变量 a是指针名
double *b;//未初始化的指针变量就是野指针,一般不知道之指哪儿就指NULL;
long *c;
char *d
//指针的大小 所有的指针在32位的平台占4字节 54位平台占8字节
#include <stdio.h>
int main()
{
int *ptr;//创建一个指向整型变量的指针,名为ptrptr是一个指针变量,它的值是某个字符变量的地址。
int a=10;
ptr=&a;//&a 得到a的首地址 赋值给ptr就使得ptr指向了a
//打印ptr指向的地址 打印地址得用%p
printf("ptr is %p\n",ptr);
printf("&a is %p\n",&a);//打印a的首地址
return 0;
}
3.11.2 通过指针修改变变量的值
//通过指针修改变量的值是一种间接修改的方法
int *p=NULL;//一般来说不知道初始化应该指向哪里,就指空。不能随便指一个地方,因为有概率修改系统文件导致崩溃
p=&a;
*p=10;//间接赋值,修改了a的值
//*p解引用可以取出这个地址的值,因此关于数组的输出又多了一种方法
int a[5]={1,2,3,4,5}
for(int i=0;i<5;i++)
{
printf("%d ",*(a+i));//等价于printf("%d ",a[i]);
}
// 虽然arr是数组首元素地址,首元素地址+1后一个指向的是下一内存单元的地址,
// 但是在解引用前,地址+1不再是纯粹的跳过一个内存单元,而是跳过一个数据元素的内存单元数。因此i++不用写成i+=4;
p //代表a[0]元素地址
p+1 //代表的是下一个元素的地址,也就是a[1]元素的地址
p+2 //代表的是下一个的下一个的元素的地址,也就是a[2]元素的地址
相当于 p+1---》p+sizeof(数据类型)
int a[5]={1,2,3,4,5};
int *p=a;
p[0]--->*(p+0)
p[i]--->*(p+i)
//a、p都是是数组首地址,允许写p[0]代表a数组的第一个元素,但是p依旧是指针
3.12函数
用于完成某一特定功能的一段代码封装在一起形成一个函数
3.12.1函数的定义
返回值类型 函数名()
{
}
//函数名可以自定义,但最好见名知意。1.不能以数字开头。2.除了”_“不能有其他的其他特殊字符。函数先定义再调用。两个风格示例
//SetNumber 大驼峰命名法(windows)
//set_number linux风格
#include<stdio.h>
void fun(int b,int a);//函数声明
void max(int a,int b)
void print_Star();
//编译是从上到下进行的,先写函数声明,再在主函数下面写函数定义,避免函数嵌套调用别的函数时未找到函数定义。
int main()//主函数是c语言程序的入口,程序从这里开始执行
{
print_Star();//函数的调用
print_Star();//程序从上往下依次执行
int a=3,b=5;//a,b是局部变量(定义在函数里面的变量)
max(a,b);// 带参函数,a,b是实参
fun(a,b);
return 0;
}
void fun(int b,int a)
{
printf("a is %d\n",a);//a is 5
printf("b is %d\n",b);//b is 3
//时和名字无关,按位置传
}
void max(int a,int b)//a,b是函数的形参,作用域只在函数内。max是一个有参无返回值函数
{
printf("%d\n", a>b? a:b);
}
void print_Star(void)//函数的定义,print_Star是一个无参无返回值函数。void->无返回值。形参里的void可以不写。
{
printf(" *\n");
printf(" ***\n");
printf("*****\n");
}
//main-->主调函数
//max-->被调函数
//主调函数传给被调函数-->传参
//被调函数传给主调函数--->返回值
//形参和实参要保持个数个类型一致
//有返回值类型的函数要搭配return使用
int max(int a,int b)//返回值类型为 int
{
return a>b?a:b;//返回函数值,返回a,b中的较大者
}
//没有返回值的函数也可以使用return
void fun(int a,int b)
{
printf("111\n");
return ;//作用:结束函数,类似于break
printf("222\n");//不会执行
}
3.12.2函数参数是指针时
//当函数的形参数是指针时,可以在函数内直接修改指针指向的变量的值
#include<stdio.h>
void swap(int *a,int *b)
{
int temp=0;
temp=*a;
*a=*b;
*b=temp;
}
int main()
{
int a=3,b=5;
swap(&a,&b);//地址传递
printf("a is %d\nb is %d\n",a,b);
return 0;
}
3.12.3函数的参数时数组时
//数组名是数组的首地址,当函数的形参是数组名时,退化成指针
#include<stdio.h>
void ShowArr(int *a,int lenth)
{
int i=0;
for(i=0;i<lenth;i++)
{
printf("%d ",a[i]);//循环打印数组
}
putchar('\n');//输出回车
}
void print(char *p)
{
while(*p!=0)
{
printf("%c\n",*p);
p++;
}
/*//或者
int i=0;
for(i=0;*(p+i)!=0;i++)
{
printf("%c\n",*(p+i);
}
*/
}
int main()
{
int a[]={1,2,3,4,5};
char b[]="hello";
int lenth=sizeof(a)/sizeof(a[0]);//计算数组长度
ShowArr(a,lenth);
print(b);//关于打印字符串的函数,不用传递数组长度,因为他本身就含有结束符
return 0;
}
//这里引用一位大佬的博客内容
//由于数组类型的形参最终会退化为指针,所以有一个细节需要额外注意:在函数体内部不能通过运算符 sizeof(形参) 的方式对数组进行内存大小的计算,因为这样计算出的是指针自身所占用的内存大小(得到的结果是4字节或者8字节),而我们想要知道的是指针指向的内存的大小。所以在进行数组传递的时候,都会给函数添加一个额外的整形参数用于标记当前这个数组的容量。
作者: 苏丙榅
链接: https://subingwen.cn/c/function-pointer/#1-2-%E5%8F%82%E6%95%B0%E4%B8%BA%E6%95%B0%E7%BB%84
来源: 爱编程的大丙
3.13字符串处理函数
strlen() //求字符串长度,不算'\0',返回值就是长度
strcpy() //字符串拷贝,把后面得复制到前面.返回值是目标字符串数组的首地址
strcat() //字符串拼接,把后面的拼接到前面
strcmp() //用于比较两个字符串的大小(比较ascii码值)
#include<stdio.h>
#include<string.h>//需要引用这个头文件
int main()
{
char a[100]="hello ";
char b[100]="world";
int lenth=strlen(a);
printf("%d\n",lenth);
strcpy(b,a);//把a复制到b,b得要能放的下a,还得给'\0'留个位
printf("%s",b);//world
printf("%s",a);//a不会改变
strcat(a,b);
printf("%s\n",a);//helloworld
int t=strcmp(a,b);//一个一个比,a[0]和b[0]比较,a[1]和b[1]相比,类推。后面的大返回负值,前面大返回正值,一样大返回0
printf("%d\n",t);
return 0;
}
3.13.1 atoi函数
输入一个字符串,转换成数字
#include<stdio.h>
#include<stdlib.h>
int main()
{
char a[100];
scanf("%s\n",a);
int at=atoi(a);
printf("%d\n",t);
return 0;
}
//1.传入字符串,返回对应的整数
//2.会一直转换,直到遇到非零数字字符
//3.如果字符串开头不是数字,返回0
//4.可解析带符号的字符串,返回正确值
//5.主要用于字符串的数字与数字的转换
3.14产生随机数
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
int main()
{
int a=0;
srand(time(0));//srand随机数种子函数,time(0)为当前对应秒数
while(1)
{
a=rand()%35;//rand()随机数函数
printf("%d\n",a);
getchar();
}
return 0;
}
//在调用rand()之前都要先调用srand()否者每次产生的随机数都想相同。一般而言只要调用srand()一次。
3.15宏常量定义
#define 宏变量名 宏变量的值
#define PI 3.14
#define maxsize 100
//宏定义没有 = 和 ;
//宏常量是文本替换,没有类型检查,一般都写大写,但是小写也可以
//使用
int a[maxsize];//等价于int a[100];
int r=10;
printf("%d\n",pi*r*r);//求面积
3.16局部变量与全局变量
3.16.1局部变量
定义在在函数的内部变量。在函数内部定义的变量,只在函数范围内生效,随着函数调用的结束变量也随之释放。如果没有初始化,那么局部变量的值默认是谁随机的。
#include<stdio.h>
void fun()
{
int a=100;//函数内部的a与主函数内的a只是同名而已,他们并不是同一个变量。
}
int main()
{
int a=3;
printf("a is %d\n",a);//3
if(a==3)
{
int a=10;//在if的{}内定义的a只在这个括号范围内生效
printf("a is %d\n",a);//10
}
fun();
printf("a is %d\n",a);//3
return 0;
}
局部变量的优点:
只在需要的的时候存在,避免对全局的污染,使得各个代码块之间可以独立工作,局部变量不使用时会被释放,能提空间的利用率。
3.16.2静态局部变量
静态局部变量是在函数内部声明的变量,他在整个程序运行期间都存在生命周期得以延长,但是只在函数内部起作用,函数外不可见。
//静态局部变量的声明
int a;//普通局部变量
static int b;//静态局部变量,没初始化默认为0。
static char c;//默认赋空字符;
3.16.3全局变量
全局变量:定义在函数外部的变量,在整个程序运行过程中有效,可以被所有函数访问。 只要有一个地方修改了全局变量,那么其他访问到的全局变量就都是修改了的。 全局变量初始化默认是 0 局部变量可以和全局变量同名,同名时优先使用局部变量。
#include<stdio.h>
int max=100;
void fun_1()
{
max=1;
printf("%d\n",max);//1
}
void fun_2()
{
max=10;
printf("%d\n",max);//10
}
int main()
{
fun_1();
printf("%d\n",max);//1
fun_2();
printf("%d\n",max);//10
return 0;
}
3.16.4静态全局变量
静态全局变量:和普通全局变量一样用关键字static修饰,没初始化默认0,静态全局变量只在当代码文件可见。
3.17结构体
一些相同的类型的元素我们可以用数组来盛放,那一些不同数据类型但是又相互关联的元素该如何存放呢?——结构体。
3.17.1结构体的声明
struct 结构体名
{
成员类型(也就是数据类型) 成员1;
成员类型 成员2;
成员类型 成员3;
......
};//注意这里有个分号不能忘记了
结构体式一种复合类型,也是我们可以自定义的类型。
3.17.2定义一个结构体类型变量
struct 结构体名 变量名;
3.17.3结构体初始化
struct stu wanger={"zhangsan",19,89.5};//按照定义结构体时候的顺序,对成员进行初始化
3.17.4访问结构体变量的内部成员
struct stu
{
int age;
char name[20];
float score;
};
int main()
{
struct stu liming;//定义了一个名为liming的stu结构体类型变量
//结构体变量名.成员名
liming,age=19;
//访问结构体变量liming的age成员变量,点引用
//当然也可以使用指针访问
struct stu *p=&liming;//定义了一个结构体变量类型的指针,保持a的地址
printf("%s\n",*(p).name);
printf("%d\n",*(p).age);
printf("%\.2fn",*(p).score);
//结构体指针可以直接通过 -> 来访问成员变量
printf("name:%s age:%d score:%.2f\n", p->name, p->age, p->score);
returnn 0;
}
成员运算符 | 算法 | 左值 | 右值 |
. | 运算得到结构体变量中的成员对象本身 | 结构体变量 | 成员名称 |
-> | 运算的到结构体变量中的成员对象本身 | 结构体指针 | 成员名 |
3.17.5结构体做函数参数
#include<stdio.h>
struct student
{
char name[20];
int age;
float score;
};
void print(struct student w)
{
printf("姓名:%s年龄:%d得分:%d\n",w.name,w.age,w.score)
}
int main()
{
struct student hz={"坤坤",27,2.5};
print(hz);//值传递,缺点就是太占空间了
return 0;
}
#include<stdio.h>
struct student
{
char name[20];
int age;
float score;
};
void print(struct student *w)
{
printf("姓名:%s年龄:%d得分:%d\n",w->name,w->age,w->score)
}
int main()
{
struct student hz={"坤坤",27,2.5};
print(&hz);//地址传递,在32位下只要4字节
return 0;
}
3.17.6结构体数组
#include<stdio.h>
struct student
{
char name[20];
int age;
int number;
};
int main()
{
struct student s[3]={{"张三",20,1001},{"王二",21,1002},{"李四",20,1003}};//定义了一个名为s的结构体数组,该数组有三个结构体变量元素。
structyige student *p=s;//定义结构体数组指针
for(int i=0;i<3;i++)
{
printf("姓名:%s\n年龄:%d\n编号:%d\n",s[i].name,s[i].age,s[i].number);
printf("姓名:%s\n年龄:%d\n编号:%d\n",p[i].name,(*(p+i).age,(p+i).number);
}
return 0;
}
3.17.7结构体所占空间大小
#include<stdio.h>
struct A
{
char name[20];
int age;
int number;
};
struct B
{
char a;
char b;
int c;
int d;
};
struct C
{
char a;
int b;
int c;
char d;
};
struct D
{
int a;
char b;
short c;
char d;
};
int main()
{
struct A a;
struct B b;
struct C c;
struct D d;
printf("%d\n",sizeof(a));//28B
printf("%d\n",sizeof(b));//12B
printf("%d\n",sizeof(c));//16B
printf("%d\n",sizeof(d));//12B
return 0;
}
结构体内存对齐的规则主要有以下几点:
1. 结构体成员首地址对齐:结构体成员首地址要对齐到其类型的自然边界上。自然边界一般为该类型的大小,比如int的自然边界为4字节。
2. 结构体总大小对齐:结构体的总大小也要对齐,一般对齐到最大成员大小的自然边界上。
3. 内存对齐时可能会有内存空洞:为了满足对齐要求,编译器会在成员之间加上填充字节(padding),这会造成一定的空间浪费。
4. 编译器会按照自身规则进行优化:结构体的实际内存对齐方式由编译器决定,编译器会根据优化规则进行调整,以获得最佳的时间和空间效率。
5. 可以通过编译器指令或属性控制对齐:一些编译器提供指令或者属性来控制结构体的对齐方式,从而优化空间或时间。
6. union不需要对齐:union的内存对齐规则比较特殊,它不需要进行对齐。
所以在定义结构体时要注意内存对齐的影响,最好按照自然边界来组织字段顺序,以减少PADDING带来的空间额外开销
3.17.8结构体嵌套
#include<stdio.h>
struct date
{
int year;
int mon;
int day;
};
struct stu
{
char name[20];
int age;
struct date birthday;//在stu结构体中定义了一个date结构体类型变量作为stu的成员变量
};
struct cat
{
char name[20];
int age;
char kind[20];
struct date birthday;//在cat结构体中定义了一个date结构体类型变量作为stu的成员变量
};
int main()
{
struct stu s = {"Tony",12,{2001,02,12}};
struct cat w = { "Bonne",2,"美短",{2020,9,8} };
printf("姓名:%s\n年龄:%d\n生日:%d-%02d-%02d\n", s.name, s.age, s.birthday.year, s.birthday.mon, s.birthday.day);
printf("姓名:%s\n年龄:%d\n品种:%s\n生日:%d-%02d-%02d\n", w.name, w.age,w.kind, w.birthday.year, w.birthday.mon, w.birthday.day);
return 0;
}
3.18内存管理
3.18.1内存分为四个部分
全局区 (全局变量、静态变量)
栈区 (局部变量、参数、返回值)
堆区 (malloc 动态内存分配从堆区中分配)
常量代码区 (代码、常量字符串)
代码区:存放函数体的二进制代码,由操作系统进行管理。
全局区:存放全局变量和静态变量,程序结束后由操作系统释放。
栈区:存放函数的参数值,本地变量等,由编译器自动分配释放。
堆区:存放动态分配的内存,需要程序员手动分配和释放。
常量区:存放字符串常量和其他常量,程序结束后由操作系统释放。 内存被分区的主要原因是:
方便管理:每个区有不同的生命周期和释放方式,便于内存的分配和释放。
防止内存冲突:每个区有不同的内存地址,避免变量地址之间的冲突和覆盖。
提高效率:代码区和常量区的内容在运行时不会改变,仅加载一次,这样可以加快程序的运行速度。
3.18.2内存分配的两种方式
1.静态内存分配,在编译时确定内存大小。
2.动态内存分配,在运行时确定内存大小。
动态开辟内存函数:malloc()
释放内存函数 free()
void *malloc(int size);
(1)功能:手动在 堆 空间申请一块 连续 的内存空间
(2)参数:int size 申请空间的大小,以字节为单位
(3)返回值:void* 无类型指针
返回值就是在堆空间申请的空间的首地址
如果申请成功,得到首地址
如果申请失败,返回NULL
void free(void *ptr);
(1)功能:手动释放堆空间申请的内存
(2)参数:void *ptr 需要的是释放空间的首地址
(3)返回值:void 无返回值
因为malloc不一定每次都成功,申请失败后返回空--->就相当于 int *p=NULL;这个导致p变成了一个野指针。为了避免野指针的危害,需要对p进行非空判断。
3.19.位运算符
位操作是基于二进制数操作
3.19.1右移>>
一个数右移n位相当于除以2的n次幂,但不能改变数本身的值.
>>
10 5
1010 >> 0101
7 1
0111 >>2 0001
3.19.2左移<<
一个数右移n位相当于乘以2的n次幂,但不能改变数本身的值.
<<
5 20
0101 <<2 0001 0100 //左移两位
10 20
1010 << 0001 0100 //左移一位,1可以省略不写
3.19.3按位与&
& 按位与,运算符两边都是1,结果才是1,有一个是0,结果就是0
&&逻辑与
&a取地址
0111 & 0100 按位与
按位与常用于强行将某些位清零或者强行置为1.任意数和1与不变,和0与清零
3.19.4按位或|
| 按位或,运算符两边都是0,结果才是0,有一个是1,结果就是1
||逻辑与
0111 | 0100 按位与
任意数和1或置为1,和0或不变
3.19.5异或^
异或:相同为0,不同为1
3^2==1
0011
0010
-----^
0001
1011
0110
-----^
1101
3.19.6按位取反
~按位取反,0变1,1变0
3.20.原码反码补码
1.原码:数据原本的二进制码。
最高位为符号位,0表示整数,1表示负数。
2.负数的反码:除了符号位,其他的位都取反,1变成0,0变成1 。
3.补码:负数的补码为其反码加一
正数的原反补三码合一。