C语言基础
计算机程序:人编写的让计算机执行的操作,这些操作的集合称为程序
计算机语言:在计算机上编写的能够让计算机完成某项功能的语言,该语言可以通过编译器,转化为计算机能够识别的二进制指令。
一、计算机语言分类
机器语言
计算机能识别的由0和1组成的语言,不方便人查看
汇编语言
用标记符来代替机器指令用来编程的语言,运行速度块
高级语言
更加贴近与人类的语言来编程的语言,有单词连接
二、C程序结构
任何计算机执行c程序时,都是从程序的入口执行的,int main()就是程序的入口,而且一个程序中只有这么一个主函数
三、变量、标识符
1.变量的使用
变量初始化:在定义变量的同时进行赋值
赋值:先定义变量,后对变量进行赋值操作
二者的区别:只定义了变量而不进行赋值操作,那么这个变量的值是随机的数。但也有例外,全局变量和static修饰的变量等默认初始值就是0的情况
注:在变量初始化的时候不能多个变量连续赋值,例如
int a=b=10; //这种赋值是错误的
//正确赋值
int a,b,c;
a=b=c=10;
2. 标识符
-
以数字、字母、下划线组成,且开头不能是数字。
-
标识符不能和关键字重名
-
关键字在编写的时候有颜色区分,且在C语言中关键字都是小写
-
C语言中区分大小写
四、数据类型
数据类型转换
-
隐式类型转换(在不同数据类型变量进行运算过程中,计算机自动将低精度的数据类型转换为高精度数据类型参与运算,结果自然也是高精度数据类型),
-
显示类型转换(强制转换)
高精度数据类型转换为低数据类型
格式:(低精度数据类型) 高精度数据类型数据:
五、输入输出
1. 输出printf
printf格式化输出,按照指定格式进行输出
printf的输出格式:
//printf输出格式
printf("格式化字符串,其中包含占位符",输出变量名);
//占位符
%d:十进制整形
%ld:长整型
%f:单精度浮点型
%lf:双精度浮点型
%c:字符型
%s:字符串型
%o:八进制
%x:十六进制
%#x:十六进制默认在前面加上0x
%e:指数形式表示小数
%p:地址
二进制:0b开头
八进制:0开头
十六进制:0x开头
注:标识符不能和关键字重名,但是printf不是关键字,可以以printf为变量名,但是之后在使用printf功能进行输出的时候会报错,显示printf不是一个函数或者函数指针,所以一般不会用它当做标识符
2.输入scanf
1.格式:
scanf("占位符/格式化字符串",变量地址);
注:从标准输入(键盘)输入数据
2.从键盘上输入数据给变量接受并存储。
3 如果没有从键盘上输入数据,那么就阻塞等待,直到键盘输入数据才执行完该语句
4.当键盘输入的字符串,与 scanf 格式化输入进行匹配,如果格式化中有非占位符字符,那么从键盘输入的普通字符如果和格式化非占位符内容匹配,如果二者匹配,就直接匹配完,进行下一个格式化内容的匹配;遇到格式化占位符就表示键盘输入的字符串的当前位置需要和占位符进行匹配,如果匹配就将内容传给格式化占位符对应的变量,如果不匹配就说明scanf()匹配失败。
变量地址:&变量名
注意:如果是连续% d % d 整数,在输入时通过空格或回车来分开
空格回车不能用来分割字符% C ,因为%输入是字符,回车,空格都是字符
例如:
int age = 0;
float date = 0.0;
char buf[10] = {0};
scanf("nihao%d%f%s",&age,&date,buf);
/*
这时从键盘上输入就必须是:nihao10 3.14good
表示将age赋值为10,date赋值为3.14,buf数组赋值为good
值的注意的是在输入3.14完之后,如果用空格或者回车,那么就会被当做buf的内容一并输入到bu数组中
*/
-
字符输入、输出:getchar、putchar
getchar():阻塞等待终端输入一个字符,整个功能就是输入一个字符
char ch; ch=getchar(); //表示从键盘中输入一个字符赋值给变量ch
putchar(字符):向终端输出一个字符
putchar('A');
gets()
从键盘上输入一个字符串
#include <stdio.h> int main() { char ch[100]; ch=gets(); printf("%s\n",ch); }
puts()
输出一个字符串
-
当程序正在运行时,通过 ctrl + c接受
六、gcc编译过程
预处理、编译、汇编、链接
七、转义字符
转义字符:\
将普通字符转为特殊字符:因为ASCII表中的一些特殊字符,不能用一个字符表示,所以用\加上字符来表示
此外也可以通过\将具有特殊意义的字符转化为普通字符,例如输出\就需要输入\\ ,输出“”就需要写成 \ " \ "
常见的特殊字符:
- ‘\a’:
- ‘\b’:
- ‘\f’:
- ‘\n’:
- ‘\v’:
- ‘\t’:
- ‘\0’:
- ‘\nn’:
- ‘\xnn’:
八、运算符
表达式:数据通过运算符进行关联的得到的式子
算数运算符
-
除法运算符:/
两个整数相除,结果一定只取的是整数部分
两个不同类型的数据相除,按照精度高的转换,结果也是高精度
-
求余数:%
一定是两个整数之间才可以求余数
自增、自减运算符
- 变量++:
- ++变量:
- 变量–:
- –变量:
关系运算符
连续使用多个关系运算符,在语法上不会报错,但是一般不会达到想要的结果
//想要变量x在30和50之间
如果连续用关系运算符
30<x<50; //这里只要x大于30永远为真,不能达到想要的结果
应该用
30<x && x<50;
表达式:由运算符连接的式子
逻辑运算符
- &&:一假为假
- ||:一真为真
- !:取反
三目运算符
语法:表达式?成立的表示式:不成立的表达式
问题:10右移3位,结果是
位运算符
计算机运算全部是二进制补码形式运算,在屏幕上显示的结果也是二进制取补码之后得到的
位运算符两边的数据正负都可
-
<<左移运算符:数据<<位数:表示二进制数据左移多少位,其中高位丢弃,低位补零。这种方法可能改变数据的正负特性。例如:12<<28,就变为了负数
而且,二进制每向左边移动一位,那么十进制数就乘以2
-
右移运算符: 数据> >位数:,如果是负数那么右移后高位补1,如果是正数,那么右移后高位补零。这种方法可能导致在不能被二整除的数据中出现精度缺失的问题,例如:5>>1,结果却是2,而不是2.5.
而且,二进制每向右边移动一位,那么十进制数就除以2
右移运算符可分为逻辑右移和算数右移:
逻辑右移:低位丢弃,高位补零
算数右移:低位丢弃,高位补符号位
-
位逻辑与&:数据1 & 数据2,这是按照数据的二进制补码的形式每一位进行匹对,只有对应为同时为1才为1,否者为0
-
位逻辑或 | :数据1 | 数据2,这是按照数据的二进制补码的形式每一位进行匹对,只有对应位有一位为1该位结果为1
-
位逻辑异或 ^:数据1 ^ 数据2,这是按照数据的二进制补码的形式每一位进行匹对,只有对应位不一样该位结果才为1
九、流程控制
流程控制:控制程序代码的运行方式,默认情况下是顺序执行。
1. 顺序结构
顺序结构就是程序按照语句编写的顺序进行执行。
2. 选择结构
(1)if选择结构
单分支、双分支、多分支
(2)switch选择结构
switch …case 结构
//表达式和每个常量表达式进行对比,如果成功,就执行对应语句
switch(表达式)
{
case 常量表达式1: //常量程序运行过程中不会改变的量
{
语句1;
语句2;
break;
}
case 常量表达式2:
{
语句1;
语句2;
break;
}
case 常量表达式3:
{
语句1;
语句2;
break;
}
default:
{
语句1;
语句2;
break;
}
}
注:每个case 语句执行完成之后,都必须加上break退出switch结构;如果不加就会导致执行完该case语句后,继续匹对下一个常量表达式,直到之后的所以case常量都匹对完才退出switch结构
注:switch比较判断,只能是整形或者字符型
3.循环结构
for循环
//for循环结构
for(表达式1; 表达式2;表达式3)
{
//语句块
}
//执行顺序:进入循环时,执行表达式1,之后执行表达式2,判断条件是否满足,满足条件则执行语句块,然后执行表达式3,实现循环条件的更新;之后执行表达式2,判断条件....
注:在for的括号中,可以不写表达式,但是,分号一定要写上
while循环
//while循环结构
while(循环控制语句) //先判断表达式是否成立,成立则执行循环体
{
//循环体
}
//int i=0;需要循环n次就while(i<n)
//其中,执行最后一次循环的时的i值为n-1;最后一次循环结束后i的值为n;
#include <stdio.h>
int main()
{
int i=0;
while(i<5)
{
printf("进行第%d次循环,此时i=%d,",i+1,i);
i++;
printf("该次循环结束,i=%d\n",i);
}
}
运行结果:
do while循环
//do while循环结构
do
{
//循环体
}while(循环控制语句); //这个分号必须写上
//此时的while判断条件为第二次开始,但是while条件判断任然是需要执行几次就是几次
#include <stdio.h>
int main()
{
int i=0;
do{
printf("这是第%d次循环\n",i+1);
i++;
}while(i<5);
}
运行结果:
注do while 循环无论条件成立与否,都至少会执行一次
goto跳转
//goto跳转结构
#include <stdio.h>
int main()
{
int i=0;
printf("1\n");
THERE: //goto到的地方,用大写的标识符表示+:表示
printf("3\n");
if(i<2)
{
i++;
goto THERE; //goto 需要跳转的地方的标识符
}
printf("6\n");
if(i==2)
{
i++;
goto THERE;
}
}
运行结果:
跳转语句
break、goto、continue、return
**break:**立即结束switch或者循环
**continue:**结束本次循环,继续下一次循环
**goto 标记:**跳转到指定标记位置,执行标记的语句块
注:goto标记处的语句,在没有遇到goto前,和其他语句顺序执行,,当遇到goto语句后,执行goto语句对应的标记语句,标记语句执行完后,继续顺序执行标记之后的语句
return:
//打印0到100,,从键盘输入一个数,如果和该数为输入地数,就不答应该数
#include <stdio.h>
int main()
{
int a,i;
i=0;
printf("输入一个数:\n");
scanf("%d",&a);
while(i<=100)
{
if(a==i)
{
printf("这是输入的数,不打印\n");
i++;
continue;
}
printf("%d\n",i);
i++;
}
}
十、数组
数组的概念
用一组连续空间地址,存放一组相同数据类型的集合称为数组,数组是构造数据类型
定义数组
一维数组定义
int类型数组定义
-
数组类型 数组名[元素个数n]
这种定义形式,数组元素个数定了,为n个
特别注意:int a[ ];这种定义数组的方式错的
这种没有初始化的元素,在没有赋值前是随机数
-
数组类型 数组名[ ]={1,2,3,4,5}
不规定元素个数,根据元素初始化的个数来判断数组元素个数,例如这里就是数组元素为5个
-
数组类型 数组名[5]={1,2,3,4}
这是部分初始化,只赋值初始化了其中4个,没有初始化的元素,默认是0
也可以指定部分初始化
int a[5]={[4]=1,[1]=5}; //指定初始化a[4]和a[1] int a[]={[4]=0}; //数组有5个元素,最后一个元素初始化为0
-
数组类型 数组名[4]={1,2,3,4}
这是完全初始化,每个元素都初始化
除了初始化的时候可以对数据一次性赋值外,其余情况下都不可以对数组一次性符多个值
char类型数组
-
char 数组名[ 字符个数]
这里定义了一个指定元素个数的数组,此时数组中是反斜杠0
-
char 数组名[ 字符个数3] ={ ‘a’,‘b’}
这是部分初始化,没有被初始化的字符,默认是’\0’,十进制就是0,字符形式打印出来就是什么也没有,空
-
char 数组名[ 字符个数3] ={ ‘a’,‘b’,‘\0’}
这是全部初始化,每个元素都赋值
-
char 数组名[ ]= “hello”
这是利用字符串来初始化数组,数组大小由多少个字符决定,例如这里数组就是6字节
特别注意,此时字符串的最后默认有一个’\0’影藏了,表示字符串结束----------'\0’表示null,空,图形符号就是什么也没有
这种初始化字符串数组的方式,比如a[6],由于已经初始化了,所以为’\0’,十进制就是0;
注意:在字符串数组中如果存入字符个数大于等于指定的数组大小,那么’\0’就没有存入数组中
char a[4]="Hello"; //此时 char a[10]="qwe"; a[8]='q'; a[9]='b'; for(int i=0;i<10;i++) { printf("%c\n",a[i]); } printf("%s\n",a); //把它当做字符串数组时,遇到'\0'就结束,打印的时候后面的字符不会打印出来,即使后面的元素已经赋值 //用作普通字符数组遍历a数组时,任然会打印a[0]~a[9],当有给后面的元素赋值时,打印对应的值,如果没有赋值,a[3]~a[9]都是'\0'或者说是0,如果以字符形式输出,就是什么也没有 //字符串输入
char c[20];
scanf(“%s”,c); //输入空格断开两个字符串
//特别注意这种利用scanf函数输入的方式,如果输入的字符串长度小于定义的数组长度,那么没有被占用的数组空间,是随机数。所以字符串通过’\0’来表示字符串结束。
例如这里输入一个hello字符串,那么数组c[20]只占用了其中的6个字节,5个看得见的字符,一个影藏的字符’\0’,其余位置为随机数。
二维数组定义
-
二维数组必须要求一维数组中元素个数相同
-
初始化的时候二维 数组的个数可以不写,但是一维数组的元素个数必须写 int a[] [5],因为他已经成为二维数组的数据类型了
//二维数组定义方式------带初始化
int a[2][3]={{1,2,3},{4,5,6}};
int b[][3]={1,2,3,4,5,6,7} //这种没有指定二维数组大小的定义方式,会根据一维数组的大小来自动判断,例如这里就相当于
//int b[3][3]={1,2,3,4,5,6,7}
char ch[3][4]={'a','b','c','d','c'};
char ch2[4][10]={"hello","world"}; //这种定义字符数组的方式,等同于一维数组中char ch[10]="hello";没有占用的数组空间默认是十进制0,字符型'\0',读作空;表示为'';这只是一种状态
//这两种定义二维数组的方式都对二维数组进行了初始化,所以没有写出来的部分默认会是十进制0
//不初始化的
int ch[3][4];
int ch[][4];
//这两种定义二维数组的方式没有对数组进行初始化,所以里面的值是随机数
scanf("%s\n",ch[0]); //这表示对二维数组中第一个一维数组进行赋值,如果没有占满这个一维数组,那么没被占用的空间,任然是随机数
数组的使用
根据数组的索引来找到数组元素,利用 数组名[下标] 来标识数组元素。例如a[2],表示数组a中的下标为2的元素
下标从0开始
对数组赋值
//利用循环对数组赋值
for(int i=0;i<n;i++)
{
scanf("%d",&a[i]); //必须加上取值符号,且加上[i]否者就会导致只给首地址赋值,其余任然是随机数,因为a只代表首地址
}
求数组元素个数
-
在定义数组时,数组的大小只能是整数常量或者整形变量
int a[10]; int b[n]; //申请当前n的值的个数个元素。即使之后n的值发生了改变,数组元素个数任然是之前的个数
十一、函数
函数概念
函数是指将一段能够完成指定功能的代码封装起来,当需要完成某项功能的时候,方便调用。
函数分类
- 按照函数定义方式
- 库函数----------------例如 printf ()、scanf()等C语言自带的函数
- 系统函数-------------系统自带的函数
- 用户自定义函数-----------用户自己定义的函数
- 按照有无函数返回值
- 有返回值函数------------函数执行的结果可以返回到函数调用的地方
- 无返回值函数-------------函数执行的结果不需要返回到函数调用的地方
- 按照有无形式参数
- 有形式参数-------------在调用函数的时候需要输入参数给形式参数
- 无形式参数-------------调用函数的时候不需要输入参数给函数
函数定义
注意:
1、形式参数可以没有或者一个或者有多个,有的话形式参数必须带上数据类型,每个形式参数之间用逗号隔开
2、函数定义不能嵌套------------------不能在一个函数里面在定义函数
3、所有函数定义都是平行的
4、当没有
-
有返回值、有形式参数
返回值数据类型 函数名 ( 形式参数1,形式参数2,形式参数3,…)
#include <stdio.h> int main() { int x,y,max1; x=1; y=2; max1=max(x,y); //必须定义一个变量来接返回值 printf("最大的数是%d\n",max1); return 0; } //在主函数外定义一个有返回值、有形式参数的函数,max(),实现两个int类型数据比较,并用变量z返回较大的数 int max(int a,int b) { float z=0.0f; if(a>b) { z=a; }else { z=b; } return z; //注意当返回数据类型超过函数定义的返回值数据类型时,会默认强转为定义的数据类型 }
-
有返回值,没有形式参数----------返回值可以为空
int function1() //没有形式参数可以不写或者写上void { int b; b++; return b }
-
没有返回值,有形式参数----------不能有返回值
void function2(int a,int b) //没有返回值可以不写void,直接写function() { printf("%d\n",a*b); return void; //由于没有返回值,所以这一行可以不写,或者写成 return空格; }
-
没有返回值,没有形式参数
void function3() //没有返回值可以不写void,直接写function() { printf("请输入:\n"); }
函数调用
-
函数调用完成之后,函数返回结果的接受变量=函数名(形式参数1,形式参数2,…);或者直接将函数名(形式参数1,形式参数2,…),当做一个表达式利用
#include <stdio.h> int add(int a,int b) { int sum=0; sum=a+b; return sum; } int main() { int s; s=add(1,2); printf("和为:%d\n",add(1,2)); printf("和为:%d\n",s); }
-
传入的数据类型必须和形式参数数据类型一致
-
return后面的变量或者常量的数据类型必须要和函数返回值的数据类型一致
-
注意当返回数据类型超过函数定义的返回值数据类型时,会默认强转为定义的数据类型
-
即使子函数函数没有形式参数,也需要将括号加上
-
形式参数的类型和实际参数的类型不一致,会转换为形式参数类型
-
如果实际参数是基本数据类型,那么形参存储实际参数的值,叫做值传递。如果对形式参数进行修改,那么不会影响实际参数
-
如果不写函数返回类型,那么默认返回类型是int类型,所以需要加上return 值;
函数声明
一、直接声明
子函数和主函数在同一个.c文件中
-
当子函数在主函数前面定义时,在主函数中调用子函数的时候,可以不用声明子函数
-
当子函数在主函数后面定义时,在主函数中调用子函数的时候,需要声明子函数;因为编译器的编译过程是从上往下进行编译的,后面定义的函数可能找不到,而报错。(这种情况可以不用声明,系统会自动隐式声明)
#include <stdio.h> int main() { int function(int a,int b); //这一行就是,子函数的第一行加上分号,这句声明只要放在函数调用前任何位置都可以。 printf("结果为:%d\n",function(1,2)); return 0; } int function(int a,int b) { int sum; sum=a*b; return sum; }
子函数和主函数不在同一个.c文件中
当主函数调用子函数时,将子函数的第一行复制过来,加上分号,该语句前面加上extern
二、间接声明--------------------------重点!!!!
主函数和子函数在同一个.c文件中
- 定义一个**.h文件用于声明子函数,在.h文件中子函数复制第一行,加上分号,并且在该语句前面加上extern**,
- 在主函数中添加.h文件,然后直接使用子函数名即可实现调用
当子函数和主函数在不同.c文件中
-
在子函数所在的.c文件的开头加上用户自定义头文件.h文件
-
在用户自定义头文件中,声明子函数-----复制子函数的第一行,并加上分号
-
在主函数中添加用户自定义文件.h文件,然后直接用子函数名即可实现调用
主函数.c文件-------test.c
#include <stdio.h> #include "my_function.h" int main() { int s; s=add(1,2); printf("结果:%d\n",s); }
子函数.c文件 -----------add.c
#include "my_function.h" int add(int a,int b) { return a+b; }
头文件.h文件---------function.h
#ifndef MY_FUNCTION_H #define MY_FUNCTION_H int add(int a,int b); #endif
十二、内存
内存分为物理内存和虚拟内存,物理内存就是实实在在的存储设备,虚拟内存就是操作系统虚拟出来的内存。操作系统会在物理内存和虚拟内存之间做映射,完成虚拟内存和物理内存之间的联系。
在32位操作系统下,每个进程的寻址范围是0x00 00 00 00~0x ff ff ff ff (一共4G大小)
在写程序的时候,需要的地址都是虚拟内存中的地址
在32位操作系统中,虚拟内存被分为用户空间(3G)和内核空间(1G),其中用户空间是当前进程所私有的,内核空间是系统中所有进程共有的
一、虚拟内存结构
堆
在动态申请内存的时候,在内存中开辟的内存--------------不懂?
栈
存放------在函数或者复合语句中,未加任何修饰的局部变量、环境变量、命令行产生(shell脚本名命令后面的参数)
栈的空间增长,从高地址到低地址分配空间(从上往下),这个分配的内存段空间叫做栈帧。因为栈的上面是内核空间,不允许访问。
静态全局区
-
未初始化静态全局区
存放全局变量以及静态变量(被static修饰的变量),这些变量只完成了定义,但是没有赋值
-
初始化静态全局变量
存放全局变量以及静态变量(被static修饰的变量),这些变量完成了定义,并且完成赋值
代码区
存放程序代码
文字常量区
存放常量( 常量就是在程序运行过程中不可以改变的量)
二、全局变量和局部变量
全局变量
1、普通全局变量
定义
没有任何修饰的,定义在任何函数外的变量
如果不赋值,那么普通全局变量默认值为0
作用范围
-
可以作用于整个程序的任何位置
-
其他.c文件调用该全局变量的时候,需要声明------在调用文件中添加extern 数据类型 全局变量名;
-
声明的时候不需要赋值
-
全局变量一般用大写表示
文件1
#include <stdio.h> float PI=3.1415926 //需要写数据类型,全局变量名,等号,值(不写默认为0)
文件2
#include <stdio.h> extern float PI; //写在头文件下面,需要加上数据类型和去全局变量名 int main() { float s,r; r=2.0f; s=PI*r*r; printf("圆的面积为:%f\n",s); }
注意:
1、一个程序中只能有一个main函数,如果多次定义main函数会报错
所以可以在主函数中定义全局变量,然后在其他子函数中调用,但是子函数中不可以有main函数;或者相反
2、在编译的时候,如果没有用make工具来管理多个文件,需要将相关联的两个文件写在一起编译
生命周期
程序运行的整个过程中一直存在,直到程序结束
2、静态全局变量
定义
被static修饰的全局变量
如果静态全局变量不赋值,那么默认值为0
作用范围
只能在定义该全局变量的文件中全局有效,其他文件不可以调用该全局变量
生命周期
整个程序运行过程中一直存在,直到程序结束
全局变量与子函数关联
子函数没有被调用前,不分配内存空间,即使全局变量被调用在子函数中了,也不会参与运算
当被调用时,创建一个临时的空间,同时也创建一个临时的变量,当子函数调用完成之后就会销毁变量,全局变量参与子函数运算之后不会销毁,且继续保持运算之后的值
局部变量
普通全局变量
定义
定义在函数中或者复合语句中的,没有添加任何修饰的变量------------局部变量
局部变量不赋值,默认初始值是随机数
作用范围
只在被定义的函数或者复合语句中有效
生命周期
使用函数之前,不为局部变量开辟内存空间,当调用函数的时候,才为局部变量开辟空间,函数结束,局部变量就释放了
#include <stdio.h>
void function()
{
// int a; //没有赋值,默认为随机数
int b=100;
b++;
//printf("%d\n",a);
printf("%d\n",b);
}
int main()
{
// printf("%d\n",a); //由于局部变量a只在function函数中定义了,所以不能在其他函数中使用,这里会报错,显示变量a未定义
function();
function();
function();
function(); //这里调用了四次,但是结果b的值始终都是101,因为局部变量b在function函数执行完后就销毁了,下次再调 //用function函数的时候就会从新初始化,int b=100;然后计算b++,结果自然每次都是101
}
静态局部变量
定义
被static修饰的,定义在函数中或者复合语句中的局部变量
如果没有赋值,那么默认的初始值为0
作用范围
只在被定义的函数或者复合语句中有效
生命周期
整个程序运行期间都不会消失,当程序结束了才消失。同时静态局部变量被多次调用时,只需要初始第一次,后面调用都不会执行初始化语句,而且静态局部变量的值每一次调用到保留上次调用运算的结果
#include <stdio.h>
void fun()
{
static int i; //静态局部变量未赋值,默认为0
i++;
printf("%d\n",i);
}
int main()
{
// printf("%d\n",i); //由于静态局部变量i只在fun函数中定义了,所以不能在其他函数中使用,这里会报错,显示变量i未定义
fun(); //第一次调用fun函数,进行初始化,i=0;,打印结果是1,1=0+1
fun(); //第二次调用fun函数,不进行初始化,i保留第一次运行的结果1,打印结果是,2,2=1+1
}
变量重名问题
-
相同作用范围内不可以重名,例如
int add(int x,int y) { int sum=0; int sum=0; //两个都是局部变量,作用范围都该函数内部,所以不能重名 }
-
不用作用范围可以重名,不过满足向上就近原则
int a=100; //定义全局变量 void fun() { int s=0; s=a+10; printf("%d\n",s); //这种情况,会使用全局变量a=100参与运算 int a=20; printf("%d\n",a); //这种情况会默认使用向上最近的变量a=20 }
三、内部函数和外部函数
内部函数
定义
内部函数,是指被static修饰的函数,只能在定义该函数的文件中使用,其他文件即使包含了该函数声明的头文件也不可以调用该函数
外部函数
外部函数是指,可以被其他文件调用的函数
使用方法:在进行多文件编程时,只需要将函数定义在指定的.c文件中,然后将该函数的声明放在指定的.h文件中,其他文件只需要包含了该.h头文件,即可实现对该函数的调用
三、宏定义
十三、构造类型
C语言中基本数据类型不能满足程序的需求,需要针对程序添加一种新的类型,这种类型是由多种基本数据类型一起构造而成的
结构体
结构体格式
struct 结构体的类型名
{
数据类型 变量名1;
数据类型 变量名2;
数据类型 变量名3;
};
结构体声明、初始化
//普通声明结构体
struct student
{
char name[20]; //这些都是成员变量,只是声明了,但没有赋值
int age;
int num;
}; //这个分号一定要加
//用typedef 声明结构体
typedef struct
{
char name[20]; //这些都是成员变量,只是声明了,但没有赋值
int age;
int num;
}STU; //之后STU就表示这个结构体类型了
//普通声明结构体并初始化
struct student
{
char name[20]; //声明结构体的时候初始化了一个结构体变量stu1
int age;
int num;
} stu1={"chongba",23,6}; //这个分号一定要加
struct student stu1={{"zhangsan"},{22},{110}};
//对结构体数组初始化
struct student stu1[10]={{"zhangsan",22,1},{"wangwu",20,2},{"zhaoliu",19,3}}; //初始化部分结构体,未初始化的为0
结构体定义
int main()
{
//普通声明结构体并定义
struct student
{
char name[20]; //声明结构体的时候定义了三个结构体变量stu1,stu2,sut3
int age;
int num;
} stu1,stu2,stu3; //这个分号一定要加
//普通结构体定义------------方式一
struct student stu1,stu2;
//typedef型结构体定义---------方式二
STU sut3,stu4;
//定义一个结构体数组--------------方式一
struct student stu[100]; //表示定义了一个由100个结构体组成的数组stu
//定义一个结构体数组--------------方式二
STU stu[100]; //表示定义了一个由100个结构体组成的数组stu
//定义一个结构体指针-------------方式一
struct student * p; //表示定义了一个指向struct student数据类型数据地址的指针变量p
//指针指向的数据类型很重要,因为指针只能指向定义的数据类型数据的地址,而不能 // 指向其他数据类型数据的地址
p=&stu1; //表示结构体指针指向结构体变量stu1的地址,而不是stu.name的地址
//定义一个结构体指针-------------方式二
STU *q;
q=&stu2;
}
结构体使用
一、单个结构体成员使用
格式1:定义的结构体变量名.结构体成员名 = 值
stu1.age=10; //将10赋值赋值给结构体变量stu1的成员age
strcpy(stu1.name,"zhangsan"); //将“zhansan”这个字符串常量赋值给stu1的成员name
注:当结构体中的成员是一个字符数组的时候,需要用strcpy( )这个函数进行复制
strcpy(参数1,参数2); -------表示将参数2中的值复制到参数1中。
注:如果参数一是一个字符数组,参数2必须为一个常量字符串
*格式2:(指针名)- > 成员名
struct student *p;
struct student stu1;
p=&stu1;
strcpy((*p)->name,"zhangsan");
二、结构体数组中对单个结构体成员使用
格式1:数组名[ ] . 成员名
struct student *p;
struct student stu[100];
p=stu; //结构体指针p指向的是数组的首地址,表示该数组中第0个结构体的地址。数组名也表示这个意思
p[i].age=10;
strcpy(p[i].name,"zhangsan");
//或者直接用结构体数组名
strcpy(stu[i].name,"lishi");
格式2:(p+i) -> 成员名
struct student *p;
struct student stu[100];
p=stu; //结构体指针p指向的是数组的首地址,表示该数组中第0个结构体的地址
strcpy((p+i) -> name ,"liuli");
结构体内存空间分配原则
注意:一般我们就是利用sizeof运算符来求结构体内存空间大小
//初始化了一个结构体
struct student stu1={"zhangsan",22,100};
//计算结构体内存大小
int n=sizeof(struct student);
//或者直接使用结构体变量名stu1
int m=sizeof(stu1);
计算机内部给构造体分配内存的原则
- 结构体成员分配的内存地址,必须可以整除成员数据类型。例如,int age;这个成员,如果给他分配的内存地址是0x11,也就是十进制的17,由于17% sizeof(int ) != 0,所以该位置不能存放该成员变量,只有到地址为0x14,也就是十进制的20,才能存放成员变量a。
- 结构体中的大小必须能够整除结构体中最大的数据类型。例如 ,成员变量在内存中按照原则一,已经存好了,但是整个结构体的空间大小为41个字节,然而结构体中最大的成员数据类型为double ,也就是8个字节,41%8 != 0,所以继续增加空间大小到48个字节才是整个结构体空间的内存大小
- 还必须能被4整除。
一个手算的方法计算结构体空间大小------------4字节对齐,不足4字节补上,这种方法还是有不灵的时候,所以选择上面的方法
#include <stdio.h>
struct student
{
char name[10]; // **** **** **** ,最后只有两个字节,不足4个,补上满足4个字节
short num; // ****
//int age; // ****
float weight; // ****
//以上相加为20个字节,所以该结构体的空间大小为20字节
}stu1={"hejunqi",22,106};
int main()
{
printf("结构体空间大小为:%ld %ld\n",sizeof(struct student),sizeof(stu1)); //64位操作系统需要写ld,也就是long类型
}
共用体
共用体和结构体形式几乎一样,定义和使用方法也一样。只不过在内存分配上,共用体共用成员数据类型最大的空间。也正是这个原因导致,只要共用体中的成员一修改,所有成员被迫修改。
共用体的格式
union 类型名
{
数据类型 变量名1;
数据类型 变量名2;
数据类型 变量名3;
};
共用体的定义
参考结构体
公用体的使用
参考结构体
枚举
枚举就是将一个事件的所有可能结果全部写出来,并给每个可能赋整数值,如果不赋值,枚举中第一个结果赋值为0,之后的默认为前一个结果的值加1.
枚举的意义就是限定范围,规定枚举类型的变量只能是枚举中的数,相当于一个宏定义
枚举格式
enum 事件名(也叫枚举名)
{
枚举元素1, //注意枚举元素之间使用逗号隔开,而不是分号
枚举元素2,
枚举元素3,
枚举元素4,
枚举元素5,
枚举元素6,
...
枚举元素n
};
枚举定义
enum day
{
星期一 = 1, //注意枚举元素之间使用逗号隔开,而不是分号。如果不赋值那么就为0
星期二 = 2,
星期三,
星期四,
星期五,
星期六,
星期天
};
枚举使用
enum day xingqi=星期一; //这句话就相当于xingqi=1
节对齐,不足4字节补上,这种方法还是有不灵的时候,所以选择上面的方法
#include <stdio.h>
struct student
{
char name[10]; // **** **** **** ,最后只有两个字节,不足4个,补上满足4个字节
short num; // ****
//int age; // ****
float weight; // ****
//以上相加为20个字节,所以该结构体的空间大小为20字节
}stu1={"hejunqi",22,106};
int main()
{
printf("结构体空间大小为:%ld %ld\n",sizeof(struct student),sizeof(stu1)); //64位操作系统需要写ld,也就是long类型
}
共用体
共用体和结构体形式几乎一样,定义和使用方法也一样。只不过在内存分配上,共用体共用成员数据类型最大的空间。也正是这个原因导致,只要共用体中的成员一修改,所有成员被迫修改。
共用体的格式
union 类型名
{
数据类型 变量名1;
数据类型 变量名2;
数据类型 变量名3;
};
共用体的定义
参考结构体
公用体的使用
参考结构体
枚举
枚举就是将一个事件的所有可能结果全部写出来,并给每个可能赋整数值,如果不赋值,枚举中第一个结果赋值为0,之后的默认为前一个结果的值加1.
枚举的意义就是限定范围,规定枚举类型的变量只能是枚举中的数,相当于一个宏定义
枚举格式
enum 事件名(也叫枚举名)
{
枚举元素1, //注意枚举元素之间使用逗号隔开,而不是分号
枚举元素2,
枚举元素3,
枚举元素4,
枚举元素5,
枚举元素6,
...
枚举元素n
};
枚举定义
enum day
{
星期一 = 1, //注意枚举元素之间使用逗号隔开,而不是分号。如果不赋值那么就为0
星期二 = 2,
星期三,
星期四,
星期五,
星期六,
星期天
};
枚举使用
enum day xingqi=星期一; //这句话就相当于xingqi=1