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 大小的整数类型(如 char 和 short)会首先提升为 int 类型,如果 int 无法表示原类型的所有可能值,那么会提升为 unsigned int。
2. 在二元运算中,例如 +, -, *, 和 /,如果两个操作数的类型不同,那么较小类型的操作数会提升为较大的类型。这称为 “二元算术转换”。
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;
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律