目录
常用
#pragma warning(disable:4996)
#define _CRT_SECURE_NO_WARNINGS
力扣刷题常用的代码格式
#include<stdio.h>
//刷题常用的一种写法
int main()
{
int ch = 0;
while ((ch = getchar()) != EOF)//getchar函数只能读一个字符(必须是字符型的) int getchar(void)
{
putchar(ch);//注意这里的一个细节,其实我按得回车也是被读取进去了,要不然不是一列一列的打印的
}
return 0;
}
不使用临时变量,交换两个数的值
#include<stdio.h>
int main()
{
// int a = 3;
// int b = 5;
// printf("a=%d,b=%d\n", a, b);
// a = a + b;//a里面放了和
// b = a - b;//b里面放和减去b,也就是a
// a = a - b;//a里面是和,b里面是a,这样a就放了b
// printf("a=%d,b=%d\n", a, b);
// //但是以上方法有一个问题,如果a与b的和超过了int的上限,就会报错。或者会溢出,会造成二进制位的丢失,从而出错
int a = 3;//00000000000000000000000000000011
int b = 5;//00000000000000000000000000000101
printf("a=%d,b=%d\n", a, b);
a = a ^ b;//a里面放了和
b = a ^ b;//b里面放和减去b,也就是a
a = a ^ b;//a里面是和,b里面是a,这样a就放了b
printf("a=%d,b=%d\n", a, b);
return 0;
}
//具体过程:由于^相同为1,所以只看后三即可
//开始a=011,b=101
//第一步011^101-->a=110,b=101,密码放在a里
//第二步110^101-->b=011,a=110,密码和原来的b^
//第三步110^011-->a=101,b=011,密码和原来的a^
//可以把a&b看成一个密码,密码和原来的a^就能得到原来的b,密码再和原来的b^就能得到原来的a
//其实可以得到很多好玩的结论
//a^a=0(a全都变成了0);0^a=a(a里面的0还是0,1还是1,不变);a^a^a=0^a=a;a^a^b=0^b=b;a^b^a=b(密码^原来的a得到原来的b);通过上两个,发现^还满足交换律
//有了这样的思考,发现上面的面试题的第二步 b=a^b-->b=a^b^b-->b=b^b^a-->b=0^a=a,这里的ab全都是原来的ab
//第三步,先把a全都替换成a^b,则a=(a^b)^a=b,这里的ab全都为原来的ab
//但是由于这种方法不仅可读性差,而且他只适用于整型(因为涉及^),而且效率不如临时变量,所以平时还是用的临时变量。
向中间靠拢
#include<stdio.h>
#include<string.h>
#include<windows.h>
#include<stdlib.h>
int main()
{
char arr1[] = "guosaitong";
char arr2[] = "##########";
int left = 0;//下标
int right = strlen(arr1) - 1;//下标,用它求的时候,不包括/0
while (left <= right)
{
arr2[left] = arr1[left];
arr2[right] = arr1[right];
printf("%s\n", arr2);
Sleep(1000);
system("cls");
left++;
right-;
}
return 0;
}
输入三个数,并从大到小排序
#include<stdio.h>
int main()
{
int a = 0;
int b = 0;
int c = 0;
int temp;
scanf_s("%d %d %d", &a, &b, &c);//分隔符可以有制表符 回车 空格
if (a < c)
{
temp = a;
a = c;
c = temp;
}
if (a < b)
{
temp = a;
a = b;
b = temp;
}
if (b < c)
{
temp = b;
b = c;
c = temp;
}
printf("%d %d %d", a, b, c);
return 0;
}
猜数字游戏
#include <stdlib.h>//rand和srand的
#include <stdio.h>
#include <time.h>
void menu()
{
printf("****************************\n");
printf("***1.play**********0.exit***\n");
printf("****************************\n");
}
void game()
{
//猜数字游戏的具体执行
//1.生成随机数,最好要有范围
/*The rand function returns a pseudorandom integer in the range 0 to RAND_MAX.(32767)
Use the srand function to seed the pseudorandom-number generator before calling rand.*/
// int ret = rand(time);//但仅仅这样,每次生成的随机数都是同一组数,而要利用srand来触发随机数生成器
/*srand()的括号中放入整数,会有随机数生成,但如果整数不变,生成的也不变。想到往里面放入一个随机数就行了,
* 但最终目的就是生成随机数,所以放入随机数是不成立的思路,此时通常利用时间戳作为参数,也即一个不断变化的数
*/
//int ret = rand((unsigned)time(NULL));//主要就是一个随机数了,但是如果这样写在这个位置,
//那我每次玩游戏的时候都要设置一个起点,这不对。
//int ret = rand();//但是这个范围有点大
int ret = rand() % 100 + 1;//1~100
//2.猜数字以及反馈
int guess = 0;
while (1)
{
scanf_s("%d", &guess);
if (guess < ret)
{
printf("猜小了\n");
}
else if (guess > ret)
{
printf("猜大了\n");
}
else
{
printf("猜对了\n");
break;
}
printf("再猜\n");
}
}
int main()
{
int input = 0;
srand((unsigned int)time(NULL));//注意只要设置一次随机数生成起点
do
{
menu();
printf("请选择:>");
scanf_s("%d",&input );
switch (input)
{
case 1:
printf("请猜数字:\n");
game();
break;
case 0:
printf("退出游戏\n");
break;
default:
printf("选择错误,请重新选择:\n");
break;
}
} while (input);//只有input为0的时候才会跳出循环,其余情况都会进入循环
}
关机程序
#pragma warning(disable:4996)
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main()
{
char input[20] = {0};
system("shutdown -s -t 60");
again:
printf("输入我是猪不然关机\n");
scanf("%s", input);
if (strcmp(input, "我是猪") == 0)
{
system("shutdown -a");
}
else
{
goto again;
}
return 0;
}
//这里调试的时候把debug改成release
找到只出现一次的数
#include <stdio.h>
int main()
{
int arr[] = { 1,2,3,4,5,4,3,2,1 };
int i = 0;
int j = 0;
int sz = sizeof(arr) / sizeof(arr[0]);
for (i = 0; i < sz; i++)
{
int count = 0;
for (j = 0; j < sz; j++)
{
if (arr[i] == arr[j])
{
count++;
}
}
if (count == 1)
{
printf("找到了只出现一次的数:%d\n", arr[i]);
break;
}
}
return 0;
}
二分查找
二分查找
#include<stdio.h>
//函数实现整型有序数组的二分查找
int binary_search1(int arr[], int k, int sz)
{
int left = 0;
int right = sz - 1;
while (left <= right)
{
int mid = (left + right) / 2;
if (arr[mid] < k)
left = mid + 1;
else if (arr[mid] > k)
right = mid - 1;
else
return mid;
}
return -1;//当他自己出了循环还没有return mid的时候就说明没找到
}
//但是此函数只能对整个数组进行操作,而不能对一块区间
int binary_search2(int arr[], int k, int left, int right)
{
while (left <= right)
{
int mid = (left + right) / 2;
if (arr[mid] < k)
left = mid + 1;
else if (arr[mid] > k)
right = mid - 1;
else
return mid;
}
return -1;
}
//次函数的灵活性就更高了
int main()
{
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
int k = 7;
int sz = sizeof(arr) / sizeof(arr[0]);
int ret = binary_search1(arr, k, sz);
int ret2 = binary_search2(arr, k, 5,9);
//找到了返回下标 找不到返回负一
if (ret == -1)
printf("找不到\n");
else
printf("找到了,下标是%d\n",ret);
if (ret2 == -1)
printf("找不到\n");
else
printf("找到了,下标是%d\n",ret);
return 0;
}
//在不同的函数中可以使用相同名字的变量,不同的函数属于不同的作用域,因此不同的函数中定义相同名字的变量不会冲突
最大公约数与最小公倍数
/*
最大公约数:即两个数据中公共约数的最大者。
求解的方式比较多,暴力穷举、辗转相除法、更相减损法、Stein算法算法
此处主要介绍:辗转相除法
思路:
例子:18和24的最大公约数
第一次:a = 18 b = 24 c = a%b = 18%24 = 18
循环中:a = 24 b=18
第二次:a = 24 b = 18 c = a%b = 24%18 = 6
循环中:a = 18 b = 6
第三次:a = 18 b = 6 c=a%b = 18%6 = 0
循环结束
此时b中的内容即为两个数中的最大公约数。
而最小个公倍数就等于两数相乘除以最大公约数
*/
#include<stdio.h>
int main()
{
int a = 18;
int b = 24;
int c = 0;
while(c=a%b)
{
a = b;
b = c;
}
printf("%d\n", b);
return 0;
}
//方法二,在while(1)里,int num=m>n?n:m,然后用num依次减一去试除两个数,满足&&时候break,这样求出最大公约数
//然后以同样的办法,把较大值给num,依次加一求最小公倍数
//如果用int,最小公倍数可能会溢出
//这个算法效率太低啦
打印正方形图案
/*
针对每行输入,输出用“*”组成的“空心”正方形,每个“*”后面有一个空格。
输入:
4
输出:
* * * *
* *
* *
* * * *
输入:
5
输出:
* * * * *
* *
* *
* *
* * * * *
*/
#include<stdio.h>
int main()
{
int n, i, j;
while (scanf("%d", &n) != EOF)
{
for (i = 1; i <= n; i++)
{
for (j = 1; j <= n; j++)
{
if (i == 1 || i == n || j == 1 || j == n)
printf("* ");
else
printf(" ");
}
printf("\n");
}
}
return 0;
}
//这道题想了很久啊 总是想着一行一行打印,找出每行相同的规律,这样的话这题是做不出的
去掉一个最高分和一个最低分,输出每组的平均成绩
#include<stdio.h>
//99 45 78 67 72 88 60
int main()
{
int arr[7] = { 0 };
int n = 0;
int max = 0;
int min = 100;
float avg = 0.0;
int sum = 0;
for (n = 0; n < 7; n++)
{
scanf("%d", &arr[n]);
sum += arr[n];
if (arr[n] < min)
min = arr[n];
if (arr[n] > max)
max = arr[n];
}
avg = (sum - min - max) / 5.0;
printf("%.2f", avg);
return 0;
}
//注意易错点,好久都没发现。就是不能定义max min都为0,这样会造成min一直是0的问题
//此题尽量做到初始化的min比最大的还大,初始化的max比最小的还小
三角形判断
方法一:
#include<stdio.h>
//三角形判断
int main()
{
int a = 0;
int b = 0;
int c = 0;
while (scanf("%d%d%d", &a, &b, &c) != EOF)
{
if (a + b > c && a + c > b && b + c > a)
{
if (a == b || a == c || b == c)
{
if(a == b && b == c)
printf("Equilateral triangle!\n");
else
printf("Isosceles triangle!\n");
}
else
printf("Ordinary triangle!\n");
}
else
printf("Not a triangle!\n");
}
return 0;
}
方法二:
#include<stdio.h>
//三角形判断
int main()
{
int a = 0;
int b = 0;
int c = 0;
while (scanf("%d%d%d", &a, &b, &c) != EOF)
//while (~scanf("%d%d%d", &a, &b, &c))
//这个while循环表示,如果scanf读取结束或失败(返回了EOF),那循环就不进去了,而EOF本质上上又是-1,内存里放的是32个1,对其取反就是32个0,也就是0(看成正整数),也就是说,当返回了EOF,就是0,不进入循环,如果不是-1,那也就不是0,也就进去了,就相当于不是返回EOF就进去了循环
{
if (a + b > c && a + c > b && b + c > a)
{
if (a == b && b == c)
printf("Equilateral triangle!\n");
else if ( a == b || a == c || b == c)
printf("Isosceles triangle!\n"); //注意这上面两个if和else if必须按照先等边再等腰的顺序
else
printf("Ordinary triangle!\n");
}
else
printf("Not a triangle!\n");
}
return 0;
}
//本题要注意如果不按照上面先等边再等腰的顺序,那么一个等边就会进入等腰的那个判断,由于是并列关系,所以只会进去其中一个,也就判断
scanf与getchar
#include<stdio.h>
//getchar和scanf会先进去缓冲区看看,如果有东西就不会等待你输入,而是直接拿走
int main()
{
int ch = 0;
char password[20] = { 0 };
printf("请输入密码:\n");
scanf("%s", password);
getchar();//他把缓冲区剩下的\n拿走
printf("请确认密码(Y/N):\n");//为了达到目标,在确认之前,希望把缓冲区的东西清理干净
ch = getchar();
if (ch == 'Y')
printf("确认成功\n");
else
printf("确认失败\n");
return 0;
}
//但以上代码的问题是,getchar一次只能清理或者读取一个有效字符! 假设你的密码带空格,scanf又默认遇到空格就不读取。
//就出现了问题
#include<stdio.h>
//修改
int main()
{
int ch = 0;
char password[20] = { 0 };
printf("请输入密码:\n");
scanf("%s", password);//scanf读完你的字符串后,自己会加一个'\0'
int temp = 0;//也可以不用temp直接读取
while ((temp=getchar()) != '\n');
{
;
}//清理所有内容
printf("请确认密码(Y/N):\n");
ch = getchar();
if (ch == 'Y')
printf("确认成功\n");
else
printf("确认失败\n");
return 0;
}
//这里扔人有错误,我scanf读取到的密码其实是不包括空格以后的东西的,这个时候就需要用gets函数来读取
只打印数字字符
#include<stdio.h>
//只打印数字字符
int main()
{
int ch = 0;
while ((ch = getchar()) != EOF)
{
if (ch<'0' || ch > '9')
continue;
putchar(ch);c
}
return 0;
}
九九乘法表
#include<stdio.h>
//实现一个函数,打印乘法口诀表,口诀表的行数和列数自己指定
//如:输入9,输出9 * 9口诀表,输出12,输出12 * 12的乘法口诀表。
void test(int a)
{
int i = 0;
int j = 0;
for (i = 1; i <= a; i++)
{
for (j = 1; j <= i; j++)
{
printf("%2d*%2d=%3d ", i, j, i * j);
}
printf("\n");
}
}
int main()
{
int num = 0;
printf("请输入");
scanf_s("%d", &num);
test(num);
return 0;
}
判断字母
#include<stdio.h>
//输入描述:(注意是多组输入)
//多组输入,每行输入包括一个字符。
//输出描述:
//针对每行输入,输出该字符是字母(YES)或不是(NO)。
int main()
{
int ch = 0;
while ((ch = getchar()) != EOF)//getchar读取失败不读取了,才会返回一个EOF
{
if ((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z'))
printf("YES\n");
else
printf("NO\n");
getchar();//处理掉\n
//如果没有他,我输入一个a,会接连打印yes和no,第二no立即输出是因为判断了\n不是字母
}
return 0;
}
//方法二
#include<stdio.h>
//从键盘任意输入一个字符,编程判断是否是字母(包括大小写)。
int main()
{
char a ='a';
while(scanf("%c", &a) != EOF)
{
if ((a >= 'A' && a <= 'Z') || (a >= 'a' && a <= 'z'))
printf("YES\n");
else if(a=='\n')
continue;
else
printf("NO\n");
}
return 0;
}
逗号表达式
#include <stdio.h>
int main()
{
int a, b, c;
a = 5;
c = ++a;//a = 6 c = 6
b = ++c, c++, ++a, a++;
// 逗号表达式的优先级,最低,这里先算b=++c, b得到的是++c后的结果,b是7
// b=++c 和后边的构成逗号表达式,依次从左向右计算的。
// 表达式结束时,c++和,++a,a++会给a+2,给c加1,此时c:8,a:8,b:7
b += a++ + c; // a先和c加,结果为16,在加上b的值7,比的结果为23,最后给a加1,a的值为9
printf("a = %d b = %d c = %d\n:", a, b, c); // a:9, b:23, c:8
return 0;
}
求两个整数二进制格式有多少个位不同
//我的代码
#include<stdio.h>
int main()
{
int a = 0;
int b = 0;
scanf("%d %d", &a, &b);
int i = 0;//循环计数
int c = 0;
int t1 = 1;//用于测试
int t2 = 0;
int count = 0;//用于计数
c = a ^ b;
for (i = 1; i <= 32; i++)
{
t2 = c & t1;
if (t2 == 1)
count++;
c = c >> 1;
}
printf("%d", count);
return 0;
}
//写的时候感觉变量定义得很慢 而且思路还是照着以前判断一个数二进制有几个一来的。
模拟实现strcpy
#include<stdio.h>
#include<string.h>
#include<assert.h>
//用好的代码风格模拟实现strcpy
//在文档里看到的Null或者null一般表示\0,而NULL一般表示的空指针,但他们的本质都是0
//char *strcpy( char *strDestination, const char *strSource )
void my_strcpy(char* dest, char* sou)
//dest指向目标空间,sou指向原字符串
{
while (*sou)//或者*sou != '\0'
{
*dest = *sou;
dest++;
sou++;
}
*dest = *sou;
//以上这一步是因为strcpy是会把最后的\0也拷贝进去的,但是while循环内部并不会做到
}
//优化1
void my_strcpy1(char* dest, char* sou)
{
while (*sou)
{
*dest++ = *sou++;
}
*dest = *sou;
}
//优化2
void my_strcpy2(char* dest, char* sou)
{
while (*dest++ = *sou++)
//这样写,当完成f拷贝之后,又加加了,也就是把0拷贝进去了,但是并不进入循环。
{
;
}
}
//优化3(对指针使用的安全性考虑,assert断言,是一个宏)
void my_strcpy3(char* dest, char* sou)
{
//这里如果直接对指针进行解引用的话是不安全的,如果传过来一个空指针程序就崩溃了。所以需要判断一下
//另外看到这里记得复习一下指针初阶部分的note笔记
/*if (sou == NULL || dest == NULL)
{
return;
//只要出现了一个空指针就直接返回,结束整个程序
}*/
assert(sou != NULL);
assert(dest != NULL);
//assert是断言,是一个宏,可以在debug里用它解决问题,然后release版本是会被优化掉的,在这里用它替代if语句
//如果这个表达式是假的,这个断言就会报错,而且报错的时候会将错误的具体信息告诉你。
//以上两个断言也可以写成assert(sou);assert(dest); 因为空指针本质上是0,0就报错了
//那进一步也可以优化成assert(sou && dest);
while (*dest++ = *sou++)
{
;
}
}
//优化4(关于const修饰,在msdn里查到的其实是有const修饰的,这点在下面讲)
//const提高了健壮性(鲁棒性)
void my_strcpy4(char* dest, const char* sou)
//这里不需要两个都加上const,因为我下面的代码只需要保证*sou不被改变,*dest是需要被改变的。这样就避免了下面while里面写反了
//所以对于指针的使用,需要养成好习惯
{
assert(sou && dest);
while (*dest++ = *sou++)
//如果有人while里面写成*sou++ = *dest++,会造成很严重的错误
//加了const在编译期间就会报错了
//如果不希望指针变量被修改,就加上const
{
;
}
}
//优化5,满分代码(对返回值的优化,也就是希望跟msdn查到的一样,直接把目标字符串的首地址返回)
char* my_strcpy5(char* dest, const char* sou)
{
assert(sou && dest);
char* ret = dest;
while (*dest++ = *sou++)
{
;
}
return ret;
//不直接return dest的原因是他加加之后不指首地址了
//这里我返回的是函数内部的一个变量,而不是ret的地址,所以是可以使用的(不能返回局部变量的地址)
//只是把值给ret了,销毁之后也没用过dest了
}
int main()
{
char arr1[] = "abcde";//abcde\0都会被拷贝
char arr2[10] = "xxxxxxxxxx";//abcde\0xxxx
//strcpy(arr2, arr1);
//\0也会被拷贝过去
char* ret = my_strcpy5(arr2, arr1);
printf("%s\n", ret);
//打印的时候就不一定要写arr2了,因为ret就是指向被修改空间的起始地址
return 0;
}
/*当我们熟知这个函数之后,就会考虑别的易错点。
第一
char arr1[]={'a','b','c'}和char arr2[]="xxxxxx";这俩就不适合上述代码,因为arr1会造成sou没有\0,从而while死循环
所以拷贝的时候,原字符串中一定要有\0
第二
即使满足原字符串有\0,程序员自己也要保证目标字符串的空间是足够大的(函数他自己不管这些)
第三
原字符串如果是{'a','b','\0','r'},只会拷贝到b
第四
目标空间可以被修改
char arr1[] = "abcde";char* arr2[10] = "xxxxxxxxxx";arr2是个指针,是常量字符串,放在常量区域,不能被修改。
(后面指针进阶会详细说)
*/
关于const修饰
#include<stdio.h>
//关于const修饰
int main()
{
int a = 0;
const int num = 10;
//num是常变量,是语法层面上不能对其改变
//num = 20;这样去修改就会报错
//int* p = #
//*p = 20;
但是用这种方法,绕过了语法层面,却能把num改变。底层原理是允许的,但语法上不应该这么做
//printf("%d", num);
//const int* p = #//或者int const* p=&num;也就是const在*左边
*p = 20; 不可以,*(p+1)也不可以,不说他的非法访问,单从语法上来看,p+1仍然受const保护。(这一点当初自己还没想到)
p = &a; 可以
const在*左边,修饰的是指针所指向的对象(*p),不能改变*p;但不是修饰指针变量本身(p),我扔可以写成p=别的地址
//int * const p = #
p = &a;不可以
*p = 20;可以
const在*右边,修饰的是指针变量本身(p),不能改变p(地址);但是所指向的内容(*p),可以修改
//const int* const p = #
两者都不能改的
return 0;
}
把函数处理结果的二个数据返回给主调函数
//形参用数组
#include<stdio.h>
void test(int arr[])
{
int a = 10;
int b = 20;
arr[0] = a;//0x00CFFEC0
arr[1] = b;//0x00CFFEC4
}
int main()
{
int arr[2] = { 0 };
test(arr);
printf("%d %d", arr[0], arr[1]);
return 0;
}
//这里数组的值确实被函数改变了,跟以前交换ab的值要区分一下,以前那个交换ab的值是把ab这俩都去创建了形参,地址是不一样的。
//而上面传输组,在函数里arr[0]的地址就是我main函数里一样的地址,是可以改变值的
//形参用二个指针
#include<stdio.h>
void test(int* px, int* py)
{
int a = 10;
int b = 20;
*px = a;
*py = b;
}
int main()
{
int x = 0;
int y = 0;
test(&x, &y);
printf("%d %d", x, y);
return 0;
}
//用二个全局变量
#include<stdio.h>
int x = 0;
int y = 0;
void test()
{
int a = 10;
int b = 20;
x = a;
y = b;
}
int main()
{
test(&x, &y);
printf("%d %d", x, y);
return 0;
}
理解函数返回
#include<stdio.h>
int test()
{
int a = 10;//函数内部创建的局部变量
return a;
}
int main()
{
int ret = test();
//可以理解成test()把值返回给ret,ret拿一块空间接收,销毁了不关他的事了
printf("%d\n", ret);
//printf("%d\n", a);报错
return 0;
}
#include<stdio.h>
int* test()
{
int a = 10;
return &a;//假设返回的地址是0xff40
}
int main()
{
int* ret = test();
//ret放了0xff40
//在这之后0xff40这块地址以及被回收了,如果我再写*ret=??,就是非法访问了!
printf("%d\n", *ret);
return 0;
}
冒泡排序(函数)
#include<stdio.h>
//数组作为函数参数
void BubbleSort(int arr[],int sz)//or (int* arr,int sz),本质上传的就是指针
{
//如果在函数内部计算sz的话,传过来的arr是首元素地址,是个指针,在32位平台上sizeof是4,arr[0]是int的大小也是4,就算出来错误的1
int i = 0;
for (i = 0; i < sz - 1; i++)//确定趟数
{
int flag = 1;//假设已经有序
int j = 0;
for (j = 0; j < sz - i - 1; j++)//第一趟,要比较9次;第二趟,要比较八次
{
if (arr[j] > arr[j + 1])
{
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
flag = 0;//只要发生了交换就把flag赋值为0,也就说明了假设该趟有序不成立
}
}
if (flag == 1)//如果再走一次flag保持了1,说明上一趟已经排好了,不必再继续了
{
break;//flag保持了1,说明已经有序了
}
}
//跳到这里,排序结束了
}
void Print(int* arr)//下面打印能用不同的方法,因为这里可以看成是首地址(指针),也可以是数组名
{
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", *(arr));// or arr[i] or *(arr + i) or *arr++
arr++;//这里是可以写arr++的,其实最好取名是int* p,这里的arr是个传参过来的指针变量,而不是常量
}
}
int main()
{
int arr[] = { 1,4,2,3,8,7,5,6,9,0 };//升序
int sz = sizeof(arr) / sizeof(arr[0]);
BubbleSort(arr,sz);
Print(arr);
return 0;
};
创建一个整形数组,完成对数组的初始化,打印,逆序
#include<stdio.h>
void Init(int* arr, int sz)
//假设全都初始化为i,这样逆序能看出效果
{
int i = 0;
for (i = 0; i < sz; i++)
{
arr[i] = i;
//or *(arr+i)=0
}
}
void Print(int* arr, int sz)
{
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
printf("\n");
}
void Reverse1(int* arr, int sz)
//第一种
{
int* left = arr;
int* right = arr + sz - 1;
while (left < right)
{
int temp = *left;
*left = *right;
*right = temp;
left++;
right--;
}
}
void Reverse2(int* arr, int sz)
//第二种
{
int left = 0;
int right = sz - 1;
while (left < right)
{
int temp = arr[left];
arr[left] = arr[right];
arr[right] = arr[left];
left++;
right--;
}
}
int main()
{
int arr[5];
int sz = sizeof(arr) / sizeof(arr[0]);
Init(arr, sz);
Print(arr, sz);
Reverse1(arr, sz);
printf("逆置后:\n");
Print(arr, sz);
Reverse2(arr, sz);
printf("再次逆置后:\n");
Print(arr, sz);
return 0;
}
判断一个数写成二进制有几个1
#include<stdio.h>
//思路是分两步,第一步判断最后一位是不是1,第二步让每个数都能去最后一位,然后用计数器计数
int main()
{
int a = 0;
scanf("%d", &a);
//00000000000000000000000000001111原反补相同
int b = 1;
//00000000000000000000000000000001原反补相同
int c = 0;
int i = 0;
int count = 0;
for (i = 1; i <= 32; i++)
{
c = a & b;
if (c == 1)
count++;
a = a >> 1;
}
printf("%d", count);
return 0;
}
//求得是补码里面有几个一
判断奇偶
#include<stdio.h>
int main()
{
int a = 0;
scanf("%d", &a);
int b = 1;
int c = 0;
if (c = a & b)
如果c是1,就说明a的最后一位是1,那肯定偶数加1,又因为是1,所以为真,需要执行;如果c是0,那a的最后一位也是0,为偶数
{
printf("是奇数");
}
else
printf("是偶数");
return 0;
}
指针加减求字符串长度
#include<stdio.h>
int my_strlen(char* p)
{
char* start = p;
while (*p != '\0')
{
p++;
}
return p - start;
//画图看,p减去start的绝对值得到他们中间元素的个数
}
int main()
{
char arr[20] = "abcdef";
int len = my_strlen(arr);
printf("%d", len);
return 0;
}
访问结构体成员
//用.或者->
//结构体变量.成员名
//结构体指针->成员名
//*(结构体指针).成员名(少用)
#include<stdio.h>
#include<string.h>
struct Book
{
char name[20];
float price;
char id[10];
};
void Print1(struct Book b)
{
printf("%s\n", b.name);
printf("%f\n", b.price);
printf("%s\n", b.id);
}
void Print2(struct Book* pb)
{
printf("%s\n", (*pb).name);
printf("%f\n", (*pb).price);
printf("%s\n", (*pb).id);
}
void Print3(struct Book* pb)
{
printf("%s\n", pb->name);
printf("%f\n", pb->price);
printf("%s\n", pb->id);
}
//23两种写法完全相同
int main()
{
struct Book b = { "C语言",55,"2021" };
//创建了一个变量b,b里面有上面定义的那些多内容
Print1(b);
b.price = 60;
//b.name = "c++";
//上面这个肯定是错的(表达式必须是可修改的左值)
//name是个数组名,是个地址(常量),不能直接把一个"c++"直接给一个地址,应该是把它放在地址所指向的空间里
//*(b.name) = "c++";
//这种写法也是错的(乱码),他只能找到第一个字符,而并非整个字符数组
//所以修改他的时候,需要用的strcpy函数
strcpy(b.name, "c++");
//char *strcpy( char *strDestination, const char *strSource ),后地址指向的字符串拷贝到前面地址指向的空间
Print1(b);
Print2(&b);
printf("\n");
Print3(&b);
return 0;
}
数组
访问一维数组的方法
#include<stdio.h>
int main()
{
int arr[5] = { 1,2,3,4,5 };
int* p= arr;
int i = 0;
for (i = 0; i < 5; i++)
{
printf("%d ", *(arr + i));
}
printf("\n========================\n");
for (i = 0; i < 5; i++)
{
printf("%d ", *(p+i));
}
printf("\n========================\n");
for (i = 0; i < 5; i++)
{
printf("%d ", *(p));
p++;
}
printf("\n========================\n");
for (i = 0; i < 5; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
//因为是连续存放的,所以指针加加可以找到下一个元素。又想到其实二维数组在内存中本质上和一维数组是相同的,所以二维数组还可以像以下这么打印
#include<stdio.h>
int main()
{
int arr[2][3] = { 1,2,3,4,5,6 };
int* p = &arr[0][0];
int i = 0;
for (i = 0; i < 6; i++)
{
printf("%d ", *(p + i));
}
printf("\n");
return 0;
}
一维二维数组的首地址加一
#include<stdio.h>
int main()
{
int arr1[2][2] = { 1,2,3,4 };
int arr2[5] = { 1,2,3,4,5 };
printf("%p\n", arr2);
printf("%p\n", &(arr2[0]));
printf("%p\n", arr2+1);//跳了四个字节
printf("====================\n");
printf("%p\n", arr1);
printf("%p\n", &(arr1[0][0]));
printf("%p\n", arr1+1);//跳了2*4=8个字节,其实他表示两个元素,每个元素是一个一组,跳一个元素也就是2个整型
return 0;
}
数组名的理解
#include<stdio.h>
//数组名
int main()
{
int arr[10] = { 1,2,3,4,5 };
printf("%p\n", arr);//首元素地址
printf("%p\n", &arr[0]);//首元素地址
printf("%p\n", &arr);//数组地址
printf("==================================\n");
printf("%p\n", arr+1);//差了4,跳一个元素
printf("%p\n", &arr[0]+1);//差了4,跳一个元素
printf("%p\n", &arr+1);//差了40(10个整型元素),跳过整个数组
//指向元素的指针跳过元素,指向数组的指针跳过整个数组
return 0;
}
//除了sizeof和&这两种情况,其余一切数组名都表示首元素地址
//所以char arr[10]="abc",sizeof求出来仍然是10字节,和放了什么东西没关系;而strlen(arr)求到的就是\0之前的,求到3
数组传参的理解
#include<stdio.h>
//对于数组传参的理解
void test(int* arr,int sz)
{
int i = 0;
for (i = 0; i < 5; i++)
{
printf("%d ", arr[i]);
//注意有arr[i]编译器会处理成*(arr+i),通过起始地址,就能访问到外部的那个数组(很类似于传值的方式)
}
}
int main()
{
int arr[5] = { 1,2,3,4,5 };
int sz = sizeof(arr) / sizeof(arr[0]);
test(arr, sz);
return 0;
}
操作符
/与%
#include<stdio.h>
int main()
{
int a = 10;
printf("%d\n", a % 4);//取模常常在随机数里用来缩小随机值的范围,模=比如说%4,那么结果不可能大于4
printf("%d\n", a / 3);//两边有一个浮点数,结果才会是浮点数
return 0;
//注意,%两边只针对整型
}
左移与右移
//左移只分最粗暴的一种,而右移分为逻辑(粗暴的)与算术
//右移移位有除2的效果,左移有乘2的效果
#include<stdio.h>
int main()
{
int a = -1;
int b = a >> 1;
printf("%d", b);
//结果仍然是-1,所以可以得出当前编译器采用的是算术右移
return 0;
}
注*整数在内存中以补码的形式存储,所以移位也是对补码进行的,但是打印出来的时候,是打印的原码,所以是有一个转化过程的
& | ^
#include<stdio.h>
//&有假(0)则假,都真(1)才真
//|有真就真,全假才假
//^相同为0,不同为1
int main()
{
int a = 3;
int b = -2;
int c = a & b;
int d = a | b;
int f = a ^ b;
printf("%d\n", c);//最高位看成符号位
printf("%u\n", c);//无符号数的打印
printf("\n");
printf("%d\n", d);
printf("%u\n", d);
printf("\n");
printf("%d\n", f);
printf("%u\n", f);
printf("\n");
return 0;
}
//a:
//00000000000000000000000000000011--原码
//00000000000000000000000000000011--反码
//00000000000000000000000000000011--补码
//b:
//10000000000000000000000000000010--原码
//11111111111111111111111111111101--反码
//11111111111111111111111111111110--补码
//对ab的补码进行&操作:
//00000000000000000000000000000011
//11111111111111111111111111111110
//结果:
//00000000000000000000000000000010-->得出的也是c的补码,看得出最高位是0,为整数,所以很容易得到原码,打印出来就是2
//对a和b的补码进行|操作:
//00000000000000000000000000000011
//11111111111111111111111111111110
//结果:
//11111111111111111111111111111111-->得出的是d的补码,假如是%d打印,最高位表示负数,打印的时候要计算出原码才能打印
//假如是%u打印,直接把结果的补码转成十进制
//上面补码的原码是10000000000000000000000000000001,为-1
//对ab的补码进行|操作:
//00000000000000000000000000000011
//11111111111111111111111111111110
//结果:
//11111111111111111111111111111101-->得出的是f的补码,%d打印需要转化;%u不用转化直接转成十进制
//上面补码的原码是10000000000000000000000000000011,为-3
左值与右值的理解
#include<stdio.h>
int main()
{
int a = 10;
int* p = &a;
*p = 20;//这样写是把20赋给了a,p找到了a的那块空间,然后把20放进去
int b = *p;//这样写是把a的值给了b,p是找到了a空间里的值
printf("%d ", a);
printf("%d ", b);
//*p放在左边右边的实质效果是不一样的
//比如int c=b,就是用的b的值;而b=20,就是用的b的那块空间
return 0;
}
//左值-->空间 右值-->空间的内容
sizeof补充
//sizeof内部的表达式不参与运算,因为在运行期间找不到内部的表达式
//单位是字节
#include<stdio.h>
int main()
{
int a = 5;
short s = 10;//2字节
printf("%d\n", sizeof(s = a + 2));//2 a+2算出的结果是int,强行放入short的话,就一定会发生整形截断
//只是从小端开始拿了两个字节放进去,如果数值比较小值属性没什么影响,但是最终这个表达式的类型属性会被改变成s的short类型
printf("%d\n",s);//10
return 0;
}
//sizeof内部的表达式不参与运算
//test.c-->编译 链接-->test.exe-->运行
//在编译期间,sizeof已经把上述解析成2了,那个表达式已经不在了。所以在运行期间,就没有机会去执行那个表达式
#include<stdio.h>
void test1(int arr[])//最好写成int*arr
//这里就算你写了int arr[10],本质上传过来的还是指针
{
printf("%d\n", sizeof(arr));
}
void test2(char ch[])//最好写成char*ch
{
printf("%d\n", sizeof(ch));
}
int main()
{
int arr[10] = { 0 };
char ch[10] = { 0 };
printf("%d\n", sizeof(arr));//40
printf("%d\n", sizeof(ch));//10
test1(arr);//4
test2(ch);//4
return 0;
}
//数组传参发生降级,就传的是首元素地址(指针变量)
//传参进去的其实是数组名,也就是首元素地址,地址也就是个指针变量,大小要么4(32位平台)要么8(64位平台)
~
//一个数不管是不是符号位,补码全都按位取反
#include<stdio.h>
int main()
{
int a = 0;//0看成正数,原反补相同
int b = ~a;//在内存里选择4列(4字节)看到了ff ff ff ff
//一个f表示15,二进制就是1111 八个f也就是32个1(-1),也就是a(int类型)不管是不是符号位统统取反的结果
printf("%d\n", b);//-1
printf("%u\n", b);//4294967295
return 0;
}
//一个随堂小练习 把某个数的补码某一位改成1,然后再改回去
#include<stdio.h>
int main()
{
int a = 13;//00000000000000000000000000001101把第二位改成1,再改回去
// |00000000000000000000000000000010即可
int b = 1;//00000000000000000000000000000001
a = a | (b << 1);
printf("%d\n", a);//00000000000000000000000000001111
//改回去只要把第二位改成0,&11111111111111111111111111111101即可
//而11111111111111111111111111111101=~(b<<1)
a = a & ~(b << 1);
printf("%d\n", a);
return 0;
}
//根据具体需求,来移不同位
//比如00000000000000000000000000001101把第5位改成1,再改回去
//那 |00000000000000000000000000010000
//改 &11111111111111111111111111101111
&&与||
//对于&&来说,左边为假,右边就不需要再算了
#include<stdio.h>
//360笔试题
int main()
{
int i = 0, a = 1, b = 2, c = 3, d = 4;
i = a++ && ++b && d++;
printf("%d %d %d %d", a, b, c, d);//1 2 3 4
//a先使用再++,由于a是0,所以a++ && b++不需要计算后面的b++,直接值为0,这样&&d++后面的d++也不需要计算整个表达式也为0,所以只有a++了
//而计算结果0还是给了i
return 0;
//如果a改成1,答案就是2 3 3 5(c没被用)
}
//对于||来说,左边为真,右边就不需要再算了
#include<stdio.h>
int main()
{
int i = 0, a = 1, b = 2, c = 3, d = 4;
i = a++ || ++b || d++;
printf("%d %d %d %d", a, b, c, d);//2 2 3 4
//a为1进去算,由于是||,左边真右边不用算,所以只有a进行了++
return 0;
//如果a改为0,a与b进行了++,但是到d,前面就真了,所以d不需要++,答案为1 3 3 4
}
条件操作符
#include<stdio.h>
//条件操作符,也叫三目操作符
int main()
{
int a = 10;
int b = 20;
int max = 0;
max = (a > b) ? (a) : (b);//括号括起来的地方就是可以放一个表达式的地方,表达式复杂要好括起来
//(a>b)?(max=a):(max=b);
printf("%d\n", max);
return 0;
}
逗号表达式的一种使用场景
#include<stdio.h>
int main()
{
int a = 0;
a = get_val();
count_val(a);
while (a > 0)
{
a = get_val();
count_val(a);
}
//while(a=get_val,count_val(a),a>0)
//{
//}
//以上逗号表达式就能完成上述代码的逻辑
return 0;
}
[]
#include<stdio.h>
//[]的进一步理解
int main()
{
int arr[5] = { 1,2,3,4,5 };
int i = 0;
for (i = 0; i < 5; i++)
{
printf("%p-------%p--------%p\n", &arr[i], arr + i,&arr[0]+i);
//arr+i,就是数组中下标为i的元素的地址,以上三个打印的结果
}
printf("%d\n", arr[4]);
printf("%d\n", 4[arr]);
//这里的[]是一个下标引用操作符,操作数是数组名加一个索引值
//我们知道arr[4]= *(arr+4)= *(4+arr),那*(4+arr)理应可以写成4[arr]。事实也证明这种写法是对的。
//其实对于一个操作符来讲 a+b可以写成b+a,交换了两个操作数也对;同样的道理,对于[]也有这种说法,只不过我们不经常使用
return 0;
}
整型提升
#include<stdio.h>
//只要发现表达式里面有达不到整型大小的变量时,就会发生整形提升。(其实也就是char8比特位,short16比特位)
int main()
{
char a = 3;//有符号char
//3的二进制序列是00000000000000000000000000000011,由于a只有一个字节,八个比特位,所以发生了截断(去了低位的八位),也就是00000011
//符号位是0
char b = 127;
//截断后存的是01111111,符号位是0
//以上定义的char,也表示是有符号char
char c = a + b;
//在运算之前,ab都是char类型,都是一个字节,要进行整型提升
//根据符号位进行提升,ab前面24个高位都补成0(负数补0,如果是无符号数,直接补0)
//a: 00000000000000000000000000000011
//b: 00000000000000000000000001111111
//相加 00000000000000000000000010000010,但是相加后放在了一个char的c里面,只能放八位,又发生了截断,c最终放了10000010
//又因为是char c,所以c是有符号的,而1就是他的符号位
printf("%d\n",c);
//这里用%d打印,又进行可整型提升,高位补符号位是1,所以是11111111111111111111111110000010,这个又是内存中的补码,最高位又是1
//所以以有符号数就行打印的时候,需要先减一再符号位不变按位取反求出原码10000000000000000000000001111110,为-126
return 0;
}
//总结大致步骤
//先把ab的补码(正数原反补相同)写出,然后截断之后给有符号的char a和char b,在运算a+b之前,按照规则进行整形提升,然后计算完之后得到的其实是补码(由于使用的是提升后的二进制序列进行计算的),然后给c的时候,又发生了截断。最后由于是%d打印的,所以又发生了整形提升,按规则补位之后,观察是直接打印还是说经过了计算转化成原码
指针
指针有什么意义
//第一个意义,访问内存的范围
#include<stdio.h>
int main()
{
int a = 0x11223344;
int b = 0x11223344;
//一个十六进制数字能换算成4个二进制位,两个就是一个字节(8个十六进制位,即8比特位)
int* p = &a;
*p = 0;
//这样去写,内存里显示八个十六进制位都变成了0
char* pc = &b;//类型不一样,但是由于是指针,理论上也可以存储
*pc = 0;
//这样去写,只改变了前两个十六进制位,也就是只改了一个字节,如上图
return 0;
}
//上面说明,不同的类型虽然大小一样大,但是类型决定了指针仅引用操作时,一次能访问的内存大小
//char*解引用访问一个字节,int*解引用访问四个字节
//第二个意义,指针类型决定了加减整数时的步长
#include<stdio.h>
int main()
{
int a = 10;
int* p1 = &a;
char* p2 = &a;
printf("%p----------", p1);
printf("%p\n", p1+1);
printf("%p----------", p2);
printf("%p\n", p2 + 1);
return 0;
}
//00EFF914----------00EFF918---int*
//00EFF914----------00EFF915---char*
//char*+1 跳一个字节,int*+1跳四个字节(整数*该元素的大小)
#include<stdio.h>
int main()
{
int arr[10] = { 0 };
int* p = arr;
//想用指针来操作这个数组的话,那每次肯定要跳过一个字节
int i = 0;
for (i = 0; i < 10; i++)
{
*(p + i) = 10;
//每次都跳过四个字节,也就是一个int
printf("%d ", arr[i]);
}
return 0;
}
//如果把arr给 char*,在内存可以看到,每次循环,就有一个字节的00变成了01
//而int*,是直接跳到了下一个元素来操作的
//所以确实需要根据需求,给指针分不同的类型
多级指针的理解
#include<stdio.h>
//多级指针的理解
int main()
{
int a = 10;
int *p = &a;
//其实这里把*p放在一看好理解,*p告诉我p是个指针,前面的int告诉我p指向的是一个int类型的
int* *pp = &p;
//后面的*告诉我pp是个指针,前面的*告诉我pp指向的是一个int*类型的
//三级指针也有同样的道理 int** *ppp=&pp
return 0;
}
指针的自增
#include<stdio.h>
int main()
{
int arr[5] = { 0 };
int i = 0;
for (i = 0; i < 5; i++)
scanf_s("%d", arr + i);
//这里不能写成arr++,因为arr算是一个常量,相当于不能写成5++。两个地址之间也不能互相的赋值
//但是数组传参上去的int* arr,就可以有arr++,因为那个arr是一个指针变量
for (i = 0; i < 5; i++)
{
printf("%d", *(arr + i));
printf("%d", arr[i]);
}//1122334455
return 0;
}
#include<stdio.h>
int main()
{
char arr[10] = "abcdf";
char* p = arr;
printf("%c", *(arr+1));//这样是合法的,相当于指针加一,也就是地址的变化,变化多少根据变量的类型
//printf("%c", *(arr++));//这样是不能的,报错是不能修改的左值
printf("%p ", (p + 1));
printf("%p ", (++p));//这两个结果相同
printf("%p ", (p++));//注意这个是先打印再自增
return 0;
}
#include<stdio.h>
int main()
{
int a = 10;
int* p = &a;
printf("%p\n", p);
printf("%p\n", p+1);
printf("%p\n", p++);
printf("%p\n", ++p);//这里打印
return 0;
}
虽然arr[7]已经越界了,但是他确实是存在内存中的