IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> C++知识库 -> C语言学习(笔记) -> 正文阅读

[C++知识库]C语言学习(笔记)

C语言学习笔记

创建项目

vs2019创建C语言项目流程

  1. 新建项目,选中C++
  2. 选择空项目并填写项目名称和存储位置
  3. 找到右侧窗口的解决方案资源管理器,右键新建>添加>新建项,选择C++文件,把后缀名改为.c
  4. 创建成功

c语言文件后缀类型

  • .c - 源文件
  • .h - 头文件

stdio.h:standard input output 标准输入输出,程序中输入输出都要包含该头文件

main函数:主函数,程序的入口,一个文件中必须有且只能有一个

printf()函数:在控制台打印输出

scanf()函数:使用键盘输入

  • scanf("%d",&num);

    将输入的数值存入num的地址;

    &:取地址符号

注意:C语言的变量不能声明在函数的中间,要在开头就声明变量

数据类型

数据类型

  • char:字符数据类型,1字节
  • short:短整型,2字节,0~2^16-1
  • int:整型,4字节,取值范围 0~2^32-1
  • long:长整型,4/8字节
  • long long:更长的整型,8字节
  • float:单精度浮点数,4字节
  • double:双精度浮点数,8字节

计算机单位

  • 字节:Byte
  • 比特:bit
  • 1Byte=8bit

使用printf函数打印不同的数据类型,需要使用不同类型所对应的格式化符号。

char c = 'c';

printf("%c\n",c);

输出格式化符号

  • %c:字符
  • %d:整数
  • %f:单精度浮点数
  • %lf:双精度浮点数
  • %p:一个指针
  • %X:无符号十六进制数

变量

定义变量的方法:类型 变量名 = 值;

全局变量:定义在代码块之外的变量是全局变量

局部变量:定义在方法体内的变量是局部变量

当一个方法体内出现和全局变量相同的局部变量时,局部变量优先级更高

变量作用域

  • 局部变量的作用域在它所在的代码块内
  • 全局变量的作用域是整个工程

声明变量

a文件中定义的全局变量 int num 如果要想在b文件中使用,需要对全局变量num进行声明。

extern int num;

变量生命周期

  • 局部变量生命周期:进入作用域生命周期开始,出作用域生命周期结束
  • 全局变量生命周期:整个程序的生命周期

常量

C语言中的常量分为以下几种:

  • 字面常量,直接写一个数字就是字面常量

  • const修饰的常变量,使用const修饰的变量无法修改,如果修改编译器会报错

    const int num;

  • #define定义的标识符常量

    #define MAX 10

  • 枚举常量,枚举关键字 - enum

    enum Sex
    	{
    		MALE,//0
    		FEMALE//1
    	};
    

浮点数

注意事项:整型数字除于整数只会得到整数。如果要想得到浮点数,分母或分子要至少有一个为浮点数。

表达式

一个表达式是一系列运算符和算子的组合,用来计算一个值

  • 运算符(operator)是指进行运算的动作,比如加法运算符"+",减法运算符"-"
  • 算子(operand)是指参与运算的值,这个值可能是常数,也可能是变量,还可能是一个方法的返回值

运算符包括:+ - * / % 加减乘除取余

运算优先级

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2EdfTptO-1627649549126)(E:\MYZ\笔记\image\image-20210722165829292-16269443145161.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QC719dLZ-1627649549129)(E:\MYZ\笔记\image\image-20210722165939536.png)]

交换变量

两种解决思路:

  • 不使用其它的空间

    int a = 5;
    int b = 6;
    a = a+b;
    b = a%b;
    a = a-b;

  • 使用额外的空间

    int a = 5;
    int b = 6;
    int c = a;
    a = b;
    b = c;
    

复合赋值

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kBaj0NDo-1627649549131)(E:/MYZ/%E7%AC%94%E8%AE%B0/image/image-20210722173701467.png)]

递增递减运算符

  • count++ count = count + 1
  • count-- count = count -1

递增递减运算符前缀后缀

  • ++和–可以放在变量的前面,叫做前缀形式,也可以放在变量的后面,交后缀形式
  • a++的值是a加1以前的值,而++a的值是加了1之后的值,无论哪个,a自己的值都加了1
int main(){
	int a = 0;
	
	printf("a++=%d\n",a++);
	printf("a=%d\n",a);
	
	a = 0;
	printf("++a=%d\n",++a);
	printf("a=%d\n",a);
	
	return 0;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oP4jo8Jb-1627649549136)(E:/MYZ/%E7%AC%94%E8%AE%B0/image/image-20210722174715940.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tlqjd7Ez-1627649549138)(E:/MYZ/%E7%AC%94%E8%AE%B0/image/image-20210722174839223.png)]

条件判断语句

if else判断语句

  • if(){}else{} 如果满足判断条件,则执行代码块1,否则执行代码块2

    if(判断条件){
    	代码块1;
    }else{
    	代码块2;
    }
    
  • if(){}else if(){}else{} 如果满足判断条件1,执行代码块1,否则再次进行判断条件2是否满足,如果满足执行代码块2,不满足执行代码块3。if后面可以跟无数个else if语句,用法一样

    if(判断条件1){
    	代码块1;
    }else if(判断条件2){
    	代码块2;
    }else{
    	代码块3;
    }
    
  • if()…else… 不带大括号的条件判断语句,不带大括号的情况下,if和else后面的一行是执行语句

    if(判断条件)	代码块1;else	代码块2;
    

关系运算

计算两个值之间的关系,叫做关系运算

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tgNNY63a-1627649549141)(E:/MYZ/%E7%AC%94%E8%AE%B0/image/image-20210722180246672.png)]

结果

比较结果有两种,true或false。即条件成立为true,条件不成立为false

优先级

关系运算优先级比数学运算符低,比赋值运算高。

实例:找零计算器

#include<stdio.h>int main(){	float price = 0;	float bill = 0;		printf("请输入账单金额:");	scanf("%f",&bill);	printf("请输入支付金额:");	scanf("%f",&price);		if(price-bill<0){		printf("提示:支付金额不足,还差%f元\n",bill-price);	}else if(price-bill>0){		printf("找零%f元\n欢迎下次光临",(price-bill));	}else{		printf("无需找零,欢迎下次光临");	}	return 0;} 

嵌套的if else判断语句

当if的条件满足或者不满足的时候要执行的语句也可以是一条if或if-else语句,这就是嵌套的if语句

if(条件1){	if(条件2){		执行体1;	}else{		执行体2;	}}else{	if(条件3){		执行体3;	}else{		执行体4;	}}

else的匹配规则:总是和最近的else进行匹配

注意事项

  • 在if或else后面总是用{}
  • 即使是只有一条语句的时候

if语句常见的错误总结

  • 忘带大括号
  • if后面多加分号
  • 错误使用==和=
  • 使人困惑的else

代码风格

  • 在if和else之后必须加上大括号形成的语句块
  • 大括号内的语句缩进一个tab的位置

switch分支语句

