C语言学习笔记3

操作符

分类:
算术操作符
移位操作符
位操作符
赋值操作符
单目操作符
关系操作符
逻辑操作符
条件操作符
逗号表达式
下标引用,函数调用和结构成员。
算术操作符
+ — * / %
除了%操作符之外,其他几个操作符可以用于整数和浮点数
对于/操作符如果两个操作数都为整数,执行整数除法。只要有浮点数就执行浮点数除法。
%操作符的两个操作数必须为整数,返回的是整除之后的余数。
移位操作符
<<左移操作符
左边丢弃,右边补0
>>右移操作符
右移操作符移位规则:
1算术右移:右边抛弃,左边补原符号位
2逻辑右移:右边抛弃,左边补0
内存中正数的原码反码补码相同,计算机存储都以补码存储。正数都一样,负数不一样,位移操作符移动的是补码,打印的是原码。
eg:int a= 8----->1000    4字节*8比特=32比特
00000000000000000000000000001000 正数原反补不变,右移一位=4
int b =-1------>100000001 补码是符号位不变,其余位取反加1,---->11111111,右移一位=-1

警告:对于移位运算符,不要移动负数位,这是标准未定义的。
位操作符
&	按位与		全1则1
|	按位或		有1则1
^	按位异或	相同为0,相异为1	
注:他们的操作数必须是整数,补码进行运算
单目操作符
!		逻辑反操作
-		负值
+		正值
&		取地址
sizeof		操作数的类型长度(以字节为单位)
~		对一个数的二进制按位取反
--		前置,后置--
++		前置,后置++
*		间接访问操作符
(类型)	强制类型转换
整型提升
在 C 语言中,“整型提升”是指较小的整型数据类型被自动转换为较大的整型数据类型的过程。这是 C 语言为了确保操作的正确性而采取的一种机制。以下是整型提升的规则:

	1.	小于 int 大小的整数类型(如 charshort)会首先提升为 int 类型,如果 int 无法表示原类型的所有可能值,那么会提升为 unsigned int2.	在二元运算中,例如 +, -, *, 和 /,如果两个操作数的类型不同,那么较小类型的操作数会提升为较大的类型。这称为 “二元算术转换”。
	3.	在进行位运算(如按位与 &,按位或 |,按位异或 ^)时,同样会先进行整型提升。

例如:

short a = 1;
int b = a + 2;  // a 提升为 int 类型

在上述代码中,short 类型的 a 被提升为 int 类型以与字面量 2 进行加法运算。

char a,b,c;
...
a=b+c;
b和c的值被提升为普通整型,然后再执行加法运算。加法运算完成之后,结构将被截断,然后再存储于a中。
整型提升:高位补符号位(整形提升是按照变量的数据类型的符号位来提升的),无符号数高位补0.

//例题
//输出:-1 -1 255 4294967168(整型提升,有无符号位)
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main() {
	char a = -1;
	//内存中存的是补码
	//-1的补码:11111111111111111111111111111111
	//因为是char类型,内存中存的是一个字节:11111111
	//以%d打印要整型提升高位要补到四个字节,高位补符号位。
	//11111111111111111111111111111111----补码(内存中存的)
	//打印要转换成原码
	//10000000000000000000000000000001----原码(-1)
	char e = -128;
	//内存中存的是补码
	//-128的补码:10000000000000000000000010000000
	//因为是char类型,内存中存的是一个字节:1000000
	//以%u打印要整型提升高位要补到四个字节,高位补符号位。
	//11111111111111111111111110000000----补码(内存中存的)
	//打印要转换成原码,由于%u是打印十进制无符号数字,原反补一样
	//11111111111111111111111110000000----原码(4294967168)
	signed char b = -1;
	//11111111111111111111111111111111----补码(内存中存的)
	//打印要转换成原码
	//10000000000000000000000000000001----原码(-1)
	unsigned char c = -1;
	//无符号数高位补0
	//00000000000000000000000011111111----补码(内存中存的)
	//打印要转换成原码
	//00000000000000000000000011111111----原码(255)
	printf("%d %d %d %u ", a, b, c, e);
	return 0;
}
操作符的属性
复杂表达式的求值有三个影响因素:
1操作符的优先级
2操作符的结合性
3是否控制求值顺序
两个相邻的操作符先执行哪个?取决于它们的优先级,如果两者优先级相同,取决于它们的结合性。
逗号表达式
逗号表达式(comma expression)在C语言中是一个特殊的表达式,它允许在一个表达式中计算多个子表达式,并返回最后一个子表达式的值。

