C语言基础练习题

常用

#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 = &num;
	//*p = 20;
	但是用这种方法,绕过了语法层面,却能把num改变。底层原理是允许的,但语法上不应该这么做
	//printf("%d", num);

	//const int* p = &num;//或者int const* p=&num;也就是const在*左边
	*p = 20; 不可以,*(p+1)也不可以,不说他的非法访问,单从语法上来看,p+1仍然受const保护。(这一点当初自己还没想到)
	p = &a;  可以
	const在*左边,修饰的是指针所指向的对象(*p),不能改变*p;但不是修饰指针变量本身(p),我扔可以写成p=别的地址

	//int * const p = &num;
	p = &a;不可以
	*p = 20;可以
	const在*右边,修饰的是指针变量本身(p),不能改变p(地址);但是所指向的内容(*p),可以修改

	//const int* const p = &num;
	两者都不能改的
	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
image-20210805181358410

左值与右值的理解

#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]已经越界了,但是他确实是存在内存中的

  • 4
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值