嵌入式学习笔记之C语言基础

目录

一、C语言基础

1.1C语言

1.2 Linux系统

1.2.1 文件操作命令

1.2.2 文件颜色属性

2.1编写第一个C语言程序

1.创建一个拓展名为".c"的文件:hello.c

2.打开文件后,处于只读模式。按“i”后进入insert模式(插入模式),就可以输入了。

3.编写程序员的第一个C语言程序

4.编译程序

5.运行程序

3.C语言的基础知识

3.1 注释

3.2数据类型

​编辑

3.3 变量与常量

3.4条件分支语句

3.5循环语句

3.6进制转换

3.7类型转换

3.8输入输出

3.9数组

3.11指针

3.12函数

3.13字符串处理函数

3.14产生随机数

3.15宏常量定义

3.16局部变量与全局变量

3.17结构体

3.17.5结构体做函数参数

3.17.6结构体数组

3.17.7结构体所占空间大小

3.17.8结构体嵌套

3.18内存管理

3.18.1内存分为四个部分

3.18.2内存分配的两种方式

3.19.位运算符

 3.19.1右移>>

3.19.2左移<<

3.19.4按位或|

3.19.5异或^

3.19.6按位取反

 3.20.原码反码补码


一、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 动态内存分配从堆区中分配)

常量代码区 (代码、常量字符串)

  1. 代码区:存放函数体的二进制代码,由操作系统进行管理。

  2. 全局区:存放全局变量和静态变量,程序结束后由操作系统释放。

  3. 栈区:存放函数的参数值,本地变量等,由编译器自动分配释放。

  4. 堆区:存放动态分配的内存,需要程序员手动分配和释放。

  5. 常量区:存放字符串常量和其他常量,程序结束后由操作系统释放。 内存被分区的主要原因是:

  6. 方便管理:每个区有不同的生命周期和释放方式,便于内存的分配和释放。

  7. 防止内存冲突:每个区有不同的内存地址,避免变量地址之间的冲突和覆盖。

  8. 提高效率:代码区和常量区的内容在运行时不会改变,仅加载一次,这样可以加快程序的运行速度。

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.补码:负数的补码为其反码加一
    正数的原反补三码合一。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值