逗号表达式的格式如下:
(表达式1, 表达式2, ..., 表达式n)每个逗号之间是一个子表达式。整个逗号表达式会按从左到右的顺序依次计算每个子表达式,并返回最后一个子表达式的值作为整个表达式的值。
例如:
int x = (1, 2, 3);
上面的代码中,(1, 2, 3)是一个逗号表达式,它会先计算1,然后计算2,最后计算3,并将3的结果作为整个表达式的值,所以x的值为3。

逗号表达式常用于在一个表达式中执行多个操作。例如:
x = 0, y = 10, z = 20; // x,y,z都赋值
上面用逗号表达式同时给x,y,z赋值。

总结一下,逗号表达式的主要作用:

- 允许在一个表达式中执行多个操作
- 返回最后一个子表达式的值作为整个表达式的值

需要注意的是,只有最后一个子表达式的值会被使用,其他子表达式的值会被忽略。

结构体

基本概念:结构体属于用户自定义的数据类型,允许用户存储不同的数据类型。
语法:struct 结构体名 {结构体成员列表};
通过结构体创建变量的方式有三种:
	struct 结构体名 变量名
	struct 结构体名 变量名={成员1值,成员2值...}
	定义结构体时顺便创建变量
	
//结构体定义
#include<stdio.h>
struct student {
	//成员列表
	char name;
	int age;
	int score;
}stu3;	//结构体变量创建方式3

int main() {
	//结构体变量创建方式1
	struct student stu1;	//struct可以省略
    stu1.name = 'A';
	stu1.age = 18;
	stu1.score = 90;
	printf("%c%d%d", stu1.name,stu1.age, stu1.score);
	//结构体变量创建方式2
	struct student stu2 = { 'A',19,77 };
	return 0;
}

结构体定义时关键字不可以省略;创建结构体变量时可以省略关键字struct;结构体变量利用操作符.访问成员。

结构体数组

结构体数组
作用:将自定义的结构体放入到数组中方便维护。
语法:struct 结构体名 数组名[元素个数] = {{},{},...{}}
#include<stdio.h>
struct student {
	//成员列表
	char name;
	int age;
	int score;
};
int main() {
	//结构体数组
	struct student arr[2]=
    {
	{'A',18,90},
	{'B',17,98},
	};
	//给结构体数组中的元素赋值
	arr[1].name='C';
	arr[1].age=19;
	arr[1].score=99;
	//遍历结构体数组
	for(int i=0;i<2;i++){
	printf("%c%d%d",arr[i].name, arr[1].age,arr[1].score);
	printf("\n");
	}
	return 0;
}

结构体指针

作用:通过指针访问结构体中的成员
利用操作符 -> 可以通过结构体指针访问结构体属性

struct student {
	//成员列表
	char name[20];
	int age;
	int score;
};
int main() {
	//结构体数组
	struct student s={"Tom",17,88};
	//通过指针指向结构体变量
	student *p=&s;
	//通过指针访问结构体中数据
	printf("%s%d%d",p->name, p->age,p->score);
	system("pause");	//执行系统命令,停下来。需引#include<stdlib.h>
	return 0;
}

结构体做函数参数

作用:将结构体作为参数向函数中传递
传递方式有两种:
1值传递
2址传递

数据类型介绍

类型的意义:
1使用这个类型开辟内存空间的大小(大小决定了使用范围)
2如何看待内存空间的视角

整型家族
	char
		unsigned char	无符号字符
		signed char		有符号字符
	short
		unsigned short[int]		[int]可以省略
		signed short[int]
	int
		unsigned int
		signed int
    long
        unsigned long[int]
        signed long[int]

浮点型
	float	单精度浮点型
	double	双精度浮点型

构造类型
	数组类型
	结构体类型struct
	枚举类型enum
	联合类型union
	
指针类型
	int* pi;
	char* pc;
	float* pf;
	void* pv;

空类型
	void表示空类型(无类型)
	通常用于函数的返回类型,函数的参数,指针类型。

有符号char范围:-128->127

无符号char范围:0-255

原码 反码 补码