switch语句可以看做是一种基于计算的跳转,计算控制表达式的值后,程序会跳转到相匹配的case(分支标号)处。分支标号只是说明switch内部位置的路标,在执行完分支中的最后一条语句后,如果后面没有break,就回顺序执行到下面的case中去,直到遇到一个break,或者switch结束为止

switch(控制表达式){		case 常量:			语句;			break;		case 常量:			语句;			break;		...		default:			执行体n+1;	}

可以把switch中的语句看做是一系列连续的语句,相匹配的case可以看做一个入口,如果case中没有break,那么语句会一直往下执行,直到遇到break或default执行完毕

实例:成绩评分

int main(){	int score;	printf("请输入你的成绩:");	scanf("%d",&score);	score = score/10;	switch(score){		case 10:			printf("你的成绩评分为:S\n");			break;		case 9:			printf("你的成绩评分为:A\n");			break;		case 8:			printf("你的成绩评分为:B\n");			break;		case 7:			printf("你的成绩评分为:C\n");			break;		case 6:			printf("你的成绩评分为:D\n");			break;		default:			printf("你的成绩评分为:E\n");	}	return 0;}

循环

while循环

  • 当while循环的循环条件满足时,会重复执行大括号中的循环体语句。当循环条件不满足时推出while循环
  • 循环执行之前会有一次是否满足循环条件的判断,所以有可能循环一次也没有被执行
  • 条件成立时循环继续的条件
while(循环条件){	循环体;}

实例:使用while循环判断数字的位数

int main(){	int i = 0;	int count = 1;	printf("请输入一个数字:");	scanf("%d",&i);	i /= 10;	while(i>0){		count++;		i /= 10;	}	printf("这个数字一共 %d 位",count);	return 0;} 

do while循环

在进入循环的时候不做检查,而是在执行完一轮循环体的代码之后,再来检查循环的条件是否满足,如果满足则继续下一轮循环,不满足则结束循环

do{	循环体;}while(循环条件);

while循环和do while循环的区别

  • while循环是先判断循环条件,再决定是否执行循环体
  • do while循环则是先执行循环体,在判断是否满足条件

实例:使用do while循环来判断数字的位数

int main(){	int i;	int count = 0;	printf("输入一个数字:");	scanf("%d",&i);	do{		i /= 10;		count++;	}while(i>0);	printf("你输入的是%d位数字",count);	return 0;}

注意:while循环的循环体内要有跳出循环的机会,否则将永远无法离开循环,成为死循环

程序的调试

  • 测试程序常使用边界数据,如有效范围两端的数据、特殊的倍数等
  • 在程序适当的地方插入printf来输出变量的内容

猜数字游戏

使用随机数生成函数rand()需要引入stdlib.h包,为了每次生成的随机数不一样,我们需要生成随机数种子,我们使用时间来帮助生成随机数种子,因此要引入time.h包。

生成随机数种子方法:

srand((unsigned)time(NULL)); 
#include<stdio.h>#include<stdlib.h>#include<time.h>//猜数字游戏int main(){	int i = 0;	int a = 0;	int c = 0;	srand((unsigned)time(NULL)); 	i = rand()%100;	printf("猜数字游戏,请输入1-100的数字\n"); 	do{		printf("输入数字:");		scanf("%d",&a);		c++;		if(a>i){			printf("太大了\n");		}else if(a<i){			printf("太小了\n");		}	}while(a!=i);	printf("恭喜你猜对了,使用了%d次机会",c);	return 0;} 

整数逆序

