程序的集合,PC机软件,手机app,游戏
软件=程序+文档
要想开发一款软件,必须掌握至少一门编程语言
主流编程语言:
低级语言:汇编语言,脚本语言
中级语言:c语言
高级语言:Java,C++,python等
于20世纪70年代在贝尔实验室诞生。c语言是跨平台的编程语言,即用c语言写的程序可以在不同的平台(Linux系统,Windows系统,macos系统)上运行。
c语言程序运行过程:编译器将c语言编译成可执行的二进制文件,并且会被操作系统加载到内存中,然后CPU会从内存中取出数据进行运算,然后将结果输入到内存中存放。
计算机的存储器:内存,外存。
外存又称为只读存储器(ROM):U盘,移动硬盘,?,? 等,可以永久性保存数据,设备断电,数据不会丢失,容量大,造价低,但访问速度慢。
内存又称为随机访问存储器(RAM):不能永久性保存,一旦断电设备就会丢失,容量小,造价高,访问速度快。
计算机是二进制的世界,即数据在计算机中都是以二进制形式存放
计算机存放数据的单位:
单位 | 长度 |
---|---|
bit(位) | 0或1 |
Byte(字节) | 1Byte=8bit |
kb(千字节) | 1KB=1024B |
MB(兆字节) | 1MB=1024KB |
GB(吉字节) | 1GB=1024MB |
TB(太字节) | 1TB=1024GB |
… | … |
数据类型分为基本(简单)数据类型和复杂数据类型
简单数据类型:数值型,字符型和特殊类型
数值型:整型,浮点型
类型 | 长度 | 范围 |
---|---|---|
short (短整型) | 2字节 | -32768~+32767 |
unsigned short(无符号短整型) | 2字节 | 0~65535 |
int(整型) | 4字节 | -231 ~ -231-1 |
unsigned int(无符号整型) | 4字节 | 0 ~ 232-1 |
long(长整型) | 4字节 | -2 31 ~ 231-1 |
unsigned long(无符号长整型) | 4字节 | 0 ~ 231-1 |
浮点型
类型 | 长度 | 范围 |
---|---|---|
float(单精度浮点型) | 4字节 | -1038 ~ -10-38 |
double(双精度浮点型) | 8字节 | -10308 ~ -10-308 |
字符型
类型 | 长度 | 范围 |
---|---|---|
char(字符型) | 1字节 | -128 ~ 127 |
unsigned char(无符号字符型) | 1字节 | 0 ~ 255 |
特殊类型
类型 | 长度 |
---|---|
void(空类型) | 不占内存大小 |
数据在内存中是以补码的形式存放
对于0和正整数来说原码、反码、补码相同
常用进制之间的转换:
1.十进制转二进制,方法:除2取余,逆序
例:100 = 1100100b
2.十进制转八进制或十六进制,方法:与二进制相似
例如:100 =0144 = 0x64
3.二进制转十进制,方法:二进制的每一位乘以其对应的权值
4.八进制或十六进制转换成十进制,方法:与二进制类似
5.八进制或十六进制转换成二进制 将每位数拆分成二进制
6.二进制转换成八进制或十六进制 将二进制从右到左进行分组,八进制分成3组,十六进制分成4组
7.十进制小数转换成二进制,方法:乘2取整,顺序书写
例:67.86 = 1000011.110111
8.十进制小数转换成八进制或十六进制小数。方法:与2进制类似
9.二进制小数转换成十进制小数。方法: 二进制的每一位乘以其对应的权值
10.二进制小数转换成八进制或十六进制小数
例如:1101101001.0011011001 = 1551.1544
11.八进制或十六进制小数转换成二进制小数
例:03242.532 = 011 010 100 010. 101 011 010b
对于有符号数来说,最高位为符号位,0表示正数,1表示负数。
对于无符号数来说,最高位为数值位,其余都一样。
例:short a =100 , 变量a的存储形态
a的二进制形态:1100100
原码:0000 0000 0110 0100 ? 0X64
例:short a = -100,变量a的存储形态
a的二进制形态:1100100
原码: 1000 0000 0110 0100
反码: 1111 1111 1001 1011
补码: 1111 1111 1001 1100
例:int a = 100 ,变量a的存储形态
原码: 0000 0000 0000 0000 0000 0000 0110 0100
int a = -100,变量a的存储形态
原码:1000 0000 0000 0000 0000 0000 0110 0100
反码:1111 1111 1111 1111 1111 1111 1001 1011
补码:1111 1111 1111 1111 1111 1111 1001 1100 ? 0xFFFFFF9C
字符型数据的存储形态:先将字符型数据转换成十进制或十六进制的整数数据,再求补码。
根据标准ASCII码表,将字符转换成整数
字符 | ASCII码值(十进制) |
---|---|
‘a’ | 97 |
‘A’ | 65 |
‘0’ | 48 |
’ '(空字符) | 32 |
‘\r’(回车) | 13 |
‘\n’(换行) | 10 |
‘\0’ | 0 |
例:char a = 'a ’ ,变量a的存储形态
原码: 0110 0001 ?0x61
浮点型数据的存储形态
例:float f = 8.5
二进制形态:1000.1
科学计数法表示: 1.0001 * 23
阶码: 3+127 =130 = 1000 0010 b
符号位: 0
尾数: 0001
存储形态:符号 阶码 尾数
0 10000010 00010000000000000000000
首先要创建一个后缀名为.c的文件,然后将代码写入程序中
由于c语言是编译型语言,所以不能像脚本一样直接执行源文件
//include 为预处理命令 ,用于包含头文件,用于包含头文件,此句的作用是包含stdio.h这个头文件
#include
int main(){
printf("hello!!!\n"); /*调用printf函数用于在屏幕上打印双引号中的数据,
而printf函数是标准c语言函数,它在stdio.h头文件中声明*/
return 0;
//函数结束语句,一旦这句程序执行,整个函数就会结束,并且返回0,即这个函数的结果为0
}
进行保存(esc退出插入模式,:wq保存退出)以后需要用到gcc编译,如果没有报错,继续执行,通过ls命令我们可以看到此时生成了一个a.out的文件,这就是编译后的文件,我们需要用.号去执行它。
GCC是Linux系统中开源免费的编译器,将c文件编译之后会生成一个a.out的可执行文件
在C语言中使用变量一定要定义,然后才能使用,否则会发生错误!
定义变量的规则:首先要指定变量的数据类型(包括简单类型和复杂类型),还要指定变量名(变量名还要为合法标志符)
C语言中的合法标志符:由英文字母,数字和下划线( _ )组成,不能以数字开发,不能为关键字(比如:int,short等)
在同一个代码段(用{}括起来的代码)中变量名不能相同
变量的初始化:在定义变量时,给变量定义一个值
不同的数据类型变量的占位符不同
类型 | 占位符(十进制) |
---|---|
int,short | %d |
unsigned int ,unsigned short | %u |
char | %c |
float | %f |
double | %lf |
#include
int main(){
char abc = 'a';
int c = 8;
//在屏幕上标准输出变量的值用printf函数,\n是换行
printf("c的值是%d\n",c);
printf("abc的值是%c",abc);
return 0;
}
常量可分为字面常量和自定义常量
字面常量
整型字面常量: 十进制整数,八进制整数,十六进制整数,二进制整数
float型字面常量: 3.14或3e-3
double型字面常量:在float型字面常量后加上l或L
char型字面常量:普通字符(‘a’,‘0’, ’ ‘),转义字符(’\n’,’\r’,’\0’)
字符串型字面常量:“abcd”
自定义常量
在定义变量时如果没有初始化,那么系统会给这个变量赋一个随机值
通过const和define关键字来自定义
define用来定义一个宏
#include
//define 后面自定义变量,不要加分毫,常量不能被赋值
#define A 12
int main(){
//另一种方式
const a = 5;
}
数据类型之间的互相转换:显式(强制)转换或隐式(自动)转换
基本数据之间的转换都是隐式进行的,但要遵守以下规则:
1.类型宽度大的整型数据转换成类型宽度小的整型数据或字符数据时,会发生截断若干位舍弃,保留低位进行赋值。
int a = 12345678; 因为二进制int是32位 0000 0000 1011 1100 0110 0001 0100 1110
short b = a ; short 是16位所以需要截断前面多余的 0000 0000 1011 1100 0110 0001 0100 1110
b的值会是24980
2.浮点型数据转换成整型数据,取整。
3.相同数据类型的有符号和无符号数互相转换,有符号转换成无符号
4.有符号数和无符号数运算时,默认将有符号数转换成无符号
执行运算功能
1.算术运算符:+,-,*,/,++(自增),–(自减), -(负号),%(模运算符)
2.比较运算符:>, < , >=,<=,==(等于),!=(不等于) ,其结果为逻辑值(布尔值,只有0或1两种情况)
3.位运算符:&(按位与),|(按位或),^(按位异或),~(按位取反),>>(按位右移),<<(按位左移)
位运算符需要对每一位进行操作,包括符号位
按位右移包括逻辑右移(在最高位补0)和算术右移(在最高位补符号位)
4.逻辑运算符:&&(逻辑与),|| (逻辑或),!(逻辑非),其运算结果为逻辑值
5.条件运算符:(操作数1)?(操作数2):(操作数3),当操作数1为真时,其运算结果为操作数2,否则就为操作数3
6.赋值运算符:=
7.混合赋值运算符: +=(例如等价于m+=i 等于 m=m+i),-= …
8.逗号运算符: ,其运算结果为右操作数
按操作数分类:
1.单目(一元)运算符:只有一个操作数,比如,++,–,!,~等
2.双目(二元)运算符:有两个操作数,比如:+,-等
3.三目(三元)运算符:有三个操作数,比如:a?b:c
表达式:由变量、常量和运算符组成的式子
1.顺序结构
从上到下顺序执行
2.循环结构
重复执行循环体(用花括号括起来的代码块)
常用的循环语句:while,do…while ,for,go to(不常用,用作跳转)
while循环?
#include
int main(){
int i = 0;
while(i<10){
i++;
printf("%d",i);
}
return 0;
}
do…while循环?
#include
int main(){
int i = 0;
do{
printf("%d",i);
}while(i++>10);
return 0;
for循环?
#include
int main(){
int i ;
for(i=0;i<10;i++){
printf("%d",i);
}
return 0;
}
嵌套循环?
不要使用太多,会导致结构复杂
#include
int main(){
int i ,j;
for(i=0;i<10;i++){
for(j=0;j<10;j++){
printf("%d",j);
}
}
return 0;
}
goto?
#include
int main(){
abc:printf("a");
goto abc;
return 0;
}
3.分支结构
由if…else和switch…case来实现
if…else?
#include
int main(){
int i =10;
if(i>10){
printf("符合条件");
}else{
printf("不符合条件\n");
}
return 0;
}
例题:判断平润年
#include
int main(){
int a;
//scanf是键盘输入,参数前面加&
//printf是屏幕输出
scanf("%d",&a);
if((a%4 ==0 && a%100 != 0)|| a%400 == 0){
printf("闰年");
}else{
printf("平年");
}
return 0;
}
#include
int main(){
int n;
scanf("%d",&n);
//清楚缓存区
getchar();
switch(n){
case 0:{
printf("0");
}break;
case 1:{
printf("1");
}break;
case 2:{
printf("2");
}break;
default:
printf("输入错误");
}
return 0;
}
break:用于结束当前循环,或跳出开关语句。
continue:用于结束当前循环,进入下一次循环。
实现某一个功能的代码块
函数可分为库函数和自定义函数
库函数:标准库函数(由c语言提供可以跨平台(操作系统)使用的库函数,比如:printf,scanf,getchar等,这样的函数大概由300多个)和系统调用函数(每个呃呃操作系统都会提供一些函数,这些函数只能在当前系统中使用,不能跨平台,比如:Linux系统提供的Linux系统调用函数(open,read,pthread),Windows系统调用函数(CreateFile,CreateThread)等)
在使用函数(调用函数)时,程序执行的流程会进入到函数内部区执行代码,当程序执行完成,又会跳转到函数调用处,继续往后执行。
1.时间处理相关的函数: time(获取一个时间戳)
时间戳:当前调用函数的时刻距离1970年1月1日0时0分0秒的秒数
例题:计算循环999999990空语句要花多长时间?
#include
#include
int main(){
int i;
time_t t1,t2;
t1 = time(NULL);
for(i=0;i<999999990;i++){
}
t2 = time(NULL);
printf("%d",t2-t1);
return 0;
}
例题:打印当前是星期几?
#include
#include
int main(){
time_t t = time(NULL);
int w;
w = ((t+8*3600)/(3600*24)%7)-4;
printf("今天星期%d",w);
return 0;
}
2.随机数处理相关函数
rand(获取一个随机数),srand(设置随机数种子)
伪随机数:在操作系统内部有一段随机数序列,每次调用rand函数就是在这个随机数序列中以相同的起点取数据,这个起点称为随机数种子,由于每次的起点都一样,所以两次运行程序,取出的随机数就是一样的,想要得到不同的随机数,救得改变随机数种子。
下面的程序随机数,每次运行都是一样的?
//显示10个随机数
#include
#include
int main(){
int i,n;
for(n=0;n<10;n++){
i = rand();
printf("%d\n",i);
}
return 0;
}
用以下方式解决?
通过随时改变的时间戳当种子去解决这个问题
//如果两次运行时间相隔太近,结果也会是一样的
#include
#include
int main(){
int i,j;
srand(time(NULL));
for(i=0;i<10;i++){
j = rand();
printf("%d",j);
}
return 0;
}
3.system():在程序中执行一段命令
获取年月日时分秒?
#include
#include
int main(){
system("date");
return 0 ;
}
4.sleep():让程序休眠多少秒
usleep():让程序休眠多少微秒
#include
#include
int main(){
pirntf("sleeping");
sleep(3);
usleep(300);
printf("wake up");
return 0;
函数的定义:要指定返回类型(没有返回值就用void),函数名(函数名要为合法标示符),函数参数(没有参数可以用void或不写)
在指定函数的参数时,要指定参数的类型,如果参数的个数超过1个那么参数之间要用( , )号隔开
**函数的调用:**在主函数或普通函数中使用函数
形参:形式参数,在定义函数时给函数指定的参数
实参:实际参数,在调用函数时给函数指定的参数
**函数的声明:**当函数的定义在函数的调用之后时
在运行程序时,程序执行流程进入主函数开始从上往下执行代码,如果有调用函数,程序执行流程就会进入函数内部去执行代码,并且会将实参的值传递给形参,当函数执行完毕,程序执行流程就会从函数内部跳转回函数调用处接着执行后面的代码。
在虚实结合方面,进行简单的值传递,即将实参的值传递给形参。
#include
int add(int a,int b);//函数的声明
int main(){
int sum;
sum = add(1,2);//函数的调用
printf("%d\n",sum);
return 0;
}
int add(int a,int b){
return a+b;
}
递归函数:在函数中调用函数本身
递归函数
函数的递归调用
递归函数的注意点:
1.递推规律:
2.递归的结束条件
累加(递归实现)?
#include
int add(int a);
int main(){
add(10);//调用
return 0;
}
int add(int a){
if(a==1)return 1;
else
return a+add(a-1);//返回时调用了add()函数,直到满足a=1是返回值为1(因为累加的开始是从1开始累加的)
}
变量的作用域:变量起作用的范围
按照变量的作用域可以将变量分为局部变量或者全局变量
形参变量也是局部变量
局部变量:定义在函数内部的变量,其作用域为变量定义处到变量所在代码块结束处
全局变量:定义在函数之外的变量,其作用域为变量定义处到变量所在源文件结束处
定义全局变量没有初始化,那么系统默认赋值为0
如果全局变量和局部变量同名,那么在局部变量的作用域内全局变量将被屏蔽
extern:可以扩充全局变量的作用域
#include包含头文件,<>和“”的区别,用<>编译器会去系统库文件中查找头文件,用“”,编译器会去当前工程目录中查找全局变量的作用域在当前源文件中,但是会辐射到同一工程中的其他源文件中,这样会导致不能定义相同的全局变量,这样是不利于开发的,所以可以使用static关键字将全局变量的作用域限定在本源文件内。
静态:被static关键字修饰的全局变量称为静态全局变量
GCC的编译过程
1.预编译(预处理),将所有的宏展开,将头文件中的内容拷贝到源文件中(-E参数)
2.汇编(-S参数)
3.编译(-c参数)
4.链接(-o参数),将目标文件和库文件链接生成可执行文件
目标文件(中间文件)在Linux系统下后缀名为.o,在windows下后缀名为.obj
include预处理命令包含头文件的本质就是将头文件的内容拷贝一份到当前文件中
GDB:Linux中开源免费的调试工具
bug:漏洞,臭虫,程序中的缺陷
debug:调试
调试步骤?
要生成带有调试信息的可执行文件,再用GDB去运行这个文件
gcc -g
b:设置断点
r:运行
n:执行下一行语句
p 变量名:打印变量值
q:退出
s:进入函数内部执行
makefile:自动化编译工具
变量的生存期(life): 变量存在的时间
定义变量就是在内存空间的某块区域按照变量的数据类型给变量划分空间
给变量赋值,就是将变量的值存放在内存空间中
当变量所在代码块运行结束,这个变量所在的空间就会被释放,这个变量就不复存在。
普通局部变量和形参变量的生存期:从变量定义开始到所在代码块结束为止。
全局变量(普通全局变量和静态全局变量)生存期:整个主函数运行期间
静态局部变量的生存期与全局变量的生存期一样
寄存器(register):
CPU内部一小块存储空间,用于存放被频繁使用的变量或数据
寄存器变量:将变量直接放入寄存器中。
volatitle: 让CPU严格从内存中存取数据
条件编译预处理命令:当条件满足才会编译程序,可以防止头文件被重复包含
#ifdef , #ifndef , #if , #else , #elif , #endif , #undef
#define: 预处理命令,用于定义宏
#include
#define X 5+5
int main(){
int k;
k= X+X;
printf("%d",k);
//k为 5+5*5+5=35
return 0;
}
typedef: 重新定义数据类型
#include
typedef unsigned char uchar;
int main(){
uchar n='a';
printf("%c",n);
return 0;
}
数组,指针,结构体,共同体,枚举
数组(Array):由有限个相同数据类型的数据组成
数组元素:组成数组的数据
数组的定义:要指定数组名、数组的基类型(存放到数组中数据的类型),数组长度
#include
int main(){
int a[10] ={100,200,300,400,500};//定义一个基类型为int的数组变量a,其长度为5
printf("%d\n",sizeof(a));//sizeof关键字用于求变量或者数据类型所占内存空间大小
printf("%d",a[1]);
return 0;}
数组的初始化:在定义数组时,要为数组赋值
访问数组元素:通过下标运算符[ ] ,和数组元素的标从0开始到n-1,(n是长度)
数组的大小:数组所占空间的大小为数组基类型的内存大小乘以数组长度,可以使用关键字sizeof来求内存大小,如果求某个数据类型的大小需要加括号,否则就报错。
数组的赋值:不能整体赋值,只能给单一赋值
数组不要越界访问,否则就产生无法预知的错误
在定义数组时没有指定数组长度,那么编译器会自动根据初始化元素的个数来匹配数组长度
数组下标不能为小数
字符型数组:数组元素都为字符
字符串:特殊的字符数组,由于c语言中没有字符串数据类型,所以字符串可以由字符数组表示,但是并不是所有的字符数组都是字符串,因为字符串的结束字符要为’\0’。
字符串有多种写法;
scanf函数原理:
当调用scanf,getchar等这些标准输入函数时,在键盘输入数据完成后需要按回车键才会起作用,当按下回车时,这些数据会被放入缓冲区中,然后scanf或getchar函数就会从缓冲区中取走相应的数据,如果数据类型匹配就会成功取走,如果不匹配,scanf函数就会取数据失败,然后直接返回,如果数据没被取走,就会一直存放在缓冲区中,直到程序结束或用户手动清空缓冲区。
字符串处理函数
strcpy:字符串拷贝,用于给字符串赋值
#include
#include //strcpy要调用此头文件
int main(){
char str[100]="hello";
char str1[20];
strcpy(str,"abcdefg");
strcpy(str1,str);//将后面的拷贝到前面的数组,并且覆盖了
printf("%s\n",str);
return 0 ;
}
strncpy:拷贝指定字节
#include
#include //strcpy要调用此头文件
int main(){
char str2[10]=" ";
strncpy(str2,"1234567890123",9);
printf("%s",str2);
return 0 ;
}
strlen:求字符串长度
sizeof:求占用内存大小
strlen((str));
strcmp:比较字符串大小
str[100]="hello";
str1[20]="hello";
strcmp(str,str1); //结果是0,相等为0,前面小于后面为-1,前面大于后面为1
strncmp:比较指定长度的字符串大小
strncmp(str,str1,5);
strcat:字符串拼接
strcat(str,"你好,中国"); //linux下中文占3个字节
strcat(str,str1);
strchr:在字符串中从前往后查找字符
strchr(str,'a'); //找到后会从找到的字符开始打印一直到结束
strrchr:在字符串中从后往前查找字符
strstr:在字符串中查找字符串
strstr(str,"你好");
strtok:字符串切割
strtok(str,","); //以逗号切割,保留逗号前面的
strcpy,strncpy,strcat这样的函数不会去检查目标字符串的空间是否充足,即容易越界
二维数组:就是一位数组按行,二维
int a[3][4] = {{1,2,3,4},{5,6,7,8},{9,10,11,12}};
char chr[2][4]={'a','b','c','d','e','f','g','h'};
char name[5][10]={"张三","李四","王五","赵六","田七"};
数组和函数的联合使用
函数的形参为数组
当数组作为函数形参时,编译器会忽略数组长度,即数组会退化成指针,可以通过形参改变实参。
例如:void test(int a[],double b)
是内存空间的地址,在32位系统上指针就是一个32位的无符号整数,操作系统将内存以一个字节位单位划分成若干单位,然后从0开始给这些单位编号,那么这个编号就是指针!
(好比一大块土地指的是内存空间,土地公公为每一块相等大小的地都按了一个门牌号,这个门牌号就是指针)
变量的地址用变量首字节的地址表示
指针是一个复杂数据类型
int i =100; int* p = &i;
取变量i的地址
int* q = NULL;
NULL是一个指针常量,可以给任意基类型指针变量赋值,这个指针为空指针
int* pp;
指针变量pp没有赋值,这个变量称为野指针
野指针、空指针、无效指针都不要访问,否则会造成无法预知的错误
运算符 | 结果 |
---|---|
* | 指针指向空间的引用 |
& | 取地址运算符 |
#include
int main(){
int i=100;
int* p=&i;
printf("%d",*p); //其值为i的值100
*p = 500; //改变了i的值
return 0;
}
内存分为:栈区(stack)、堆区(heap)、静态存储区、常量区
栈区:被操作系统管理,即栈区空间的申请和释放都是由操作系统亲自实现,无法人为干预
栈区的特性:FILO(先进后出)或LIFO(后进先出)
栈区用于存放普通的局部变量和形参变量
对于连续定义的普通局部变量来说,先定义的先入栈,先入栈的地址大
数据在内存中存放模式:小端存储模式(低字节存放低地址)和大端存储模式(低字节存放高地址)
一般采用小端存储模式
值 | 低位 | ? | 高位 | |||||||
---|---|---|---|---|---|---|---|---|---|---|
100 | 0x64 | 0x00 | 0x00 | 0x00 |
数据在内存中存放时要考虑内存对齐。
为了提高数据的读取效率,通常在定义变量时会空出一些字节来保证内存对齐,在函数中内存对齐基数为最大的数据类型所占的字节数。
指针和整数的加减运算有意义,将指针以基类型宽度为单位进行移动(加,向高地址移动,减,向低地址移动)
指针和指针的减法有意义
表示两个指针所指向的空间相差几个基类型宽度
函数中内存对齐的规则
1.以最大的数据类型为对齐基数
2.每个变量的地址要为其数据类型大小的整数倍
堆区(heap):
是内存中一块可以由用户自行管理的区域,即堆区空间需要用户自己申请和释放
申请的空间在使用完之后一定要释放,否则会造成、内存泄漏
内存泄漏:申请了空间但没有即使释放,导致操作系统不能将这块空间给其他变量使用
内存溢出:申请的空间不足导致超出范围,发生不可预知的错误
栈区只有1~2M的大小,所以对于占内存较大的数据需要放入堆区中存放
堆区需要手动释放或程序运行结束后操作系统自行释放
堆区空间处理函数 | 作用 |
---|---|
calloc | 申请空间 |
malloc | 申请空间 |
realloc | 申请空间,会将指定空间中的数据拷贝到新的空间中,并且会释放原空间 |
free | 释放空间 |
在申请堆区空间之后如果在使用空间时溢出来了,那么在释放空间时也会将溢出的空间释放,同一块堆空间只能释放一次
静态存储区
由操作系统管理
全局变量(普通全局变量和静态全局变量)和静态局部变量存放在静态存储区中
存在静态存储区的变量,其生存期为变量定义时到主函数结束为止
常量区:放在常量,由操作系统管理
存放于常量区的数据不能被修改,但是通过指针可以修改它的值
1.常量指针 const int* p = &a;
*p=20; 是错误的,不能改变变量a的值
不能通过指针来改变a的值,只能a自己去改变,但p本身是变量p = &b; 是可以的
。
2.指针常量 int* const pp = &c;
定义一个指针常量pp,即指针本身是常量,不能改变存储c的地址但指针所指向的空间是变量。
3. 常量指针常量 const int* const qq =&c;
定义一个常量指针常量qq,即指针所指向的空间是常量,指针本身也是常量。
数组名是数组的指针,数组的地址用数组首元素的地址表示
1.数组和指针
int a[3]={1,2,3};
int* p = a;
printf("%d %d",a[0],*a);//两个相等
printf("%d %d",*(a+1),*(p+1));
2.堆区数组
int* p =malloc(5*sizeof(int));
printf("%d %d %d",*p,*(p+1),p[0]);
3.指针数组:数组元素为指针int* a[2]={&a,&b}; 元素是地址
4.数组指针:指向数组的指针int a[2]={1,2}; int (*p)[5]=NULL; int *p=&a
5.数组元素的指针 int m[3]={a,b,c}; &m[0]
int a = 100;
int* p =&a;
int** q =&p;
当数组作为函数形参时会退化成指针
字符串指针
1.函数指针:指向函数的指针,对于一个函数来说,其入口地址就是函数的指针,函数名就是函数的指针。
int(*func)(int ,int) = NULL;
func = add;
printf("%d",func(3,4));
typedef int(*f) (int,int); //定义一个数据类型为函数指针
f func1 = add;
int add(int a,int b){
return a+b;
}
2.函数的返回值为指针
3.函数的形参为指针
如果想要通过形参去改变实参的值,那么要将实参的地址传给形参
向函数内部传入数据的方法
1.标准输入
2.命令行输入
int main(int argc,char** argv){
//argc:表示传入参数的个数
//grgv:表示参数的值
if(argc ==3){
printf("第一个参数:%s\n",argv[0]);
printf("第二个参数:%s\n",argv[1]);
printf("第三个参数:%s\n",argv[2]);
}else
printf("参数传入错误");
return 0;
}
./a.out 123 456
由有限个数据组成,这些数据可以是相同的数据类型,也可以是不同的数据类型
结构体变量的定义:
结构体变量的初始化,对数据成员从上往下依次赋值
结构体变量的访问,通过成员运算符(.)访问每一个数据成员
#include
struct student{
int id;
char name[10];
int age;
float sight;
char sex[4];
char addr[100];
};
typedef struct student stu;
int main(){
struct student s;//定义一个结构体类型不不变量
struct student s1 = {1001,"张三",22,2.5,"男","湖北武汉"};//从上到下依次填写数据
stu s2={1002,"李四",22};//定义一个结构体并部分初始化,没有初始化的部分各位全零
//s ={1003,"王五",20,4.5}; //错误,结构体变量不能整体访问
//id =1004; //错误,结构体成员不能单独访问;
s.id =1003;
//s.name="王五"; 错误,数组不能整体访问
strcpy(s.name,"王五");
printf("%d %s %d %f %s %s\n",s1.id,s1.name,s1.age,s1.sight,s1.sex,s1.addr);
static stu s5;
const stu s6 ={...};
//s6.id=1007; //错误,常量不能赋值
return 0;
}
1.结构体指针
创建堆区结构体
#include
#include
#include
typedef struct student{
int sno;
char name[10];[
float sight;
char sex;
char addr[100];
}stu;
int main(){
stu s={1001,"张三",4.5,'1',"湖北武汉"};
stu* p = &s;//定义一个结构体指针变量p,结构体的地址用第一个数据成员的地址表示
//使用结构体指针变量访问结构体数据成员时使用指向运算符
printf("%d %s %f %c %s",p->sno,p->name,p->sight,p->sex,p->addr);
stu s1;
p = &s1;
s1.sno=1002;
strcpy(p->name,"李四");
(&s1)->sight = 5.2;
(*p).sex = '0';
strcpy((&*p)->addr,"湖北宜昌");
stu* q = malloc(sizeof(stu)); //堆区结构体
if(NULL == q){
perror("空间申请失败!");
return -1;
}
(*q).sno = 1200;
(&q)->sight =3.0;
free(q);
return 0;
}
2.结构体数据成员为指针
typedef struct teacher_info{
int id;
char name[10];
char sex;
short high;
char* addr;
char* phone;
}t_info;
t_info tt;
tt.id =1004;
strcpy(tt.name,"赵六");
tt.sex='0';
tt.high =153;
tt.addr ="湖北黄冈"
tt.phone = "1233444121";
结构体数组
stu s[2]={{1001,"张三",'m',15,"湖北武汉"},{1002,"李四",'m',18,"湖北孝感"}};
struct score{
int C;
int Java;
int english;
};
typedef struct studet{
int sno;
char name[10];
char* sex;
struct score sc;
}stu;
stu s1={1008,"张三",'m',80,100,100}; //嵌套赋值
#include
struct student{
int id;
char name[100];
char sex;
float sight;
};
typedef struct student stu;
void show(stu s);
void show1(stu* s1);
stu test();
int main(){
stu s1 ={1001,"张三风",'m',4.5};
show(s1);//传递结构体指针比传递结构体变量要节约时间和空间
show1(&s1);
stu s;
s = teset();
return 0;
}
void show(stu s){
printf("%d %s %c %f\n",s.id, s.name,s.sex,s.sight);
}
void show(stu* s1){
printf("%d %s %c %f\n",s1->id, s1->name,s1->sex,s1->sight);
}
stu test(){
stu s={1002,"李四",2.5}
return s;
}
写法与结构体类似,由有限个相同或不同的数据成员组成,其中所有的数据成员共用一块空间
联合体的大小用所占内存最大的数据成员的内存大小表示,要考虑内存对齐,默认对齐基数是所占内存最大的数据成员的大小
#include
union student{
int sno;
char name[10];
short age;
};
typedef union student stu;
int main(){
stu s;
printf("%p %p %p\n",&s.sno,&s.name,&s.age);//地址相同,说明共用一块空间
return 0;
}
只有最近使用的成员才有效
联合体与其他复杂数据类型的使用方法和结构体类似
#include
enum color {red,yellow,black,bule,pink};
int main(){
enum color c = red;
return 0;
}
泛型编程:不考虑数据类型,通过void* 来实现
回调函数(Callback Function):不需要用户主动的调用,当满足某个条件或发生某个事件后,这个函数自动调用。 主函数就是一个回调函数
回调函数可以通过指针来实现
内存操作函数:memcpy(拷贝内存),memset(设置内存的值),menmov(内存移动)等
#include
#include
int main(){
char str[]="hello,world";
char str1[20];
memcpy(str1,str,5);
str1[5]='\0';
printf("%s\n",str1);
return 0;
}
时间处理函数:time,localtime,ctime,gmtime,mktime
#include
#include
int main(){
time_t t =time(NULL);
struct tm* tt =NULL;
tt =localtime(&t);
printf("%d-%02d-%02d %02d:%02d:02d 星期%d\n",tt->tm_year+1900,tt->tm.mon+1,tt->tm.mday,tt->tm_hour,tt->tm_min,tt->tm_sec,tt->tm_wday);
printf("%s\n",ctime(&t));
return 0;
}
1.标准I/O:
标准输入,就是将数据写入标准输入缓冲区中,然后调用标准输入函数(比如:scanf,getchar等)将标准输入缓冲区中的数据取出;标准输出,将内存中的数据取出并存放到标准输出缓冲区中,当标准输出缓冲区满了或遇到特殊字符(’\n’)就会将缓冲区中的数据标准输出。
fflush()刷新标准输入(stdin)和输出(stdout)缓冲区。
2.文件I/O:
数据的读写都是从文件中进行,向文件中写入数据或从文件中读取数据都是根据文件指针的位置来读取或写入,打开文件后,文件指针在文件开头处,向文件中写入或读取n个字节的数据那么文件指针就会向文件末尾处移动n个字节,可以设置文件指针的位置来控制数据写入或读取的位置。
文件I/O处理函数:
fopen:·打开文件
打开模式 | 作用 |
---|---|
w | 只写,不存在就新建,存在就清空文件内容 |
w+ | 读写,如果不存在就新建,否则就打开文件,并清空 |
r | 只读,不存在就打开失败 |
r+ | 读写,不存在就打开失败,否则就会打开文件 |
a | 将文件指针移动到末尾,在文件末尾添加内容,不存在就新建 |
a+ | 读写,同上 |
t | 文本模式 |
b | 二进制模式 |
fputc:向文件中写入一个字符
fputs:向文件中写入一个字符串
fprintf:向文件中标准输出
fwrite:向文件中写入一块内存
fgetc:从文件中获取一个字符
fgets:从文件中获取一个字符串
fscanf:从文件中标准输入
fread:将文件中的数据原样读出
rewind:直接将指针移动到文件开头
ftell:获取文件指针的位置
fseek:设置文件指针的位置
fseek(fp,0,SEEK_SET);
fp是文件,0是偏移量,SEEK_SEK 文件开头 SEEK_CUR 当前位置 SEEK_END文件末尾
feof:判断文件指针是否到达文件末尾,需要将文件末尾标示符EOF读出
access:判断文件权限或是否存在
fclose:关闭文件
3.字符串I/O
1.字符串输入sscanf 可以将其他数据类型转换成字符串
2.字符串输出sprintf
将字符串转换成其他简单数据类型
atoi(将字符串转换成int)
atol,atof,strtod
实现某个功能的算法
程序由算法和数据结构来构成
衡量一个算法的优劣可以通过时间复杂度和空间复杂度
bsearch()函数的例子?
由大O记法来表示
时间复杂度可分为:常量级复杂度(0(1)),指数级复杂度 (O(n)),平方级复杂度(O(n2)),对数级复杂度(0(log(n)))等
时间复杂度的算法:
1.如果只是代码相加,那么就是常量级
2.如果有一层循环,那么就是(O(n))
3.如果有两层或以上的循环那么就是循环总数的最高次幂,并且舍弃掉最高次幂的常数项
数据结构可分为:
1.线性结构(1:1)
2.树形结构(1:n)
3.图形结构(n:m)
线性结构:
1.集合
2.线性表
3.栈
4.队列
线性表示采用线性结构的数据结构,线性表可以有两种实现方法:
1.链表存储(物理空间地址不连续,逻辑地址连续)
2.顺序存储(物理空间地址和逻辑地址都连续)
链式存储结构又称为链表,在c语言中链表通过结构体来实现
结构体包含数据域(存放数据)和指针域(存放下一个数据的地址)
节点:存放数据和指针
第一个节点称为头节点,最后一个节点称为尾节点
一个节点的前一节点称为这个节点前驱,后一个节点称为后继
头节点没有前驱,尾节点没有后继
一般情况下,头节点不存放数据,用来记录尾节点
head | ||||
---|---|---|---|---|
next |
#include
typedef int type;
struct NODE{
type data;
struct NODE* next;
}
typedef struct NODE node;
//初始化
void init(node** head){
*head = malooc(sizeof(node));
(*head)->next= NULL;
}
//插入数据(头插法,在头节点之后插入新节点)
void push_front(node* head,type data){
//创建新节点
node* new_node = malloc(sizeof(node));
//给新节点赋值
new_node->data = data;
new_node->next = head->next;
//头节点存放存放新节点的地址
head->next =new_node;
}
//尾插法(在尾节点之后插入新节点)
void push_back(node* head ,type data){
node* new_node = malloc(sizeof(node));
//给新节点赋值
new_node->data =data;
new_node->next =NULL;
//改变指向关系(即让原来的尾节点的指针域存放新节点的地址)
//找到原来的尾节点
while(head->next!=NULL)head->next;
head->next = new_node;
}
void show(node* head){
node* p =head->next;
while(p!=NULL){
printf("%d",p->data);
p = p->next;
}
}
//插入数据(中插法,在中间第pose个节点之后)
void insert(node* head,int n,type data){
int i=0;
node* p = head->next;
for(;i<n;i++){
if(p->next!=NULL){
p =p->next;
}else
return 1;
}
node* new_node = malloc(sizeof(node));
new_node->next = p->next;
p->next = new_node;
new_node->data = data;
}
//修改数据(通过数据修改)
int ch(node* head,type odata,type ndata){
while(head->next!=NULL && head->data!=old_data) head=head->next;
if(head->data ==odata){
head->data =ndata;
return 0;
}
return -1;
}
//修改数据(通过第pos个节点的数据修改)
int update_pos(node* head,innt pos,type new_data){
while(head->next!=NULL && pos>0){
head=head->next;
pos--;
}
if(pos==0){
head->data = data;
return 0;
}
return -1;
}
//获取长度
int get_len(node* head){
unsigned int size=0;
p=head->next;
while(p!=NULL){
size++;
p=p->next;
}
return size;
}
//删除
int erase(node* head, type data){
while(head->next!=NULL && head->next->data!=data)head=head->next;
head->next = head->next->next;
head->next->next = NULL;
free(head->next);
}
//排序
void sort(node* head,int n){
int i,j;
type temp;
node* p NULL;
for(i=0;i<n-1;i++){
node* p =head->next;
for(j=0;j<n-1-i;j++){
if(p->data>p->next->data){
temp=p->data;
p->data = p->next->data;
p->next->data = temp;
}
p=p->next;
}
}
}
void select_sort(node* head ,int n){
int i,j ;
type temp;
node* k=NULL;
node*p = NULL:
for(i=0;i<n;i++){
p=head->next;
k=p;
for(j=i+1;j<n;j++){
p=p->next;
if(p->data>k->data) k=p;
}
if(k!=head->next){
temp =head->next->data;
head->next->data = k->data;
k->data = temp;
}
head = head->next;
}
}
int main(){
node* l=NULL;
init(&l);
push_front(l,100);
push_front(l,200);
push_front(l,300);
push_back(l,400);
push_back(l,500);
push_back(l,600);
return 0;
}
一种操作受限的数据结构,数据的进栈和出栈都是一个出口,具有FILO和LIFO的特点
数据的入栈和出栈都是由栈顶指针决定的
栈顶(top)
栈底
入栈(push)和出栈(pop)
栈按照数据的存放形式分为:链栈和顺序栈
一种操作受限的数据结构,只能在队尾插入数据,只能在队头删除数据
队列按照数据的存放形式分为:链式队列和顺序队列
采用树形结构存放数据的数据结构
子树(subtree): 树的分支
节点(Node):存放数据的空间
双亲节点(prant node)
子节点(child): 左孩子 、右孩子 、第一个孩子…
子孙节点
祖先节点
兄弟节点
堂兄弟节点
节点的度:
1.树的度:节点的度的最大值
2.节点的度:节点拥有的子节点的数量
3.分支节点:度不为0的节点
4.叶子节点:度为0的节点
树的层次:
1.树的高度
2.树的深度
森林:由多棵树组成
二叉树(binary tree):一种特殊的树,每个节点最多只有两个子节点
二叉树的遍历方式:先序遍历,中序遍历,后序遍历,层序遍历
先序遍历:DLR,先遍历根节点,再遍历左子树,再遍历右子树。
中序遍历:LDR,先遍历左子树,再遍历根节点,在遍历右子树。
后序遍历:LRD,先遍历左子树,再遍历右子树,在遍历根节点。
根据先序遍历和中序遍历可以唯一确定一棵树,中序遍历和后续遍历可以唯一确定一棵二叉树,先序遍历和后续遍历不能唯一确定一个树。
二叉树的实现:顺序表,三叉链表
两种特殊的二叉树:满二叉树和完全二叉树
满二叉树:所有的叶子节点在同一层次,并且除了叶子节点外的其他节点都有两个子节点。
完全二叉树:节点连续的二叉树
满二叉树和完全二叉树的特点:
1.满二叉树的节点数为2的n次方-1,n为二叉树的高度,n>=0;
2.二叉树第k层的节点数最多为2的k-1次方,k>=1;
3.完全二叉树,叶子节点个数为n0,度为2的节点个数为n1,那么n0=n1+1;