计算机中的有符号数有三种表示方法,即原码 反码 补码。
三种表示方法均有符号位和数值位两部分,符号位都是用0表示正,用1表示负。而数值位三种表示方法各不相同。

整数
	有符号数
		正数:原码反码补码相同
		负数:原码反码补码不同,需要计算
	无符号数
		原码反码补码相同
有符号正数原码反码补码相同,负数不同。计算如下:
原码
	直接将二进制按照正负数的形式翻译成二进制。
反码
	将原码的符号位不变,其它位依次按位取反就可以得到了。
补码
	反码加1就得到补码。
	
对于整型来说,数据存放内存中其实存放的是补码。
在计算机系统中,整型数值一律用补码来表示和存储。原因在于,使用补码可以将符号位和数值域统一处理,同时,加法和减法也是统一处理(cpu只有加法器)此外补码与原码相互转换,其运算过程是相同的,不需要额外的硬件电路。

大小端介绍

内存地址

大端(存储)模式:是指数据的低位保存在内存的高地址中,而数据的高位保存在内存的低地址中。
小端(存储)模式:是指数据的低位保存在内存的低地址中,而数据的高位保存在内存的高地址中。
	

指针

一般使用:
int main(){
char ch='w';
char *p=&ch;	//取地址
*p='w';
return 0;
}

int main(){
char arr[]="abcdef";
char *p=arr;	//arr本身就是首地址,不需要&
printf("%s\n",arr);	//abcdef
printf("%s\n",p);	//abcdef
return 0;
}

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
int main() {
	char* p = "abcdef";//"abcdef"是一个常量字符串
	printf("%c\n",*p );//打印a
	printf("%s\n", p);//打印abcdef
	return 0;	
}
//代码char* pstr="hello bit";这里是把字符串hello bit首字符的地址放到了pstr中。

指针数组是存放指针的数组。
数组指针是指向数组的指针。
函数指针是指向函数的指针。(存放函数地址的一个指针)

定义一个函数:Add
printf('%p\n',&Add);
printf('%p\n',Add);
&函数名 和 函数名都是函数的地址。

#include <stdio.h>
int main() {
	//int* p = NULL;//p是整型指针-指向整形的指针-可以存放整型的地址
	//char* pc = NULL;//pc是字符指针-指向字符的指针-可以存放字符的地址
	//数组指针 - 指向数组的指针 - 可以存放数组的地址
	//arr-首元素地址
	//&arr[0]-首元素地址
	//&arr-数组的地址
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int (*p)[10] = &arr;//数组的地址要存起来,p就是数组指针
	int i = 0;
	for (i = 0; i < 10; i++) {
		printf("%d\n", (*p)[i]);
	}
	return 0;//一般不是这么用,太麻烦,最少二维数组使用
}
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int *p=arr;
int i=0;
for (i = 0; i < 10; i++) {
		printf("%d\n", (*p+i));
		printf("%d\n", p[i]);
		printf("%d\n", *(arr+i));
		printf("%d\n", arr[i]);
	}
这样就可遍历,简单。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main() {
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;
	int i = 0;
	for (i = 0; i < 10; i++) {
		printf("%d", (*p + i));
		printf("%d", p[i]);
		printf("%d", (*arr+i));
		printf("%d", arr[i]);//四种等价
		//(*p + i)=p[i]=(*arr+i)=arr[i]
	}
	return 0;//一般不是这么用,太麻烦,最少二维数组使用
}

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
void print(int arr[3][5], int x, int y) {
	int i = 0;
	for (i = 0; i < x; i++) {
		int j = 0;
		for (j = 0; j < y; j++) {
			printf("%d ", arr[i][j]);
			printf("%d ", (*arr[i] + j));			
		}
		printf("\n");
	}
}
void print1(int (*p)[5], int x, int y) {
	int i = 0;
	for (i = 0; i < x; i++) {
		int j = 0;
		for (j = 0; j < y; j++) {
			//printf("%d ", p[i][j]);
			//printf("%d ", (*p[i] + j));
			//printf("%d ", *(*(p+i) + j));
			//printf("%d ", (*(p+i))[j]);
		}
		printf("\n");
	}
}
int main() {
	int arr[3][5] = { {1,2,3,4,5 },{2,3,4,5,6 } ,{3,4,5,6,7} };
	print(arr, 3, 5);
	print1(arr, 3, 5);
	return 0;
}