int main(){	int i = 1234567800;	int ret = 0;	int t = 0;	while(i>0){		ret = i%10;		t = t*10 + ret;		i /= 10; 		printf("%d",ret);		}	//printf("%d",t);	return 0;}

for循环

for循环的运行步骤为

  1. 判断条件是否满足,满足:执行循环体,不满足:退出for循环

  2. 执行完循环体后,执行改变动作

  3. 再次进行循环条件的判断

    重复以上操作…

  4. 直到判断条件为假,退出for循环

for(初始值;判断条件;改变动作){	循环体;}

for循环求阶乘

int main(){	int n = 3;//求n的阶乘	int fact = 1;	int i;	for(i=2;i<=n;i++){		fact *= i;	}	printf("%d",fact);	return 0;}

for循环改造为while循环,只需要将for循环的初始值放在外面,判断条件作为while循环的退出条件,改变初始值的操作放在while循环的循环体内,例如:

int main(){	int n = 3;//求n的阶乘	int fact = 1;	int i=2;	while(i<=n){		fact *=i;		i++;	}	printf("%d",fact);	return 0;}

几类循环的使用场景

  • for:有固定循环次数
  • do while:必须先执行一次
  • while:其他情况

跳出循环

  • break:跳出循环
  • continue:跳过这轮循环剩下的语句,进入下一轮循环
  • goto:可以直接到达程序指定的位置。

break和continue都只能跳出当前层的循环,如果我们需要跳出多层循环,有两种方法,使用break接力或使用goto语句。

演示

#include<stdio.h>//break接力和金手指goto语句 int main(){	int a=0;	int b=0;	int c=0;//在break接力方法中,我们使用c作为标志位 	for(a=1;a<5;a++){		for(b=1;b<5;b++){			//我们假设当a=3时跳出所有循环 			if(a==3){				c=1;				break;			}		}		if(c==1){			break;		}		printf("%d",a);	}	//goto语句可以跳转到代码中指定的地方	 for(a=1;a<5;a++){		for(b=1;b<5;b++){			//我们假设当a=3时跳出所有循环 			if(a==3){				goto out;			}		}		printf("%d",a);	}	out:	return 0;} 

循环嵌套

  • 循环里面还是循环

练习

练习:输出100以内的素数

#include<stdio.h>//练习:输出100以内的素数int main(){	int s=2;	while(s<=100){		int sign = 0;//标志,0为素数,1不是素数		int i = 0;		for(i=2;i<s;i++){			if(s%i==0){				sign = 1;				break;			}		}		if(sign==0){			printf("%d ",s);		}		s++;	}	return 0;} 

练习:凑硬币,使用1角、2角、5角凑出指定的金额

#include<stdio.h>//练习:使用1角,2角,5角凑指定的金额(可以是小数)int main(){	int one;	int two;	int five;	float m;	printf("请输入需要凑的金额(元):");	scanf("%f",&m);	for(five=0;five<=m*10/5;five++){		for(two=0;two<=m*10/2;two++){			for(one=0;one<=m*10;one++){				if(five*5+two*2+one==m*10){					printf("%d个5角 %d个2角 %d个1角\n",five,two,one);				}			}		}	}	return 0;} 

练习:1+1/2+1/3+…+1/n

#include<stdio.h>int main(){	int i=0;	int n=0;	double sum;	printf("求1到1/n的和,输入:");	scanf("%d",&n);		for(i=1;i<=n;i++){		sum+=1.0/i;	}	printf("1到1/%d的和为:%f",n,sum);}

练习:1-1/2+1/3-1/4+…+1/n

#include<stdio.h>//练习:1-1/2+1/3-1/4+...+1/nint main(){	int i=0;	int n=0;	double sum;	printf("求1到1/n的和,输入:");	scanf("%d",&n);		for(i=1;i<=n;i++){		if(i%2==0){			sum-=1.0/i;		}else{			sum+=1.0/i;		}	}	printf("1到1/%d的加和减为:%f",n,sum);}

练习:正序分解整数

#include<stdio.h>//练习:正序分解整数int main(){	int x = 0;	int temp= 0;//临时容器,用于保存x的初始值	int size = 1;//输入数字长度	printf("输入:");	scanf("%d",&x);	temp=x;	while(x>9){		size*=10;		x/=10;	}	do{		printf("%d ",temp/size);		temp=temp%size;		size/=10;	}while(size>0);	return 0;}

练习:求两个整数的最大公约数

#include<stdio.h>//求两个整数的最大公约数 int main(){	int a = 0;	int b = 0;	int min;	int ret = 1;	int max = ret;	printf("输入两个数:");	scanf("%d %d",&a,&b);		if(a>b){		min = b;	}else{		min = a;	}	for(ret = 1;ret<=min;ret++){		if(a%ret==0){			if(b%ret==0){				if(ret>max){					max = ret;				}			}		}	}	printf("%d 和 %d 的最大公约数为:%d",a,b,max);	return 0;}

补充:使用辗转相除法求两个整数的最大公约数

#include<stdio.h>//使用辗转相除法求两个整数的最大公约数 /*	如果b等于0,计算结束,a就是最大公约数	否则,计算a除以b的余数,让b等于b,而b等于那个余数	回到第一步 	a  b  t	12 18 12	18 12 6	12 6  0	6  0*/int main(){	int a = 0;	int b = 0;	int t;	printf("输入两个数:");	scanf("%d %d",&a,&b);		while(b!=0){		t = a%b;		a = b;		b = t;	}	printf("最大公约数为:%d",a,b,a);	return 0;}

练习:输出指定条件的整数集

#include<stdio.h>/*	给定不超过6的整数A,考虑从A开始的连续4个数字,请输出所有由他们组成的无重复数字的3位数	要求:从小到大输出,每行6个整数,行末不能有空格 */ int main(){	int a;	int cnt=0;	int i,j,k;	printf("输入一个数:");	scanf("%d",&a);	if(a>6){		printf("输入数字超过6");		return 0;	}	for(i=a;i<=a+3;i++){		for(j=a;j<=a+3;j++){			for(k=a;k<=a+3;k++){				if(i!=j && j!=k &&  i!=k){					printf("%d%d%d",i,j,k);					cnt++;					if(cnt%6==0){						printf("\n");					}else{						printf(" ");					}				}			}		}	}	return 0;}

练习:水仙花数

#include<stdio.h>/*	给定一个数字n,水仙花数是每位上的数字的n次幂之和等于它本身	要求输出3<=N<=7 */int main(){	int x;									//x是输入的位数 	int i=1;	int first = 1;	scanf("%d",&x);	while(i<x){		first*=10;							//输入是4时,输出1000 		i++;	}	i=first;	while(i<first*10){ 						//加入输入是3 i=100 first*10=1000 输出100-999的水仙花数 		int t = i; 							//临时变量t,用来记录该轮循环的i的值 		int sum = 0; 						//如果i的每一位的n次幂,等于sum,则说明这个数是水仙花数 		while(t!=0){ 						//先取出数字每一位的值 			int d = t%10;			int p = 0;			int a = 1;			t /= 10;			while(p<x){				a *= d;				p++;			}			sum +=a;		}		if(sum==i){							//判断每一位的n次幂的和与初始值是否相等 			printf("%d\n",i);		}		i++;	}	return 0;} 

练习:打印九九乘法表

#include<stdio.h>//练习:九九乘法表 int main(){	int i = 0;	int j = 0;		for(i=1;i<10;i++){		for(j=1;j<=i;j++){			printf("%d*%d=%d ",j,i,i*j);		}		printf("\n");	}} 

练习:求序列前n项和

#include<stdio.h>

//求序列前n项和 2/1+3/2+5/3...

int main(){
	int i,j;
	double k=2;
	double p=1;
	double sum=0;
	printf("input:");
	scanf("%d",&i);
	
	for(j=1;j<=i;j++){
		sum+=k/p;
		double t=k;
		k = k+p;
		p = t;
	}
	printf("output:%f",sum);
	return 0;
}  

数据类型(翁帆)

  • 整数

    char、short、int、long、long long

  • 浮点数

    float、double、long double

  • 逻辑

    bool

  • 指针

  • 自定义类型

sizeof

  • 是一个运算符,给出某个类型或变量在内存中所占据的字节数

负数的表达

在c语言中,负数以补码的形式来表达。

例如:char类型的数据,00000000-111111111 表示的是从-128~127

1000000011111111表示的是-128 -1,当这个数据的二进制最高位为1时,被认为是以补码的形式表示的负数。

如果不想要表示负数,可以在数据生命前加unsigned关键字

当我们使用代码来验证时,a输出的是-1,b输出的是255

#include<stdio.h>int main(){	char a = 255;	unsigned char b = 255;	printf("%d\n",a);	printf("%d\n",b);	return 0;}

整数是以纯二进制方式进行计算的

  • 11111111+1 ===> 100000000 ===> 0
  • 01111111+1 ===> 10000000 ===> -128
  • 10000000-1 ===> 01111111 ===> 127

8进制和16进制

  • 一个以0开始的数字字面量是8进制
  • 一个以0x开始的数字字面量是16进制
  • %0用于8进制,%x用于16进制
  • 8进制和16进制只是如何把数字表达为字符串,与内部如何表达数字无关

选择整数类型

为什么整数要有那么多种?

  • 为了准确表达内存,做底层程序的需要

没有特殊需要,就选择int

  • 现在的CPU的字长普遍是32位或64位,一次内存读写就是一个int,一次计算也是一个int,选择更短的类型不会更快,甚至可能更慢
  • 现代的编译器一般会设计内存对齐,所以更短的类型实际在内存中有可能也占据一个int的大小

unsigned与否只是输出的不同,内部计算是一样的

浮点类型

  • float 4字节 字长32位 scanf:%f printf:%f,%e
  • double 8字节 字长64位 scanf:%lf printf:%f,%e
  • float类型只有小数点后7位是有效地,double类型小数点后15位是有效的,超过有效位的数值会出现误差

%e 表示数值以科学记数法表达

浮点数输出精度

  • 在%和f之间加上.n可以指定输出小数点后几位,这样的输出是做四舍五入

例如:输出结果保留两位小数

#include<stdio.h> int main(){	double d = 2;	printf("%.2f",d);	return 0;}//输出结果:2.00

超过范围的浮点数

  • printf输出inf表示超过范围的浮点数:±∞
  • printf输出nan表示不存在的浮点数
  • 无穷大不能用整数来表示,只能在浮点数的运算中出现

浮点运算的精度

  • 带小数点的字面量是double而不是float
  • float需要用f或F后缀来表明身份

浮点数的内部表达

  • 浮点数在计算时是由专用的硬件部件实现的
  • 计算double和float所用的部件是一样的

选择浮点数类型

  • 如果没有特殊需要,只是用double
  • 现代CPU能直接对double做硬件运算,性能不会比float差,在64位的计算机中,数据存储的速度也不会比float慢

字符类型

char是一种整数,也是一种特殊的类型:字符。这是因为

  • 用单引号表示的字符字面量:‘a’,‘1’
  • ''也是一个字符
  • printf和scanf里用%c来输出字符

字符的输出输出

如何输入’1’这个字符给char c?

#include<stdio.h> int main(){	char c;	scanf("%c",&c);	//以整数类型输出 	printf("%d\n",c);	//以字符类型输出 	printf("%'c'\n",c);		return 0;}//输入:1		输出结果为:49	'c'

字符计算

  • 一个字符加一个数字得到ASCII码表中那个数之后的字符
  • 两个字符的减,得到它们在表中的距离

大小写转换

  • 字母在ASCII表中是顺序排列的
  • 大写子母和小写字母是分开排列的,并不在一起
  • ‘a’-'A’可以得到两个字母在ASCII表中的距离

逃逸(转义)字符

用来表达无法打印出来的控制字符或特殊字符,它由一个反斜杠“\”开头,后面跟上另一个字符,这两个字符合起来,组成一个字符

  • \b 回退一格
  • \t 到下一个表格位
  • \n 换行
  • \r 回车
  • \" 双引号
  • \’ 单引号
  • \\ 反斜杠本身

自动类型转换

当运算符的两边出现不一致的类型时,会自动转换成较大的类型

  • 大的意思是能表达的数的范围更大
  • char->short->int->long->long long
  • int->float->double

强制类型转换

要把一个量强制转换成另一个类型,通常是把较大的类型强制转换成较小的类型

  • (类型)值
  • 强制类型转换的优先级高于四则运算

此时需要注意安全性问题,因为小的变量有时候无法表达大的量

逻辑类型bool

必须首先导入头文件#include<stdbool.h>,之后才可以使用bool和true、false。但是在c语言中true和false依然是用整数1、0来表示,所以printf输出时依然使用%d

逻辑运算

逻辑运算符

逻辑运算是对逻辑量进行的运算,结果只有0或1。逻辑量是关系运算或逻辑运算的结果。

  • ! 逻辑非 !a 如果a是true结果就是false,如果a是false结果就是true
  • && 逻辑与 a&&b 如果a和b都是true,结果就是true;否则就是false
  • || 逻辑或 a||b 如果a和b一个是true,结果就是true;两个都是false,结果为false

优先级

  • ! > && >||

短路

逻辑运算是自左向右进行的,如果左边的结果已经能够决定结果了,就不会做右边的计算。

条件运算

条件运算符

  • count=(count>20)?count-10:count+10;
  • 结果=条件?条件满足时操作:条件不满足时操作

优先级

  • 条件运算符的优先级高于赋值运算符,但是低于其它运算符

函数

什么是函数?

  • 函数是一块代码,接收零个或多个参数,做一件事情,并返回零个或一个值
  • 可以先想象成数学中的函数y=f(x)

函数结构

返回值类型 函数名(参数){	函数体;}//当函数无返回值时,返回值类型使用void;有返回值类型时,需要在函数体中return对应类型的返回值

调用函数

//函数名(参数);#include<stdio.h>void cheer(){	printf("cheer\n");}//函数调用 int main(){	cheer();	return 0;}

函数原型

上面的函数在main方法的前面,如果自定义函数要放在main方法的后面,需要在程序的开始声明函数的原型。否则有可能会报错。

  • 函数头,一份好结尾,就构成了函数的原型
  • 函数原型的目的是告诉编译器这个函数长什么样子(名称、参数、返回类型)
//函数名(参数);#include<stdio.h>void cheer();//函数声明//函数调用 int main(){	cheer();	return 0;}void cheer(){	printf("cheer\n");}

参数传递

  • 调用函数时给的值与参数的类型不匹配是C语言传统上最大的漏洞
  • 编译器总是悄悄替你把类型转换好,但是这很可能不是你所期望的
  • 后续的语言,C++、Java在这方面更严格
  • C语言在调用函数时,只会传值给函数

局部变量

  • 函数的每次运行,就产生了一个独立的变量空间,在这个空间中的变量,是函数的这次运行所独有的,称为局部变量
  • 定义在函数内部或方法内部的变量都是局部变量
  • 参数也是局部变量

变量的生存周期

  • 函数开始运行到函数运行结束

变量的作用域

  • 局部变量的作用域在函数内部

数组

c语言支持数组数据结构,它可以存储一个固定大小的相同类型元素的顺序集合。数组中的特定元素可以通过索引访问,第一个索引值为0

声明数组

数组类型 数组名[数组容量]

初始化数组

数组类型 数组名[数组容量] = {元素1,元素2...}

需要注意的是,如果初始化数组时数组容量不为空,则数组的元素个数不能大于数组容量。

//初始化数组int arr[5] = {1,2,3,4,5};	//有初始容量int arr[] = {1,2,3,4,5,6};	//无初始容量

访问数组元素

数组元素可以通过数组名加索引进行访问。元素的索引放在方括号内

printf("%d",arr[0]);//输出:1

二维数组

  • int a[3][5];
    
  • 通常可以理解为a是一个3行5列的矩阵

二维数组的遍历

使用一个双层for循环进行遍历。例如:

#include<stdio.h>
int main(){
	//二维数组初始化 
	int a[2][2] = {1,2,3,4};
	int i,j;
	//二维数组遍历 
	for(i=0;i<2;i++){
		for(j=0;j<2;j++){
			printf("%d ",a[i][j]);
		}
	} 
} 

数组变量是特殊的指针

  • 数组变量本身表达地址,所以,int a[10];int *p=a; //无需用取地址符&
  • 但是数组的单元表达的是变量,需要用&取地址,a==&a[0]
  • []运算符可以对数组做,也可以对指针做:p[0]<==>a[0]
  • *运算符可以对指针做,也可以对数组做:*a=25;
  • 数组int b[] 等价于 int *const b

枚举enum

枚举是C语言中的一种基本数据类型,它可以让数据更简洁,更易读。语法:

enum 枚举名{
	枚举元素1,枚举元素2,...
}枚举变量;

练习:输出张三 李四 王五三个人的年龄

#include<stdio.h>

int main(){
	enum Name{
		zhangsan=21,lisi=22,wangwu=23
	}name;
	printf("张三的年龄:%d\n",name = zhangsan);
	printf("李四的年龄:%d\n",name = lisi);
	printf("王五的年龄:%d\n",name = wangwu);
	return 0;
}

取地址符号&

  • scanf("%d",&i)里的&
  • 获得变量的地址,它的操作数必须是变量

练习:输出某个变量的地址

#include<stdio.h>

int main(){
	int i;
	printf("%p",&i);
	return 0;
}

指针变量

  • 指针变量的值是内存的地址,是具有实际值的变量在内存中存放的地址
  • 普通变量的值是实际的值

指针变量的声明

int i;
int* p = &i;
int* p,q;//指针变量p,整数变量q
int  *p,q;//指针变量p,整数变量q
//指针变量的声明推荐星号离变量近
int *p;

在上面声明指针变量的例子中,我们这样描述:指针变量p指向整形变量i,因为p存放的是i在内存中的地址

使用指针输出地址变量

#include<stdio.h> 

//使用指针输出变量地址
int main(){
	int i=0;
	int *p = &i;
	printf("%p",p);
	return 0;
} 
//输出:000000000062FE14

访问地址符号*

  • *是一个单目运算符,用来访问指针的值所表示的地址上的变量
  • 可以做右值也可以做左值 int k = *p; *p = k+1;

指针访问和修改变量

#include<stdio.h>

//指针访问和修改变量 
int main(){
	int i=0;
	int *p = &i;
	//访问变量 
	printf("使用指针访问,i:%d\n",*p);
	//修改变量
	printf("使用指针修改变量后,i:%d\n",*p=26); 
	return 0;
} 
//输出:0	26

练习:输出一个整形数组的最大值和最小值

#include<stdio.h>

void swap(int a[],int len,int *min,int *max);
int main(){
	int arr[6]={2,45,6623,7,43,2};
	int min,max;
	minmax(arr,6,&min,&max);
	printf("arr最小值:%d\n",min);
	printf("arr最大值:%d\n",max);
}
void minmax(int a[],int len,int *min,int *max){
	int i;
	*min=a[0];
	*max=a[0];
	for(i=0;i<len;i++){
		if(*min>a[i]){
			*min=a[i];
		}
		if(*max<a[i]){
			*max=a[i];
		}
	}
}
//输出:arr最小值:2	arr最大值:6623

指针最常见的错误

  • 定义了指针变量,还没有指向任何变量,就开始使用指针

c语言求数组长度

求c语言中数组的长度,我们可以先使用sizeof方法获取数组的所占的字节数,然后再除以数组元素的长度,即得到数组的长度

#include<stdio.h>

int main(){
	int a[3]={1,2,3};
	int size = sizeof(a)/sizeof(a[0]);
	printf("%d\n",size);
	return 0;
}
//输出:3

使用指针获取数组指定元素的值

#include<stdio.h>
int main(){
	int arr[6]={1,2,3,4,5,6};
	int *p = &arr[0];
	printf("数组arr第2个元素值:%d\n",*(p+1));
	printf("数组arr第3个元素值:%d\n",arr[0]+2);
	return 0;
}

数组变量是特殊的指针

  • 数组变量本身表达地址,所以,int a[10];int *p=a; //无需用取地址符&
  • 但是数组的单元表达的是变量,需要用&取地址,a==&a[0]
  • []运算符可以对数组做,也可以对指针做:p[0]<==>a[0]
  • *运算符可以对指针做,也可以对数组做:*a=25;
  • 数组int b[] 等价于 int const *b

指针与const

int i;
const int *p1 = &i;
int const *p2 = &i;
int *const p3 = &i;
//分别是什么意思?

判断哪个被const了的标志是const在*的前面还是后面。如果const在*前面,表示它所指向的东西不能被修改。如果const在*后面,表示指针不能被修改。

因此,前两种const在*前面,表示两个指针所指向的东西不能修改。第三种const在*后面,表示指针不能被修改。

转换

  • 总是可以把一个非const的值转换成const的

    void f(const int *x);
    int a = 15;
    f(&a);
    const int b = a;
    
  • 当要传递的参数的类型比地址大的时候,这是常用的手段:既能用比较少的字节传递值给参数,又能避免函数对外面的变量的修改

const数组

  • const int a[] = {1,2,3,4,5,6};
  • 数组变量已经是const的指针了,这里的const表明数组的没个单元都是const int
  • 所以只能通过初始化进行赋值

保护数组值

  • 因为把数组传入函数时传递的是地址,所以那个函数内部可以修改数组的值

  • 为了保护数组不被函数破坏,可以设置参数为const

    int sum(const int a[],int length);
    //这样,传入的数组就无法被修改
    

指针运算

  • 给一个指针加1表示要让指针指向下一个变量

    int a[10];
    int *p = a;
    *(p+1) ——> a[1]
    *(p+n) <==> a[n]	//等价
    
  • 如果指针不是指向一片连续分配的空间,如数组,则这种运算没有意义

*p++

  • 取出指针p所指的那个数据来,之后顺便把p移到下一个位置去
  • *的优先级虽然高,但是没有++高
  • 常用于数组类的连续空间操作
  • 在某些CPU上,这可以直接被翻译成一条汇编指令
#include<stdio.h>
//遍历整型数组 
int main(){
	int a[] = {1,2,3,4,5};
	int i=0;
	for(i=0;i<sizeof(a)/sizeof(a[0]);i++){
		printf("%d ",a[i]);
	}
	return 0;
}

指针的类型

  • 无论指针指向什么类型,所有的指针的大小都是一样的,因为都是地址
  • 指向不同类型数据的指针最好不要直接互相赋值,避免不必要的错误

指针的类型转换

  • void*表示不知道指向什么类型数据的指针,计算时和char*相同

  • 指针也可以强制转换类型

    int *p = &i;
    void *q = (void\*)p;
    
  • 指针的转换类型没有改变p指向的变量的类型,只是改变了指针的类型

动态分配内存

在c99之后,直接使用变量定义数组所占空间,但是在之前需要使用malloc函数申请指定大小的空间,并且使用完后要使用free释放空间(谨记)

int *a = (int*)malloc(n*sizeof(int));
free(a);

练习:使用malloc函数构建数组

#include<stdio.h>
#include<stdlib.h>
//使用malloc函数动态构建数组 
int main(){
	int n = 5;
	int i = 0;
	int s = 0;
	int *a = (int*)malloc(n*sizeof(int));
	for(i=0;i<n;i++){
		scanf("%d",&a[i]);
	}
	for(i=0;i<n;i++){
		printf("%d ",a[i]);
	}
	free(a);
	return 0;
}

练习:测试可以向系统申请的最大空间

#include<stdio.h>
#include<stdlib.h>
//测试可以向系统申请的最大空间
int main(){
	int *p;
	int c = 0;
	while((p=malloc(100*1024*1024))){	//一次循环100M 
		c++;
	}
	printf("系统最大申请空间:%dM",100*c);
	return 0;
} 

字符串

  • 以0结尾的一串字符,0和’\0’是一样的
  • 0标志字符串的结束,但它不是字符串的一部分,计算字符串的长度时不包含这个0
  • 字符串以数组的形式存在,以数组或指针的形式访问
  • string.h里有很多字符串的函数

字符串变量

  • char *str = “Hello”;
  • char word[] = “Hello”;
  • char line[10] = “Hello”;

字符串常量

  • “Hello”,它会被编译器变成一个字符数组放在某处,这个数组的长度为6,其中字符串内容占5个字节,但是结尾还有表示结束的0
  • 两个相邻的字符串常量会被自动连接起来

总结

  • C语言的字符串是以数组的形态存在的
  • 不能用运算符对字符串做运算
  • 通过数组的方式可以遍历字符串
  • C语言的字符串特殊的地方是字符串常量可以用来初始化字符数组

创建字符串

  • 使用指针创建字符串:char *s = “hello”;等价于 const char *s = “hello”;
  • 使用数组创建字符串:char s[] = “hello”;
  • 它们的区别在于,使用指针创建的字符串只能读,使用数组创建的字符串不仅可以读,而且可以修改。
#include<stdio.h>int main(){	char s[] = "Hello";	printf("%c\n",s[0]);	printf("Hello字符串的长度:%d,因为它还有末尾表示字符串的标志位0,也占一个字节",sizeof(s)/sizeof(char));	return 0;} 

char*是否是字符串?

  • 字符串可以用char*的形式表示
  • char*并不一定是字符串,有可能是指向字符的指针
  • 只有char*指向的字符数组有结尾的0时,才能说它所指的是字符串

字符串输入输出

char string[8];scanf("%s",string);printf("%s",string);

字符串可以用%s来输入和输出

#include<stdio.h>
int main(){
	char s[6];
	//scanf键盘输入
	scanf("%5s",s);
	printf("%s",s); 
	return 0;
}

安全的输入

如何避免输入的字符串数量越界问题,我们可以在%s中间加入最大字符的数量,来避免这个问题

字符串数组

两种方式

  • char a\[][10] = {"Hello","World"};
    
  • char *a[] = {"Hello","World"};
    

第一种方式是指,创建一个字符串数组,每个元素的最大长度不能超过10。

第二种方式是指,创建一个字符串数组,每个元素都是一个指向一个字符串的指针变量。

单字符输入输出

putchar

  • int putchar(int c);
    
  • 向标准输入写一个字符

  • 返回写了几个字符,EOF (-1) 表示写失败

#include<stdio.h>

int main(){
	int ch;
	while((ch = getchar())!=EOF){
		putchar(ch);
	}
	return 0;
}

字符串函数

strlen

返回字符串的长度;

int strLen(char str[]){
	int index = 0;
	while(str[index]!="\0"){
		index++;
	}
	return index;
}

strcmp

int strcmp(const char *s1,const char *s2);

比较两个字符串,返回:

  • 0:s1==s2
  • 1:s1>s2
  • -1:s1<s2

strcpy

  • char *strcpy(char \*restrict dst,const char\* restrict src);
    
  • 把src的字符串拷贝到dst,restrict表明src和dst不重叠

  • 返回dst

#include<stdio.h>
#include<string.h>

int main(){
	char a[5];
	char b[5] = {"abc"};
	strcpy(a,b);	//将字符串数组b拷贝给a 
	printf("%s",a);
	return 0;
}

strcat

  • char *strcat(char *restrict s1,const char *restrict s2);
    
  • 把s2拷贝到s1的后面,形成一个长的字符串,返回s1。过程会改变s1

  • s1必须具有足够的空间

#include<stdio.h> 
#include<string.h>

int main(){
	char a[10] = {"ab"};
	char b[3] = {"cde"};
	strcat(a,b);
	printf("%s",a);
}

安全问题

strcpy和strcat都可能出现安全问题,如果目的地没有足够的空间使用这两个函数就会出现越界问题。

安全版本

int strncmp(s1,s2,n);		//字符串比较函数
char *strncpy(s1,s2,n);		//n表示s1所能接受的最大字符数量
char *strcat(s1,s2,n);		//n表示s1所能接受的最大连接字符数量

strchr

strchr函数是在一个字符串中搜索一个字符,如果有就返回该字符的地址,如果没有就返回NULL

#include<stdio.h>
#include<string.h>

int main(){
	char s[] = "hello";
	char *p = strchr(s,'l');
	printf("%s",p);
	char *q = (char*)malloc(strlen(p)+1);
	strcpy(q,p);
	printf("%s",q);
	free(q);
	return 0;
}

strstr

strstr(s1,s2);	//在一个字符串中找另一个字符串

结构类型

声明结构形式

struct 结构名{
	结构体;
}变量名;
//在结构体声明中,结构名或变量名可以忽略,但是不能同时忽略,因此就出现了三种声明形式
struct data{	//单纯声明结构体,不带变量名
    int year;
};
struct{			//不带结构体名,直接生成变量,这种形式可以理解为一次性生成两个变量
    int year;
}y1,y2;
struct data{	//带结构体名,带变量名
    int year;
}y1,y2;

结构变量赋值的三种方式

#include<stdio.h>

struct data{
	int year;
	int month;
	int day;
};
int main(){
	//第一种结构变量赋值方式 
	struct data today;
	today.year = 2021;
	today.month = 7;
	today.day = 29;
	
	//第二种结构变量赋值方式
	struct data t1 = {2020,7,29};
	
	//第三种结构变量赋值方式
	struct data t2 = {.year=2019,.month=7};
	
	printf("today:%d年%d月%d日\n",today.year,today.month,today.day);
	printf("t1:%d年%d月%d日\n",t1.year,t1.month,t1.day);
	printf("t2:%d年%d月%d日\n",t2.year,t2.month,t2.day);
	return 0;
}

结构变量取地址

#include<stdio.h>

struct data{
	int year;
	int month;
	int day;
};
int main(){
	struct data t1 = {1,2,3};
	struct data *p = &t1;
	printf("%p",p);
    return 0;
}

结构与函数

结构作为函数参数

int funcTion(struct data d)
  • 结构体可以作为参数的值传入函数,并不是传结构变量本身
  • 这时候是在函数内部新建一个结构变量,并赋值调用者的结构的值
  • 结构体也可以作为返回值

结构指针

  • 用->表示指针所指的结构变量中的成员
#include<stdio.h>

struct data{
	int year;
	int month;
	int day;
}today;

//使用指针访问结构体变量的方式为	指针->变量值
int main(){
	struct data *p = &today;
	p->year=2020;
	p->month = 7;
	p->day = 29;
	printf("%d,%d,%d",p->year,p->month,p->day);
	return 0;
}

结构数组

typedef自定义数据类型

  • C语言提供了一个叫做typedef的功能来声明一个已有的数据类型的新名字。比如:typedef int Length,使Lenght成为int类型的别名。
  • 因此,Length就可以代替int出现在变量定义和参数声明的地方。Length a,b,len; Length num[10];
#include<stdio.h>

/*
	使用typedef关键字后,结构体声明struct Data d1 等价于 data d1 
*/
typedef struct Data{ 
	int year;
	int month;
	int day;
}data;

int main(){
	struct Data p = {2021,2,1};
	data d = {2020,2,1};
	printf("%d,%d,%d\n",p.year,p.month,p.day);
	printf("%d,%d,%d",d.year,d.month,d.day);
	return 0;
}

union

  • 存储:所有成员共享一个空间,同一时间只有一个成员有效,union大小是其最大的成员
  • 初始化:对第一个成员做初始化

全局变量

  • 定义在函数外面的变量是全局变量
  • 全局变量具有全局的生存周期和作用域,在所有函数中都可以使用它们

全局变量初始化

  • 没有做初始化的全局变量会得到0值,指针会得到NULL值
  • 只能用编译时刻已知的值来初始化全局变量
  • 全局变量的初始化发生在main函数之前
#include<stdio.h>
int A = 2;	//声明全局变量 
int main(){
	printf("%d",A);
}

静态本地变量

  • 在本地变量定义时加上static修饰符就成为静态本地变量
  • 当函数离开的时候,静态本地变量会继续存在并保持该值
  • 静态本地变量的初始化只会在第一次进入这个函数时做,以后进入函数时会保持上次离开的值
#include<stdio.h>
void f();

int main(){
	f();
	f(); 
}

void f(){
	static int all = 1;
	all++;
	printf("%d\n",all);
}
  • 静态本地变量实际上是特殊的全局变量,它和全局变量在相同的内存区域
  • 静态本地变量具有全局的生存周期,函数内的局部作用域,static在这里的意思是局部作用域

总结:静态本地变量的生存周期是全局的,作用域是本地的。

返回指针的函数

  • 返回本地变量的地址是危险的
  • 返回全局变量或静态本地变量的地址是安全的
  • 返回在函数内malloc的内存是安全的,但是容易造成问题
  • 最好的做法是返回作为参数传入的指针

宏定义

编译预处理指令

  • #开头的是编译预处理指令
  • 它们不是C语言的成分,但是C语言程序离不开它们
  • #define用来定义一个宏
#include<stdio.h>
#define MAX 100

int main(){
	printf("%d",MAX);
}
  • #define 名字 值
  • 注意结尾没有分号,因为不是C语句
  • 名字必须是一个单词,值可以是各种东西
  • 编译预处理程序会把程序中的MAX换成100

  • 如果一个宏的值中有其它宏的名字,也是会被替换
  • 如果一个宏的值超过一行,最后一行之前的行末要添加\
  • 宏的值后面出现的注释不会被当做宏的值的一部分

没有值的宏

#define _DEBUG

这类宏是用于条件编译的,后面有其它的编译预处理指令来检查这个宏是否已经被定义过

预定义的宏

  • _LINE_:行数
  • _FILE_:文件路径
  • _DTAE_:日期
  • _TIME_:时间

带参数的宏

#include<stdio.h>
#define MAX(x) (x)*(x)*(x)

int main(){
	printf("%d",MAX(5));
	return 0;
}

使用原则

  • 一切都要括号,整个值要括号,参数出现的每个地方都要带括号

  • #define MAX(x) (x)*(x)*(x)
    
  • 定义宏时,结尾一定不要加分号

多个源代码文件

  • main()函数里的代码太长了适合分成几个函数
  • 一个源代码文件太长了适合分成几个文件
  • 两个独立的源代码文件不能编译形成可执行的程序

项目

  • 在Dev C++中新建一个项目,然后把几个源代码文件加入进去
  • 对于项目,Dev C++的编译会把一个项目中所有的源代码文件都编译后,连接起来
  • 有的IDE有分开的编译和构建两个按钮,前者是对单个源文件编译,后者是链接整个项目

头文件

把函数原型放到一个头文件(以.h结尾)中,在需要调用这个函数的源代码文件(.c文件)时,#include这个头文件,就能让编译器在编译的时候知道函数的原型

#include

  • #include是一个编译预处理指令,和宏一样,在编译之前进行处理
  • #include把那个文件的全部文本内容插入到#include所在的地方

""还是<>

  • #include有两种形式来指出要插入的文件,双引号要求编译器首先在当前目录寻找这个文件,如果没有再到编译器指定的目录寻找,尖括号是让编译器只在指定的目录去找这个文件
  • 编译器自己知道自己的标准库的头文件在哪
  • 环境变量和编译器命令行参数也可以指定寻找头文件的目录

头文件

  • #include其实就是将文件中可能使用到的函数的原型,从对应的.h文件中拷贝到当前文件的开头。
  • 在使用和定义这个函数的地方都应该#include这个头文件
  • 一般的做法就是任何.c都有对应的同名的.h文件,把所有对外公开的函数的原型和全局变量的声明都放进去

不对外公开的函数

  • 在函数前面加上static就使得它成为只能在所在的编译单元中被使用的函数
  • 在全局变量前面加上static就使它成为只能在所在的编译单元中被使用的全局变量

声明

声明全局变量的方法:在.h文件中加上

extern int All;

声明和定义

声明是不产生代码的东西

  • 函数原型
  • 变量声明
  • 结构声明
  • 宏声明
  • 枚举声明
  • inline函数

定义时产生代码的东西

重复声明

  • 在同一个编译单元里,同名的结构不能被重复声明
  • 如果你的头文件里有结构的声明,很难这个头文件不会在一个编译单元里被#include多次
  • 所以需要标准头文件结构

标准头文件结构

使用max.h文件举例

#ifndef _MAX_H_		//判断语句,判断是否定义过_MAX_H_,false:执行下列语句。true:跳过
#define _MAX_H_		//宏定义 #_MAX_H_

double max(double a,double b); 
extern int All;

#endif				//结束
  • 运用条件编译和宏,保证这个头文件在一个编译单元中只会被#include一次
  • #pragma once 也能起到相同的作用,但不是所有的编译器都支持

格式化输入输出

%[flags][width][.prec][hlL]type

flag

  • - 左对齐
  • + 在前面放+或-
  • (space) 正数留空
  • 0 零填充

width或prec

  • number 最小字符数
  • * 下一个参数是字符数
  • .number 小数点后的位数
  • .* 下一个参数就是小数点后的位数

type

  • i或d:int
  • u:unsigned int
  • o:八进制
  • x:十六进制
  • X:字母大写的十六进制
  • f或F:float,6
  • e或E:指数
  • g:float
  • G:float
  • a或A:十六进制浮点数
  • c:char
  • s:字符串
  • p:指针
  • n:读入/写出的个数

scanf:%[flag]type

flag

  • *:跳过
  • 数字:最大字符数
  • hh:char
  • h:short
  • l:long,double
  • ll:long long
  • L:long double

type

  • d:int
  • i:整数,可能为十六进制或八进制
  • u:unsigned int
  • o:八进制
  • x:十六进制
  • a,e,f,g:float
  • c:char
  • s:字符串
  • p:指针

printf和scanf的返回值

  • scanf:输出的字符数
  • printf:读入的项目数

在要求严格的程序中,应该对scanf和printf的返回值进行判断,判断程序是否出错

文件输入输出

用>和<做重定向

打开文件的标准代码

FILE *fp = fopen("file","r");
if(fp){
	fscanf(fp,...);
	fclose(fp);
}else{}

fopen函数

  • r:打开只读
  • r+:打开读写,从文件头开始
  • w:打开只写,如果不存在则新建,如果存在则清空
  • w+:打开读写。如果不存在则新建,如果存在则清空
  • a:打开追加。如果不存在则新建,如果存在则从文件尾开始
  • …x:只新建,如果文件已存在则不能打开

二进制文件

按位运算

按位运算的运算符:

  • &:按位的与
  • |:按位的或
  • ~:按位取反,0变1,1变0
  • ^:按位的异或
  • <<:左移,左移一位等价于乘2,移两位等价于乘4,移n位等价于乘2^n
  • >>:右移,右移n位等价于除以2^n,对于unsigned类型,右移后原来的位置填入0,而对于signed类型,右移后最高位不变。例如:signed 1000右移1位,得到1100。最高位依然是原来的数。unsigned 1000右移1位,得到0100,最高位填充0。

位段

  • 可以直接用位段的成员名称来访问,比移位、与、或更方便
  • 编译器会安排其中的位的排列,不具有可移植性
  • 当所需的位超过一个int时会采用多个int

可变数组

  • Array arrayCreate(int init_size); 创建一个数组
  • void arrayFree(Array *a); 回收数组空间
  • int arraySize(const Array *a); 查询数组可用空间数量
  • int *arrayAt(Array *a,int index); 返回指定的数组元素
  • void arrayInflate(Array *a,int more_size); 让数组变大

头文件array.h

#ifndef _ARRAY_H_
#define _ARRAY_H_

typedef struct{
	int *array;
	int size;
}Array;
Array arrayCreate(int init_size);				//创建一个数组
void arrayFree(Array *a);						//回收数组空间
int arraySize(const Array *a);					//查询数组可用空间数量
int *arrayAt(Array *a,int index);				//返回指定的数组元素
void arrayInflate(Array *a,int more_size);		//让数组变大
void travelArr(const Array *a);					//遍历数组 
#endif

源文件array.c

#include <stdio.h>
#include <stdlib.h>
#include "array.h"

const block=5;	

//主函数入口
int main(){
	Array a = arrayCreate(5);
	printf("%d\n",arraySize(&a));
	int s = 0;
	int cnt = 0;
	while(s!=-1){
		scanf("%d",&s);
		*arrayAt(&a,cnt)=s;
		cnt++;
	}
	travelArr(&a);
	arrayFree(&a);
	return 0;
}

//创建一个数组
Array arrayCreate(int init_size){
	Array a;
	a.size = init_size;
	a.array = (int*)malloc(sizeof(int)*a.size);
	return a;   
}
//回收数组空间			
void arrayFree(Array *a){
	free(a->array);
	a->array = NULL;
	a->size = 0;
}
//查询数组空间数量				
int arraySize(const Array *a){
	return a->size;
}		
//返回指定的数组元素			
int *arrayAt(Array *a,int index){
	if(index>=a->size){
		arrayInflate(a,block );
	}
	return &(a->array[index]);
}
//让数组变大	
void arrayInflate(Array *a,int more_size){
	int *p = (int*)malloc(sizeof(int)*(a->size+more_size));
	int i;
	for(i=0;i<a->size;i++){
		p[i] = a->array[i];
	}
	free(a->array);
	a->array = p;
	a->size += more_size;
}
//遍历数组
void travelArr(const Array *a){
	int i=0;
	printf("arr:[");
	for(i=0;i<a->size;i++){
		if(a->array[i]==-1){
			break;
		}
		printf(" %d ",a->array[i]);
	}
	printf("]");
} 

链表结构

链表结构体定义:

typedef struct _node{		//链表名
	int val;				//类型
	struct _node *next;
}Node;			//链表变量名
//在后续的使用中,可以直接使用链表的变量名定义链表

创建链表结点:

//头结点定义
Node *head = (Node*)malloc(sizeof(Node));
head->next = NULL;

//普通结点定义
Node *p = (Node*)malloc(sizeof(Node));
p->val = 100;
p->next = NULL;

练习:创建链表并实现以下功能

  • 链表尾部添加元素
  • 打印链表元素
  • 删除某个元素
  • 指定下标插入元素
  • 修改指定下标的元素
  • 获取链表长度
#include <stdio.h>
#include <stdlib.h>
#include "LinkList.h"

int main(int argc, char *argv[]) {
	Node *head = (Node*)malloc(sizeof(Node));
	head->next = NULL;
	int i;
	for(i = 0;i<10;i++){
		add(head,i);
	}
	print(head);
	printf("%d\n",getSize(head));
	reMove(head,5);
	print(head);
	printf("%d\n",getSize(head));
	insert(head,0,222);
	print(head);
	revise(head,4,10000);
	print(head);
	return 0;
}

//打印链表 
void print(Node *head){
	Node *temp;
	for(temp = head->next;temp;temp=temp->next){
		printf("%d ",temp->val);
	}
	printf("\n");
}

void add(Node *head,int val){
	//创建结点 
	Node *p = (Node*)malloc(sizeof(Node));
	p->val = val;
	p->next = NULL;
	
	//找到链表的最后结点
	Node *last = head;
	while(last->next!=NULL){
		last = last->next;
	}
	//将新创建结点添加到最后结点后面
	last->next = p; 
}

//删除指定元素
void reMove(Node *head,int i){
	Node *temp = head;
	while(temp->next!=NULL){
		if(temp->next->val==i){
			temp->next = temp->next->next;
			break;
		}else{
			temp = temp->next;
		}
	}
} 

//指定下标插入元素 
void insert(Node *head,int index,int val){
	Node *p = head;
	Node *t = (Node*)malloc(sizeof(Node));
	t->next=NULL;
	t->val = val;
	int cnt;
	for(cnt=0;cnt<index;cnt++){
		p=p->next;
	}
	if(p){
		printf("插入元素不存在\n");
	}
	t->next = p->next;
	p->next = t;
}

//获取链表长度
int getSize(Node *head){
	int i=0;
	Node *p = head->next;
	for(p = head->next;p;p=p->next){
		i++;
	}
	return i;
} 

//修改指定元素 
void revise(Node *head,int index,int val){
	Node *p = head->next;
	int c=0;
	for(p=head->next;p;p=p->next){
		if(c==index){
			p->val = val;
			break;
		}
		c++;
	}
}

LinkList.h头文件

#ifndef _LinkList_
#define _LinkList_

//定义链表结构体 
typedef struct _node{
	struct _node *next;
	int val;	
}Node;
//添加链表元素 
void add(Node *head,int val);
//打印链表元素 
void print(Node *head);
//删除指定元素 
void reMove(Node *head,int i);
//插入元素
void insert(Node *head,int index,int val); 
//修改元素
void revise(Node *head,int index,int val); 
//获取链表长度
int getSize(Node *head);
#endif
  C++知识库 最新文章
【C++】友元、嵌套类、异常、RTTI、类型转换
通讯录的思路与实现(C语言)
C++PrimerPlus 第七章 函数-C++的编程模块(
Problem C: 算法9-9~9-12:平衡二叉树的基本
MSVC C++ UTF-8编程
C++进阶 多态原理
简单string类c++实现
我的年度总结
【C语言】以深厚地基筑伟岸高楼-基础篇(六
c语言常见错误合集
上一篇文章      下一篇文章      查看所有文章
加:2021-07-31 16:26:28  更:2021-07-31 16:27:52 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年4日历 -2024/4/27 16:49:07-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码