int arr[5]			//arr是一个5个元素的整型数组
int *arr[10]		//arr是一个数组,数组有10个元素,每个元素类型是int*,arr是指针数组
int (*arr)[10]		//arr是一个指针,该指针指向了一个数组,该数组有10个元素,每个元素类型是int,arr是数组指针
int (*arr[10])[5]	//arr是数组,该数组有10个元素,每个元素是一个数组指针,该数组指针指向的数组有5个元素,每个元素是int

数组参数 指针参数

一维数组传参
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
void test(int arr[])
{}
void test(int arr[10])
{}
void test(int *arr)
{}
void test1(int *arr[20])
{}
void test1(int **arr)
{}
int main() {
	int arr[10] = { 0 };
	int* arr1[20] = { 0 };
	test(arr);
	test1(arr1);
	return 0;
}
二维数组传参:
二维数组传参,函数形参的设计只能省略第一个[]的数字。
因为对一个二维数组,可以不知道有多少行,但是必须知道一行有多少元素。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
void test(int arr[3][5])
{}
void test(int arr[][])//错误❌
{}
void test(int arr[][5])
{}
void test(int *arr)//错误❌
{}
void test(int *arr[5])//错误❌
{}
void test(int (*arr)[5])
{}
void test(int **arr)//错误❌这要传一维数组的地址
{}
int main() {
	int arr[3][5] = { 0 };
	test(arr);
	return 0;
}

一级指针传参
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
void print(int* p, int sz) {
	int i = 0;
	for (i = 0; i < sz; i++) {
		printf("%d\n", *(p + i));
	}
}
int main() {
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;
	int sz = sizeof(arr) / sizeof(arr[0]);
	//一级指针p,传给函数
	print(p, sz);
	return 0;
}
二级指针传参
传的是一级指针的地址
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
void test(int** ptr) {
	printf("%d\n", **ptr);
}
int main() {
	int n = 10;
	int* p = &n;
	int** pp = &p;
	test(pp);
	test(&p);
	//也可以传int* arr[1]的arr
	return 0;
}
函数指针
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int Add(int x,int y) {
	int z = 0;
	z = x + y;
	return z;
}
int main() {
	int a = 10;
	int b = 20;
	//函数指针
	int (*p)(int, int) = Add;
	printf("%d\n",p(2, 3));
	printf("%d\n",Add(2, 3));
	printf("%d\n",(*p)(2, 3));//都可以
	return 0;
}
或则
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
void print(const char* str) {
	printf("%s\n", str);
}
int main() {
	//函数指针
	void (*p)(const char*) = print;
	(*p)("hello bit");
	return 0;
}

函数指针数组

数组是一个存放相同类型数据的存储空间,比如指针数组:int* arr[10];

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int Add(int x, int y) {
	return x + y;
}
int Sub(int x, int y) {
	return x - y;
}
int Mul(int x, int y) {
	return x * y;
}
int Div(int x, int y) {
	return x / y;
}
int main() {
	
	//函数指针数组-需要一个数组,可以存放4个函数的地址
	int (*p[4])(int, int) = {Add,Sub,Mul,Div};
	int i = 0;
	for (i = 0; i < 4; i++) {
		printf("%d\n", p[i](6, 2));//8 4 12 3
	}	
	return 0;
}

函数指针数组的用途:转移表
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h> 
void menu() {
	printf("********************\n");
	printf("***1.Add  2.Sub  ***\n");
	printf("***3.Mul  4.Div  ***\n");
	printf("***5.Xor  0.exit ***\n");
	printf("********************\n");
}
int Add(int x, int y) {
	return x + y;
}
int Sub(int x, int y) {
	return x - y;
}
int Mul(int x, int y) {
	return x * y;
}
int Div(int x, int y) {
	return x / y;
}
int Xor(int x, int y) {
	return x ^ y;
}
int main() {
	int input = 0;
	int x = 0;
	int y = 0;
    int(*pfArr[])(int, int) = { 0,Add,Sub,Mul,Div,Xor };
	do 
	{
		menu();
		printf("请选择:\n");
		scanf("%d", &input);
		
		if(input>0 && input<6){
			printf("请输入两个操作数:\n");
			scanf("%d%d", &x, &y);
			printf("%d\n", pfArr[input](x, y));
		}
		else if (input == 0) {
			printf("退出\n");
		}
		else {
			printf("选择错误\n");
		}
	} while (input);
	return 0;
}

指向函数指针数组的指针

指向函数指针数组的指针是一个指针,指针指向一个数组,数组的元素都是函数指针

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h> 
int main() {
	int arr[10] = { 0 };
	int(*p)[10] = &arr;
	int(*pf)(int, int);//函数指针
	int(*pfArr[4])(int, int);//pfArr是一个数组-函数指针的数组
	int(*(*ppfArr)[4])(int, int)=&pfArr;
	//ppfArr是一个数组指针,指针指向的数组有四个元素
	//指向的数组的每个元素的类型是一个函数指针int(*)(int,int)
	return 0;
}

回调函数

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时有另外的一方调用的,用于对该事件或条件进行响应。
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h> 
void menu() {
	printf("********************\n");
	printf("***1.Add  2.Sub  ***\n");
	printf("***3.Mul  4.Div  ***\n");
	printf("***5.Xor  0.exit ***\n");
	printf("********************\n");
}
int Add(int x, int y) {
	return x + y;
}
int Sub(int x, int y) {
	return x - y;
}
int Mul(int x, int y) {
	return x * y;
}
int Div(int x, int y) {
	return x / y;
}
int Xor(int x, int y) {
	return x ^ y;
}
void Calc(int(*pfArr)(int x, int y)) {
	int x = 0;
	int y = 0;
	printf("请输入两个操作数:\n");
	scanf("%d%d", &x, &y);
	printf("%d\n",pfArr(x, y));
}
int main() {
	int input = 0;
	do
	{
		menu();
		printf("请选择:\n");
		scanf("%d", &input);
		switch (input) {
		case 1:
			Calc(Add);
			break;
		case 2:
			Calc(Sub);
			break;
		case 3:
			Calc(Mul);
			break;
		case 4:
			Calc(Div);
			break;
		case 5:
			Calc(Xor);
			break;
		case 0:
			printf("退出\n");
			break;
		default:
			printf("选择错误\n");
		}
	} while (input);
	return 0;
}

void* p //void*类型的指针,可以接收任意类型的地址。

​ //void*类型的指针,不能进行解引用操作。

​ //void*类型的指针,不能进行+-整数的操作。

qsort函数

qsort函数是C语言中的快速排序函数,可以用来对数组进行排序。
qsort函数的原型为:
void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*));
其中参数含义如下:
- base: 待排序数组的首地址
- nitems: 数组中待排序元素的个数
- size: 每个元素的大小(单位字节)
- compar: 用来比较两个元素的函数指针,需要程序员自己实现

使用qsort函数排序数组的步骤是:
1. 定义一个比较函数compar,该函数负责比较两个元素的大小,并返回比较结果(大于、等于、小于)。

2. 调用qsort函数,传入数组名、数组元素数量、每个元素的大小和比较函数compar。

3. qsort根据compar函数的结果对数组进行排序。

例如:
#include <stdio.h>  
#include <stdlib.h>

int compar(const void *a, const void *b) {
  return (*(int*)a - *(int*)b); 
}

int main() {
  int arr[] = {4, 2, 1, 5, 3};
  int n = sizeof(arr)/sizeof(arr[0]);
  qsort(arr, n, sizeof(int), compar);
  for(int i=0; i<n; i++) {
    printf("%d ", arr[i]);
  }
  return 0;
}
以上程序首先定义了一个compar函数用于比较两个int类型的大小,然后调用qsort函数排序数组arr,最后打印出排序后的结果。

例题

//答案:4,2000000
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h> 
int main() {
	int a[4] = { 1,2,3,4 };
	int* ptr1 = (int*)(&a + 1);//数组的地址末尾强转(int*)
	int* ptr2 = (int*)((int)a + 1);//数组地址强转(int)+1向后挪1字节
	//计算机中内存情况(小端存储)01 00 00 00 02 00 00 00 03 00 00 00 04 00 00 00
	printf("%x,%x", ptr1[-1], *ptr2);
	return 0;
}

内存情况

例题

#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h> 
int main() {
	int a[5][5];
	int(*p)[4];
	p = a;
	printf("%p,%d\n" & p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
	return 0;
}

分析

posted @   张彧520  阅读(99)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律
点击右上角即可分享
微信分享提示