C语言复习-持续更新

该博客围绕C语言展开,涵盖数据类型、存储、变量常量、字符串及内存函数等基础内容,介绍关键字、操作符、分支循环、函数、递归等知识,还涉及指针、结构体等复杂概念,最后包含OJ题和复习错题,是C语言学习的全面总结。
摘要由CSDN通过智能技术生成

数据类型

int a = 10;//整型
char b = 1;//字符数据类型
short c = 3;//短整型
long d = 4;//长整型
float e = 12.3;//单精度浮点型
double f = 12.33;//双精度浮点型
printf("int大小:%d\n", sizeof(a)); //4
printf("char大小:%d\n", sizeof(b));//1
printf("short大小:%d\n", sizeof(c));//2
printf("long大小:%d\n", sizeof(d));//4
printf("long long大小:%d\n", sizeof(long long));//8
printf("float大小:%d\n", sizeof(e));//4
printf("double大小:%d\n", sizeof(f));//8

类型的意义

  • 使用这个类型开辟了多大的空间
  • 如何看待内存空间的视角(int能访问4个字节)

整形的范围

整型范围的公式: -2(n-1)~2(n-1)-1 (n为整型的内存占用位数),所以 int 类型 32 位那么就是 -(2^31) ~ 2^31-1 即 -2147483648~2147483647

0000 0001

1111 1111

1 0000 0000 …………………… 由于 char 为 8 位,最高位 1 被丢弃结果为 0

环形char

		char a[1000];
		int i;
		for (i = 0; i<1000; i++)
		{
			a[i] = -1 - i;
		}
		printf("%d", strlen(a)); //255
		return 0;

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vtHcyhyg-1674720201666)(C:\Users\75935\Desktop\csdn照片\环形char.png)]

数据的存储

原码,反码,补码

  • 原码:直接将二进制按照正负数的形式翻译成二进制就行
  • 反码:原码的符号位不变,其他位按位取反
  • 补码:反码+1
int main()
{
	char a = -1;
	//10000000000000000000000000000001 //在内存中先与整形看待,再截取
	//11111111111111111111111111111110
	//11111111111111111111111111111111
	//11111111 ---a
	//补码:11111111111111111111111111111111 整形提升补的符号位是0/1看a变量类型有无符号
	//反码:11111111111111111111111111111110
	//原码:10000000000000000000000000000001

	signed char b = -1;
	//11111111 ---b

	unsigned char c = -1;
	//11111111 ---c
	//补码00000000000000000000000011111111  整形提升
	//反码00000000000000000000000011111111 无符号数原,反,补码相同
	//原码00000000000000000000000011111111 	
	printf("a=%d,b=%d,c=%d", a, b, c);// %d有符号整形
    
    char a = 128; /char a=-128 //打印出来的值是一样的
	printf("%u\n",a);
    

    
    
	unsigned int  a = -1;
	//10000000000000000000000000000001
	//11111111111111111111111111111110
	//11111111111111111111111111111111 //补码
	//在内存中的存储2个都是补码一样的,
	//当以%d打印的时候,就会转为原码再打印
	//当以%u打印的时候,因为是无符号的a原码和补码一样不用转
	printf("%d\n", a); //-1
	printf("%u\n", a); //4294967295
    
    


  unsigned char a = 200;
  unsigned char b = 100;
  unsigned char c = 0;
  c = a + b;
  printf(%d %d”, a+b,c); //44 300
  //a+b的结果是用一个四字节的整数接收的,不会越界。
  //而c已经在c = a + b这一步中丢弃了最高位的1,所以只能是300-256得到的44了。
//由于printf是可变参数的函数,所以后面参数的类型是未知的,所以甭管你传入的是什么类型,
//printf只会根据类型的不同将用两种不同的长度存储。其中8字节的只有long long、float和double
//(注意float会处理成double再传入),其他类型都是4字节。所以虽然a + b的类型是char,
//实际接收时还是用一个四字节整数接收的。
//另外,读取时,%lld、%llx等整型方式和%f、%lf等浮点型方式读8字节,其他读4字节。


	return 0;
}

正数的原,反,补码相同,对整形来说数据存放在内存中的是补码

在计算机系统中,数值一律用补码来表示和存储。原因在于,使用补码,可以将符号位和数值域统
一处理
同时,加法和减法也可以统一处理(CPU只有加法器)此外,补码与原码相互转换,其运算过程
是相同的,不需要额外的硬件电路

大端,小端

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

小端模式:数据的低位保存在内存的低地址中,数据的高位保存在高地址中

例如在一个16bit的short型的a中,在内存的地址为0x1234,a的值为0x5566,那么0x55为高字节,0x66为低字节

为什么有大小端呢?

在计算机系统中,我们以字节为单位,一个字节等于个8bit,对于大于8位的处理器存在如何安排多个字节的问题

验证机器大小端

int main()
{
	int a =1;
	char *p = &a;
	if (*p = 1){
		printf("小端\n");
	}
	else
		printf("大端\n");
	return 0;
}

浮点型在内存的存储

浮点数包括:float,double,long double

浮点数表示

(-1)^S * M * 2^E
(-1)^s表示符号位,当s=0,V为正数;当s=1,V为负数。
M表示有效数字,大于等于1,小于2。 //保留M时舍弃第一位1
2^E表示指数位 E为一个无符号整数(unsigned int )

E为8位,它的取值范围为0255;如果E为11位,它的取值范围为02047

要加个中间数8位加127,11位加1023

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oN4o5rti-1674720201668)(C:\Users\75935\Desktop\csdn照片\浮点数内存存储表.png)]

关于E的取出

  1. E不为全0或者全1

    8位-127,11位-1023就可以得出真实的值,再将有效数字M前加上第一位的1

  2. E为全0

    浮点数的指数E等于1-127(或者1-1023)即为真实值,
    有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示±0,以及接近于
    0的很小的数字

  3. E为全1

    如果有效数字M全为0,表示±无穷大(正负取决于符号位s)

	float f = 4.5f;
	//100.1 小数点后面的1代表2^-1 
	//(-1)^0 *1.001*2*2
	//S=0
	//M=1.001
	//E=2 +127
	//0 10000001 10010000 00000000 0000000  
	//40 90 00 00	16进制 4个一组

	int n = 9;
	//0 00000000 00000000000000000001001
	//写成浮点数
	//-1^0*0.00000000000000000001001*2^(-126)=1.001*2^(-146`)  
	float *pFloat = (float *)&n;
	printf("n的值为:%d\n", n);	//n的值为:9
	printf("*pFloat的值为:%f\n", *pFloat);	//*pFloat的值为:0.000000
	*pFloat = 9.0;
	//1001.0
	//1.001*2^3
	//0 10000010 10010000 00000000 0000000
	printf("num的值为:%d\n", n);//num的值为:1091567616
	printf("*pFloat的值为:%f\n", *pFloat);//*pFloat的值为:9.000000

变量/常量

作用域:变量可用范围就是该作用域

  • 局部变量作用域:变量所在的局部范围
  • 全局变量作用域:整个工程

生命周期

变量的生命周期是指创建到销毁的时间段

  • 局部变量生命周期:进入作用域开始到出作用域结束
  • 全局变量生命周期:整个程序的生命周期
int NUM=1221 //全局变量 全局变量,没有给初始值时,编译其会默认将其初始化为0
int main
{	
	int NUM = 12;//局部变量
	printf("NUM:%d\n", NUM);//局部变量和全局变量同名时,优先使用局部变量

	//局部变量的使用
	int num1 = 2;
	int num2 = 3;
	scanf_s("%d%d", &num1, &num2);
	int sub = num1 + num2;
	printf("sub=%d+%d=%d\n",num1,num2, sub);
    
}

常量

  1. 整形常量:10进制,八进制(017 -0开头的就是8进制),16进制那些数
  2. 实型常量:浮点型常量float,double,e与E,表示以10为底数的幂数,且e与E后面必须跟整数,若是小数,也是错误的
  3. 字符型、字符串常量:如 ‘ a’,“ab” 必须加这个符号
  4. 转义字符常量:’ \0 ’
  5. define宏定义类型的
#define g 12;//denfine修饰的常量

//枚举特点
//第一个枚举成员的默认值为整型的 0,后续枚举成员的值在前一个成员上加 1
enum DAY{
	MON=1,//枚举常量
	TUE,
	WED,
};

int main
{
	//常量
	100;//字面常量
	const int const_a = 10;//const修饰常变量,a是不可以被修改的
	enum Student day;
	day = WED;
    char *p="advd";//不能修改的
	printf("%d\n", WED);
    
}

字符串

int main()
{
	//字符串
	//字符串的结束标志是一个 \0 的转义字符。在计算字符串长度的时候 \0 是结束标志
	char arr1[] = "hello";
	char arr2[] = { 'h', 'e', 'l' ,'l','o'}; //error
	char arr3[] = { 'h', 'e', 'l',  'l', 'o','\0' };
	printf("%s\n", arr1);
	printf("%s\n", arr2);
	printf("%s\n", arr3);

	return 0;
}

转义字符 \

int main()
{
	//strlen从前往后依次检测,直到遇到'\0'是就终止检测。
	//转义字符
	printf("%c\n", '\'');
	printf("%d\n", strlen("abcdef"));//6
	// \62被解析成一个转义字符
	printf("%d\n", strlen("c:\test\628\test.c")); //14
    
    printf("%d\n", strlen("c:\test\121")) //7
    // \t转移字符,水平制表  
    //是讲121看做8进制数组,转换为10进制后的81,作业为ASCII码值的字符,即:字符'Q' ,   
	return 0;
}

字符串函数

strlen

size_t strlen ( const char * str )

  • 字符串已经 ‘\0’ 作为结束标志,strlen函数返回的是在字符串中 ‘\0’ 前面出现的字符个数(不包

    含 ‘\0’ )。

  • 参数指向的字符串必须要以 ‘\0’ 结束。

  • 注意函数的返回值为size_t,是无符号的


//模拟实现strlen
//1.计数器
//int my_strlen(const char*ch)
//{
//	int count = 0;
//	while (*ch)
//	{
//		count++;
//		ch++;
//	}
//	return count;
//}

//2.指针-指针
//int my_strlen(const char *ch)
//{
//	const char *str = ch;
//	while (*ch)
//	{
//		ch++;
//	}
//	return ch - str;
//}

//3.不创建临时变量计数器
int my_strlen(const char*ch)
{
	if (*ch == '\0')
		return 0;
	else
		return 1 + my_strlen(ch + 1);
		
}

int main()
{
	char*str1 = "abcdef";
	int ret=my_strlen(str1);
	printf("%d\n", ret);

}

strcpy

char strcpy(char * destination, const char * source );*

  • 源字符串必须以 ‘\0’ 结束。
  • 会将源字符串中的 ‘\0’ 拷贝到目标空间。
  • 目标空间必须足够大,以确保能存放源字符串。
  • 目标空间必须可变
char *my_strcpy(char* dest, const char*src)
{
	char *ret = dest;
	assert(dest != NULL);
	assert(src != NULL);
	while ((*dest++ = *src++));
	return ret;
}

//strcpy
int main()
{
	char ch[20];
	my_strcpy(ch, "hh");

	return 0;
}

strncpy

char * strncpy(char * destination, const char * source, size_t num);

拷贝num个字符从源字符串到目标空间。
如果源字符串的长度小于num,则拷贝完源字符串之后,在目标的后边追加0,直到num个


int main()
{
	char a[10] = "abcd";
	char b[5] = "efg";
	strncpy(a, b, 4);
	return 0;
}

strcat

**char *strcat( char strDestination, const char strSource );

自己给自己追加?

这个函数不能实现自己给自己追加的原因在于;两个指针同时操作一个字符串,
当*dest找到 '\0' 之后,*src开始追加,当追加完一个字符后,*src往后走一位,
*dest指向的 '\0'被覆盖,*dest就必须往后走一位,找到下一个'\0',找到之后*src又开始追加,
不断重复,导致程序崩溃。

char *my_strcat(char *dest, const char *src) //字符串追加
{
	assert(dest != NULL);
	assert(src != NULL);
	char *ret = dest;
    //1.找尾
	while (*dest)
	{
		dest++;
	}
    //2.追加
	while ((*dest++ = *src++))
	{
		;
	}

	return ret;

}
int main()
{
	char arr[20] = "hello";
	char *p = "world";
	char *ret=my_strcat(arr, p);
	printf("%s\n", ret);
	return 0;
}

strncat

char * strncat ( char * destination, const char * source, size_t num );

int main()
{
	
	char a[20] = "abcdefg";
	char b[10] = "hijk";
	strncat(a, b, 7);
	return 0;
}

strcmp

比较的是字符串的内容,对应的ASCLL值

int strcmp ( const char * str1, const char * str2 );

  • 第一个字符串大于第二个字符串,则返回大于0的数字
  • 第一个字符串等于第二个字符串,则返回0
  • 第一个字符串小于第二个字符串,则返回小于0的数字

int my_strcmp (const char * src, const char * dst)
{
	int ret = 0 ;
	assert(src != NULL);
	assert(dest != NULL);
while( ! (ret = *(unsigned char *)src - *(unsigned char *)dst) && *dst)
	++src, ++dst;
	if ( ret < 0 )
	ret = -1 ;
	else if ( ret > 0 )
	ret = 1 ;
	return( ret );
}

strncmp

int strncmp ( const char * str1, const char * str2, size_t num );

int main()
{
	char a[][5] = {"AB11","ab11","AB22"};
	for (int i = 0; i < 3; i++)
	{
		if (strncmp(a[i], "ABxx", 2) == 0)
			printf("%s\n",a[i]);
	}
	return 0;
}

strstr

char * strstr ( const char *str1, const char * str2);

字符串查找,在str1里找str2,找到就返回第一次找到的位置,找不到返回NULL

char* my_strstr(const char*str1, const char* str2)
{
	assert(str1 && str2);
	char* s1;
	char* s2;
	char* cp = str1;
	
	if (*str2 == '\0')
		return str1;
	//cp是来记录从这个字母开始比较的,如果这次比较失败就++,下个字母开始重新比较。
	while (*cp)
	{
		s1 = cp;
		s2 = str2;

		//while (*s1!='\0'  && *s2 != '\0' && *s1 == *s2)
		while (*s1 && *s2 && *s1 == *s2)
		{
			s1++;
			s2++;
		}
		if (*s2 == '\0')
		{
			return cp;
		}
		cp++;
	}

	//找不到
	return NULL;
}
int main()
{
	char a[50] = "is an apple ,oh a apple";
	char b[10] = "applee"; 
	char *pret = strstr(a, b);
	if (pret == NULL)
	{
		printf("找不到\n");
	}
	else
		printf("找到了!\n");
	return 0;
}

strtok

char * strtok ( char * str, const char * sep )

  1. sep参数是个字符串,定义了用作分隔符的字符集合

  2. 第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标

    记。

  3. strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(注:

    strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容

    并且可修改。)

  4. strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串

    中的位置。

  5. strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记。

  6. 如果字符串中不存在更多的标记,则返回 NULL 指针。


int main()
{
	
	char str1[] = "gitee.com/jiantaoWU";
	char sep[] = "./";
	char tmp[100] = { 0 };
	char *ret = NULL;
	strcpy(tmp, str1);
	//strtok(tmp, sep); //gitee\0 com/jiantaoWU 返回g的地址
	//strtok(NULL, sep);//记录截端的位置并从这开始c
    
    //ret!=NULL说明找到了
	for (ret = strtok(tmp, sep); ret != NULL; ret = strtok(NULL, sep))
	{
		printf("%s\n", ret);
	}
	/*gitee
	com
	jiantaoWU*/
}

strerr

char * strerror ( int errnum ); //返回错误码对应的错误信息

#include <stdio.h>
#include <string.h>
#include <errno.h>//必须包含的头文件

内存函数

memcpy

void * memcpy ( void * destination, const void * source, size_t num )

  • 函数memcpy从source的位置开始向后复制num个字节的数据到destination的内存位置。
  • 这个函数在遇到 ‘\0’ 的时候并不会停下来。
  • 如果source和destination有任何的重叠,复制的结果都是未定义的
void *my_memcpy(void* dest, const void *str, size_t num)
{
	//一个字节一个字节拷贝
	void *ret = dest;
	assert(dest&&str);
	while (num--)
	{
		*(char*)dest = *(char*)str;
		dest = (char*)dest + 1;
		str = (char*)str + 1;
	}
	return ret;
}
int main()
{
	int arr1[] = { 1,2,3,4,5,6,7,8 };
	int arr2[10] = { 0 };
	my_memcpy(arr2, arr1, 8 * sizeof(int));
    my_memcpy(arr1+2,arr1,3*sizeof(int)); //error 不能重叠拷贝 
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", arr2[i]);
	}
	return 0;
}

memmove

void * memmove ( void * destination, const void * source, size_t num )

和memcpy的差别就是memmove函数处理的源内存块和目标内存块是可以重叠的。
如果源空间和目标空间出现重叠,就得使用memmove函数处理。

//memmove
void*my_memove(void *dest, const void*str, size_t num)
{
	assert(dest&&str); 
	void *ret = dest;
	if (dest > str)
	{
		//后向前拷贝
		while (num--)
		{
			*((char*)dest+num) = *((char*)str+num);	
		}
	}
	else
	{
		//前向后拷贝
		while (num--)
		{
			*(char*)dest = *(char*)str;
			str = (char*)str+1;
			dest = (char*)dest + 1;
		}
	}
	return ret;
}
int main()
{
	int arr[10] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	my_memove(arr, arr+2, 4*sizeof(int));
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

memcmp

int memcmp ( const void * ptr1,const void * ptr2,size_t num );

比较从ptr1和ptr2指针开始的num个字节

return <0 prt1<ptr2

memset

void *memset(void *dest, int c, size_t count);


//memset
int main()
{
	int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
	memset(arr, 0, 20);//把前20个字节全部设置为0
	for (int i = 0; i < 10; i++)
	{
		printf("%d ", arr[i]);
	}
	return 0;
}

动态内存管理

关键字

typedef

对类型定义别名,可以是结构体

typedef unsigned int uint;
typedef struct Number{
	int ID;

}Num;

//注意 使用typedef 指针时最好 typedef const int * p_int
typedef int* p_int; //error
void(const p_int a) //这里相当于int *const 相当于指针是const指针,
				  //而不是我们要的const *int 
   
int main()
{
	//typedef 类型重命名
	unsigned int a = 0;
	uint d= 0; //2个类型的一样的
	Num num1;
	num1.ID = 12;
	printf("%d\n", num1.ID);
	
}

define

  • #define又称宏定义,标识符为所定义的宏名,简称宏,define 的功能是将标识符定义为常量
  • 定义的标识符不占内存,只是一个临时的符号
#define SUB(x,y) ((x)-(y))
#define NUM 12
#define DSUB SUB(x,y)*2  //宏可以套

//#define   宏是替换 可以用#undef 终止宏的作用域
int main()
{
	printf("%d\n", NUM);
	int x = 2;
	int y = 3;
	int result = SUB(x, y);
	printf("NUM\n");//宏在" " 里不进行替换
	printf("%d\n", result);
	printf("%d\n", DSUB);
	 
	return 0;
}

#define vs typedef

  • 宏定义只是简单的字符串替换,由预处理器来处理;而 typedef 是在编译阶段由编译器处理的,它并不是简单的字符串替换,而给原有的数据类型起一个新的名字,将它作为一种新的数据类型。

  • #define p_int1 int*
    typedef int * p_int2
    int main()
    {
        p_int1 a,b; // int *a ,int b
        p_int2 c,d;// int *a ,int *b
    }
    

static

static 被引入以告知编译器,将变量存储在程序的静态存储区而非栈上空间

static修饰函数

  • 非静态函数可以在另一个文件直接引用,static函数只能在该文件中使用不能跨文件调用会出现链接错误
  • 不同的文件可以使用相同名字的静态函数,互不影响

static修饰局部变量

  • 延长了局部变量的生命周期
  • 对静态关键字修饰的局部变量的初始化。
  • 在编译的过程中,会在数据区为该变量开辟空间,并对其进行初始化,如果代码中未对其进行初始化,则系统默认初始化为0
int fun(int x){
    static a; //编译时初始化为0
    static b=x;//等运行fun函数才进行赋值
}

static修饰全局变量

  • 在全局数据区分配存储空间,且编译器会自动对其初始化。
  • 静态全局变量仅对当前文件能使用,其他文件可以用相同的名字
void test(){
	static int i = 0;
	i++;
	printf("%d ", i);
}

 void fun(){
	printf("fun()\n");
}

int main()
{
	extern c;
	//static int a;
	//printf("%d\n", a); //1.自动初始化为0
	/*static int arr1[3];*/

	//2.延长了局部变量的生命周期
	int i = 5;
	while (i--){
		test();//不加static 11111 加了12345 
	}
	
	//fun1(); //3.static 修饰函数只能在本文件中使用
	//printf("%d\n", c);//4.static修饰全局变量也只能在本文件中使用

	return 0;
}

逗号表达式vs三目操作符

  • C语言中的三目运算符表达式格式为:a ? b : c ,其规则为:

当a为真的时候返回b的值,否则返回c的值

  • 逗号表达式:exp1, exp2, expp3, … , expN-1, expN

    表达式的值为最后一个表达式的值,从左向右计算

getchar()

#include<stdio.h>
int main()
{
    char ch=0;
    while((ch=getchar())!=EOF)
    {
        if((ch>='A'&&ch<='Z')||(ch>='a'&&ch<='z'))
        {
          printf("YES\n"); 
         
        }

        else
      	  printf("NO\n");
         getchar(); //清除"\n"
            
    }
    
    return 0;
}

const

const修饰指针变量的时候

  1. cosnt 放在*号的左边const int *** 修饰的指针指向的内容,保证指针所指向的内容不会被修改但**指针本身可以修改
  2. const 放在*号右边 int const 修饰的是指针本身,保证指针自己不可以被修改*,但是指向的内容可以修改
void test1()
{
	int a = 3;
	int b = 2;
	int *p = &a;
	*p = 20; //a=20 p=2 b=2
	p = &b;
}

void test2()
{
	int a = 3;
	int b = 2;
	const int *p = &a;
	/**p = 20;*/
	p = &b; //a=3 b=2 p=2 //const保护了a变量不会被修改
}

void test3()
{
	int a = 3;
	int b = 2;
	int *const p = &a;
	*p = 20; //a=20 b=2 p=20 //const保护p不会被修改
	//p = &b;
}


int main()
{
	//test1();//无const
	//test2();//const 在*左边 
	test3();//const 在*右边
	return 0;
}

操作符

左移,右移

移位操作符的操作数只能是整数,不要移位负数

  • 左移

左边抛弃、右边补0

int num=1;
 num<<1; //num本身的值没有改变
  • 右移

    1. 算术右移:左边的值用符号位补,右丢弃
    2. 逻辑右移:左补零,右丢弃

位操作符

& 按位与 :2个为1才是一

| 按位或:二个二进位有一个为1时,结果位就为1

^ 按位异或:两个相应bit位相同,则结果为0,否则为1

逻辑操作符


int main()
{
	int i = 0, a = 0, b = 2, c = 3, d = 4;
	/*i = a++ && ++b && d++;*/ //运算完a++为假之后就不再运算后面的
	i = a++||++b||d++;//没运算d++;
	printf("a = %d\n b = %d\n c = %d\nd = %d\n", a, b, c, d);
	return 0;
}

隐式类型转换

整形提升

表达式的整型运算要在CPU的相应运算器件内执行,CPU内整型运算器(ALU)的操作数的字节长度
一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型的相加,在CPU执行时实际上也要先转换为CPU内整型操作数的标准长
度。
通用CPU(general-purpose CPU)是难以直接实现两个8比特字节直接相加运算(虽然机器指令
中可能有这种字节相加指令)。所以,表达式中各种长度可能小于int长度的整型值,都必须先转
换为int或unsigned int,然后才能送入CPU去执行运算。

整形提升是按照变量的数据类型的符号位来提升的  
//负数的整形提升
char c1 = -1;
变量c1的二进制位(补码)中只有8个比特位:
1111111
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为1
提升之后的结果是:
11111111111111111111111111111111
//正数的整形提升
char c2 = 1;
变量c2的二进制位(补码)中只有8个比特位:
00000001
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为0
提升之后的结果是:
00000000000000000000000000000001
//无符号整形提升,高位补0
    

发生运算的时候也会发生整形提升


int main()
{
	char ch = 1;
	printf("%u\n", sizeof(ch));//unsigned int 说明原码反码补码是一样的%u
	printf("%u\n", sizeof(+ch));//整形提升
	printf("%u\n", sizeof(-ch));
	return 0;
}

整数中有多少个比特位1


int main()
{
	
	int num = 3;
	int count = 0;
	for (int i = 0; i < 32; i++){
		if (num&(1 << i))
			count++;
	}
	printf("%d\n", count); 
}

//优化,数据的二进制比特位中有几个1,循环就循环几次,而且中间采用了位运算,处理起来比较高效
int main()
{
	int num = -1;
	int i = 0;
	int count = 0;//计数
	while (num)
	{
		count++;
		num = num&(num - 1);
	}
	printf("二进制中1的个数 = %d\n", count);
	return 0;
}

两个int(32位)整数m和n的二进制表达中,有多少个位(bit)不同?

/*
思路:
1. 先将m和n进行按位异或,此时m和n相同的二进制比特位清零,不同的二进制比特位为1
2. 统计异或完成后结果的二进制比特位中有多少个1即可
*/
#include <stdio.h>
int calc_diff_bit(int m, int n)
{
	int tmp = m^n;
	int count = 0;
	while(tmp)
	{
		tmp = tmp&(tmp-1);
		count++;
	}
	return count;
}
 
 
int main()
{
 int m,n;
 while(scanf("%d %d", &m, &n) == 2)
 {
     printf("%d\n", calc_diff_bit(m, n));
 }
 return 0;
}



获取一个整数二进制序列中所有的偶数位和奇数位,分别打印出二进制序列*


int main()
{
	int a = 3;
	
	for (int i = 31; i >=1; i-=2){
		printf("%d", (a>>i)&1);
	}
	printf("\n");
	for (int i = 30; i >= 0; i -= 2){
		printf("%d", (a>>i) & 1);
	}
	return 0;
}

水仙花数

int main()
{
	int i = 0;
	for(i=0; i<=999999; i++)
	{
		int count = 1;
		int tmp = i;
		int sum = 0;
		//判断i是否为水仙花数
		//1. 求判断数字的位数
		while(tmp/10)
		{
			count++;
			tmp = tmp/10;
		}
     
		//2. 计算每一位的次方和
		tmp = i;
		while(tmp)
		{
			sum += pow(tmp%10, count);
			tmp = tmp/10;
		}
     
		//3. 判断
		if(sum == i)
			printf("%d ", i);
	}
	return 0;
}

分支和循环

for

for(初始化语句; 循环条件; 自增或自减){
语句块
}

都能进行忽略

switch

int main()
{
	int day = 0;
	scanf("%d", &day);
     //switch语句中表达式的类型只能是:整形和枚举类型
	switch (day)
	{
//case语句后一般放整形结果的常量表达式或者枚举类型,枚举类型也可以看成是一个特殊的常量
	case 1:
		printf("MON\n");
		//break
		//1.循环会立即终止,且程序流将继续执行紧接着循环的下一条语句。
		//2.它可用于终止 switch 语句中的一个 case。
        //3.switch的每个case之后如果没有加break语句,
         //当前case执行结束后,会继续执行紧跟case中的语句。
		break;
	case 2:
		printf("TUS\n");
		break;

	default: //都不是就走这
		printf("error\n");
		break;
	case 3printf(" hhhh");//如果default没有break会走到这里
	}
}

猜数字游戏

#include <stdlib.h>
#include <time.h>
//猜数字游戏
//猜1-100的数字
//有菜单的
void menu()
{
	printf("####1.play#####\n");
	printf("####0.play#####\n");

}

void game()
{
	int n = 0;
    //C 库函数 int rand(void) 返回一个范围在 0 到 RAND_MAX 之间的伪随机数。
	//RAND_MAX 是一个常量,它的默认值在不同的实现中会有所不同,但是值至少是 32767。
	int rund_num = rand() % 100 + 1;
	
	while (1)
	{
		printf("输入1一个数字!\n");
		scanf_s("%d", &n);
		if (rund_num == n){
			printf("对了\n"); 
		}
		else if (rund_num < n){
			printf("big\n"); 
		}
		else{
			printf("small\n");
		}
	}
}

int main()
{
	int input=0;
    // /* 初始化随机数发生器 */
	srand((unsigned int)time(NULL));
	do{
		menu();
		printf("请输入:\n");
		scanf_s("%d", &input);
		
		switch (input)
		{
		case 1:
			game();
			break;
		case 0:
			printf("退出游戏!\n");
			break;
		default:
			printf("输入错误请重新输入!\n");
			break;
		}
	} while (input);
	return 0;
}


else

else会与最近的if进行匹配

while与for

#include<stdio.h>

int main()
{
	//int i = 0;
	//while (i <= 5){
	//	i++;
	//	printf("%d\n", i);
	//	if(3==i)
	//		//break; // 终止循环 0,1,2
	//		continue;// 去到判断条件 0,1,2,3,4,5
	//
	//}

	//for (i = 0; i <= 5;i++){
	//	
	//	if (i == 3){
	//		break;// 0, 1, 2
	//		continue; // 0,1,2,4,5
	//	}
	//	printf("%d\n", i);

	//}

	return 0;
}

do while

int main()
{

	/*int i = 5;
	do{
			printf("%d \n", i);
	} while (i < 5);
*/
	
	//int i = 5;
	//do{
	//	if (3 == i){
	//		break;
	//		printf("%d \n", i);
	//	}
	//} while (i < 5);

	int i = 5;
	do{
		if (3 == i){
			continue;
			printf("%d \n", i);
		}
	} while (i < 5);


	return 0;
}

二分查找

//1.找中间值
//2.与key值比较,大的mid-1
//3.左右都是下标值
int main()
{
	int arr[] = { 1, 2, 3, 4,5};
	int key =5;
	int left = 0;
	int right = sizeof(arr) / sizeof(arr[0]) - 1;
	int mid = 0;
	while (left <= right)
	{
		mid = (left + right) >> 1;
		if (arr[mid] > key)
		{
			right = mid - 1;
		}
		else if (arr[mid] < key)
		{
			left = mid+1;
			
		}
		else{
			break;
		}
	}

	if (arr[mid] == key)
	{
		printf("找到了\n");
	}
	else{
		printf("找不到\n");
	}
	return 0;
}

函数

形参

  • 函数()里面的变量,形参只有在函数被调用的时候才会实例化(分配内存)
  • 形参在调用完之后就会销毁了,只在函数中存在
  • 形参实例化后相当于实参的一份临时拷贝

实参

  • 真实传给函数的参数,叫实参。
  • 实参可以是:常量、变量、表达式、函数等。
  • 无论实参是何种类型的量,在进行函数调用时,它们都必须有确定的值,以便把这些值传送给形参
  • 形参和实参在不同的函数中,即不同的作用域,因此形参和实参可以同名

函数的声明与定义

声明

  • 告诉编译器有个函数,名字叫什么,返回值叫什么
  • 声明放在使用之前,先声明再使用
  • 声明一般放在头文件中

定义

  • 对函数的实现

递归

递归条件

  • 存在限制条件,当满足条件的时候,便不再递归
  • 每次递归越来越接近这个条件

递归容易栈溢出

字符串逆序

//编写一个函数 reverse_string(char * string)(递归实现)
//实现:将参数字符串中的字符反向排列,不是逆序打印。
//要求:不能使用C函数库中的字符串操作函数。
//比如 :
//char arr[] = "abcdef";
//逆序之后数组的内容变成:fedcba


//非递归 left right指针一头一尾相互交换直到相遇交换结束
//void reverse_string(char arr[])
//{
//	char* left = arr;
//	char* right = arr + strlen(arr) - 1;
//	while (left < right)
//	{
//		char tmp = *left;
//		*left = *right;
//		*right = tmp;
//
//		right--;
//		left++;
//	}
//	printf("%s\n", arr);
//}


//递归
void reverse_string(char* arr)
{
	int len = strlen(arr);
	char tmp = *arr; //保存第一个字符
	arr[0]=arr[len-1]//拿到最后一个字符

	arr[len-1]= '\0';//在最后一个字符放\0
	if (strlen(arr + 1) >= 2)  //大于2个字符就换过来
		reverse_string(arr + 1);
	arr[len-1]= tmp;

}
int main()
{
	char arr[] = "abcdef";
	reverse_string(arr);
	printf("%s\n", arr);
	
}
//计算一个数的每位之和(递归实现)
//
//写一递归函数DigitSum(n),输入一个非负整数,返回组成它的数字之和
//例如,调用DigitSum(1729),则应该返回1 + 7 + 2 + 9,它的和是19
//输入:1729,输出:19

int DigitSum(int n)
{

	if (n > 9)
	return 	DigitSum(n / 10) + n % 10;
	else
		return n;
}

int main()
{
	int num = 1729;
	int ret=DigitSum(num); 
	printf("%d\n", ret);
}

6进制转化


//6进制转换
//递归 
//1.n==0作为结束条件
//2.不断递归/6 到底 再printf(n%6)
//#include<stdio.h>
//void Printf(int n)
//{
//	if (n == 0);
//	else
//	{
//		Printf(n / 6);
//		printf("%d", n % 6);
//	}
//}
//
//int main()
//{
//	int n = 0;
//	scanf("%d", &n);
//	Printf(n);
//	return 0;
//}

//非递归
//1.取除6的余数放到数组中
//2.逆序打印出来
//int main()
//{
//	int n,i = 0;
//	int arr[50];
//	scanf("%d", &n);
//	while (n)
//	{
//		arr[i++] = n % 6;
//		n /= 6;
//	}
//	for (i--; i >=0; i--){
//		printf("%d", arr[i]);
//	}
//}

最大公约数/最小公倍数

//最大公约数求法
//欧几里得的辗转相除法:
//大数除以小数取余数(相当于模运算),直到余数为零时
//(也即模运算为零时)的除数(也即模数)就是最大公约数,该算法时间复杂度约为O(logN)。
//
//求最小公倍数的方法:原始数据的乘积除以最大公约数
//
//两个数的乘积等于这两个数的最大公约数和最小公倍数的积。

#include <stdio.h>

int main()
{
	int n = 0;
	int m = 0;
	scanf("%d%d", &n, &m);
	//设max是最大公约数
	int max = n>m?m:n; //放的是小数
	//设min是最小公倍数
	int min = n>m?n:m; //放的是大数
	while(1)
	{
	//2个数除到小数能整除
	if(m%max==0 && n%max ==0)
	{
	break;
	}
	max--;
}
	while(1)
	{
    //用大数除到2个都能整除
	if(min%m == 0 && min%n==0)
	{
	break;
		}
	min++;
	}
	printf("%d\n", max+min);

return 0;
}


//
//int main()
//{
//	long long n = 0;
//	long long m = 0;
//	long long k = 0;
//	scanf("%lld %lld", &n, &m);
//	long long a = n;
//	long long b = m;
//  最大公约数b
//	while (k = a%b)
//	{
//		a = b;
//		b = k;
//	}
//	printf("%lld\n", b + m*n / b);
//
//	return 0;
//}

求阶乘

int factorial(int x)
{
	int ret = 1;
	if (x == 0)
		ret = 1;
	else if (x > 1)
		ret = x*factorial(x-1);
	return ret;
}
int main()
{
	int a = 6;
	int ret = 1;
	for (int i = 1; i <= a; i++)
	{
		ret = ret*i;
	}
	printf("%d", ret);
	printf("%d", factorial(6));

}

数组

  • 数组在内存是连续存放的
  • 数组通过下标来访问

数组名

数组名一般是数组首元素的地址

例外

sizeof (arr) //计算的是整个数组的大小
 &数组名 取的是数组的地址

sizeof()数组总大小

strlen()有效元素个数

int main()
{
	int  arr[10] = { 0 };
	for (int i = 0; i <=9; i++){
		printf("arr[%d]=[%p]\n", i, &arr[i]);
	}
}

#include <stdio.h>
int main()
{
  int *p = NULL;
  int arr[10] = {0};
  return 0;
}

p=&arr  是错误的 左边类型是int * 右边是 int(*)arr[10];

二维数组

	//二维数组 2是行 3是列
	int arr[2][3] = { { 1, 2, 3 }, { 4, 5, 6 } };
	int arr1[][3] = { {1,2,3} }; //行可以省略,列不能省略
	//*(aa + 1)) 第二行去了 aa

	//二维数组存储也是连续的
	int i = 0;
	for (i = 0; i < 2; i++){
		int j = 0;
		for (j = 0; j < 3; j++){
			printf("arr[%d][%d]=[%p] ", i, j, &arr[i][j]);
		}
		printf("\n");
	}
	//二维数组行列转化
	int main()
{
	int a[3][2] = { { 2, 4 }, { 5, 6 }, { 7, 8 } };
	int b[2][3] = {0};
	for (int i = 0; i < 3; i++)
	{
		for (int j = 0; j < 2; j++) //这2个for循环用原来a的
		{
			b[j][i] = a[i][j];//行列中数字替换
		}
	}
}

冒泡排序

void BubbleSort(int arr[], int sz)
{
	//总共要排的趟数
	for (int i = 0; i < sz-1; i++){
		//每一趟的排序 排升序 排一趟要排的数减少一个
		for (int j = 0; j < sz - 1 - i; j++){
			if (arr[j]>arr[j+1]){
				int tmp = arr[j];
				arr[j] = arr[j+1];
				arr[j+1] = tmp;
			}
			
		}
	}
}

//优化
//void BubbleSort(int arr[], int sz)
//{
//	总共要排的趟数
//	for (int i = 0; i < sz-1; i++){
//		每一趟的排序 排升序 排一趟要排的数减少一个
//		int flag = 1; //默认进来就是有有序的
//		for (int j = 0; j < sz - 1 - i; j++){
//			if (arr[j]>arr[j+1]){
//				int tmp = arr[j];
//				arr[j] = arr[j+1];
//				arr[j+1] = tmp;
//				flag = 0;
//			}
//			
//		}
//		if (flag == 1)
//			break;
//	}
//}

指针

指针是内存最小单元的编号就是地址

平时的指针是指指针变量,用来存放地址的变量

特点

  • 指针是用来存放地址的,地址是唯一标识一块地址空间的

  • 指针的大小32位平台是4个字节,64位平台是8个字节

    32位机器上32根地址线,地址是32个0/1组成的地址序列所以(2^32/1024Kb/1024Mb/1024Gb==4Gb)空闲进行编址

指针类型的意义

  1. 指针类型决定了指针向前或向后走一步的距离是多大
  2. 指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。
int main()
{
	int a = 3;
	int *int_p = &a; //这里将a(4个字节)第一个字节放入p中,p就是一个指针变量
	char *char_p = (char*)&a;
	printf("%p\n", &a);
	printf("%p\n", int_p);
	printf("%p\n", int_p+1);
	printf("%p\n", char_p);
	printf("%p\n", char_p+1);
    //两个指针相减,指针必须指向一段连续空间
    //减完之后的结构代表两个指针之间相差元素的个数
	return 0;
}

野指针

指向的位置是不可知的(随机的,不正确的,没有明确限制的)

造成野指针的原因

	int *p;
	p = 20; //1.没有初始化指针,默认为随机值

	int arr[5] = {0};
	int *p = arr;
	for (int i = 0; i < 6; i++){
		p++; //2.指针越界
	}

	//3.指针所指向的内存给释放掉了

避免野指针

  1. 指针使用前检查有效性并初始化
  2. 小心越界访问
  3. 指针指向的内存释放前把指针置空
  4. 避免返回局部变量的地址

二级指针

	int a = 10;
	int *pi = a;
	int **ppi = &pi;
	printf("%p\n", &ppi);
	printf("%p\n", &pi);
	printf("%d\n", pi);
	printf("%d\n", *ppi);	

  1. 二级指针能接受指针数组传参

指针数组

	int *arr[5] = { 0 }; //int *[5] 类型

数组指针

int *arr[5];//[]优先级高于* 这里是指针数组
int (*arr)[5]; //arr是一个指针变量,指向的是一个大小为5的整形数组
int(*parr3[5])[4] //表示存着4个分别指向数组空间大小为5的指针数组

函数指针数组

(*(void (*)())0)(); //把0强制转为void (*)(),就是在0处放了一个void (*)()的地址,*解引用找到这个函数
//后面()就是传参


int arr[10] //这个数组的类型是 int [10] 去掉名字就是类型
int (*p)(int)(int)=&add;  //函数指针
//函数指针 typedef int (*fun_ptr)(int,int); 
// 声明一个指向同样参数、返回值的函数指针类型

int (*arr[5])();//函数指针数组

int add(int x,int y)
{	
    return x+y;
}
int sub(int x,int y)
{	
    return x-y;
}
int (*p1)(int ,int )=add;
int (*p2)(int ,int )=sub;
//转移表
int (*p3[2])(int,int)={add,sub};//函数指针数组
int ret=p3[0](1,2) //3



计算器改进版

int add(int x,int y)
{
    return x+y;
}
int sub(int x,int y)
{
    return x-y;
}

void cal(int(*p)(int ,int))
{
    int x;
    int y;
    int ret;
    printf("请输入!\n");
    scanf("%d%d",&x,&y);
    ret=p(x,y);
}
int main()
{
    int input=0;
    scanf("%d",&input);
    switch(input)
    {
        case 1:
            cal(add);
            break;
        case 2:
            cal(sub);
            break;
    }
}

字符指针

	char str1[] = "hello";
	char str2[] = "hello";
	const char *str3 = "hello";
	const char *str4 = "hello";
	if (str1 == str2)
		printf("str1 and str2 are same\n");
	else
		printf("str1 and str2 are not same\n");//not same
	if (str3 == str4)
		printf("str3 and str4 are same\n"); //same
	else
		printf("str3 and str4 are not same\n");
这里str3和str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当
几个指针。指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化
不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4不同。

指向函数指针数组的指针

void test()
{
	printf("test\n");
}
int main()
{
	void(*p)() = test;//函数指针
	void(*parr[5])();//函数指针数组
	parr[0] = test;

	void(*(*pparr)[5])()=&parr;//指向函数指针数组指针
    // *pparr  是一个指针指向[5]数组	void(*)()类型
	return 0;
}

回调函数

回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个
函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数
的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进
行响应。

sqort

void qsort(void *base, size_t num,
	size_t width, int(__cdecl *compare)(const void *elem1, const void *elem2));


int int_compare(const void*e1, const void*e2)
{
	return *(int*)e1 - *(int *)e2; //>0 e1>e2 先强制转化为int*
}

void print(int arr[], int sz)
{
	for (int i = 0; i < sz; i++)
	{
		printf("%d ", arr[i]);
	}
}

void test1()
{
	int arr[] = { 9, 8, 7, 6, 5, 4, 3, 2, 1 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	qsort(arr, sz, sizeof(arr[0]), int_compare);
	print(arr, sz);
}

struct STU
{
	char name[10];
	int age;
};
//void *可以传递任何类型的参数,但不能运算
void stu_name(const void*e1, const void*e2)
{
	return strcmp(((struct STU*)e1)->name,((struct STU*)e2)->name);
}

void stu_age(const void *e1, const void *e2)
{
	return ((struct STU*)e1)->age - ((struct STU*)e2)->age;
}
void test2()
{
	struct STU people[3] = { {"xiaoming",30}, {"xiaozhang",15}, {"dagou",35} };
	int sz = sizeof(people) / sizeof(people[0]);
	qsort(people, sz, sizeof(people[0]), stu_age);
}


int main()
{
	//test1();
	test2();
	return 0;
}

用回调函数实现通用冒泡排序

//比较函数
int int_compare(const void*e1, const void*e2)
{
	return *(int*)e1 - *(int *)e2; //>0 e1>e2 先强制转化为int*
}

//自定义一个bubbleSort 模拟实现sqort
//void qsort(void *base, size_t num,
//size_t width, int(__cdecl *compare)(const void *elem1, const void *elem2));
void Swap(char*num1, char*num2,int width)
{
	int i = 0;
	//交换宽度次
	//09 00 00 00 和 08 00 00 00  一个一个字节交换
	for (i = 0; i < width; i++)
	{
		char *tmp = *num1;
		*num1 =* num2;
		*num2 = tmp;
		num1++;
		num2++;
	}
}

void bubbleSort(void *base, size_t num, size_t width, int(*compare)(const void*, const void *))
{
	size_t i = 0;
	for (i = 0; i < num; i++)
	{
		for (size_t j = 0; j < num - i - 1; j++)
		{
			//以1字节的char+width 就可以指向数组的元素
			if (int_compare((char*)base + j*width, (char*)base + (j + 1)*width)>0)
			{
				Swap((char*)base + j*width, (char*)base + (j + 1)*width, width);
			}
		}
	}
}
void test3()
{
	int arr[] = { 9, 8, 7, 6, 5, 4, 3, 2, 1 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	bubbleSort(arr, sz, sizeof(arr[0]), int_compare);
	print(arr, sz);
}

int main()
{
	//test1();
	//test2();
	test3();
	return 0;
}

指针相关的练习

数组名

  • sizeof(arr)计算的是整个数组的大小 ,sizeof(arr)要单独才表示整个数组大小
  • &arr 取的是整个数组的地址 int (*)[ ] 运算也是整个数组运算
  • 除此之外数组名都表示首元素地址
int main()
{
    int a[] = { 1,2,3,4 };
	printf("%d\n", sizeof(a));//数组名a单独放在sizeof内部,数组名表示整个数组,计算的是整个数组的大小
	printf("%d\n", sizeof(a + 0));//a表示首元素的地址,a+0还是首元素的地址,地址的大小是4/8字节
	printf("%d\n", sizeof(*a));   //a表示首元素的地址,*a 就是首元素 ==> a[0] ,大小就是4
	//*a <==> *(a+0) <==> a[0]
	printf("%d\n", sizeof(a + 1));//a表示首元素的地址,a+1是第二个元素的地址,大小就是4/8
	printf("%d\n", sizeof(a[1])); //a[1] 就是第二个元素 - 4
	printf("%d\n", sizeof(&a));   //&a - 数组的地址 - 4/8 - int(*)[4]
	printf("%d\n", sizeof(*&a));  //*&a - &a是数组的地址,对数组的地址解引用拿到的是数组,
    //                              所以大小时候16
	//printf("%d\n", sizeof(a));//16
	printf("%d\n", sizeof(&a + 1));//4/8 &a是数组的地址,&a+1 是数组的地址+1,
    								//跳过整个数组,虽然跳过了数组,
	//还是地址  4/8
	printf("%d\n", sizeof(&a[0]));//4/8
	printf("%d\n", sizeof(&a[0] + 1));//第二个元素的地址 4/8
    
    
    char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", strlen(arr));//随机值
	printf("%d\n", strlen(arr + 0));//随机值
	//printf("%d\n", strlen(*arr));//*arr - 'a' - 97 - err
	//strlen就以为传进来的'a'的ascii码值97就是地址
	//printf("%d\n", strlen(arr[1]));//'b' - 98 - err
	printf("%d\n", strlen(&arr));//随机值
	printf("%d\n", strlen(&arr + 1));//随机值
	printf("%d\n", strlen(&arr[0] + 1));//随机值


	char arr1[] = { 'a', 'b', 'c', 'd', 'e', 'f' }; //6
	//[a b c d e f]
	char arr[] = "abcdef";//7
	//[a b c d e f \0]
	printf("%d\n", strlen(arr));//6
	printf("%d\n", strlen(arr + 0));//6
	//printf("%d\n", strlen(*arr));//err		
	//printf("%d\n", strlen(arr[1]));//err	
	printf("%d\n", strlen(&arr));//6
	printf("%d\n", strlen(&arr + 1));//随机值
	printf("%d\n", strlen(&arr[0] + 1));//5
	//printf("%d\n", sizeof(arr));//7 字节
	//printf("%d\n", sizeof(arr + 0));//arr是首元素的地址 4/8
	//printf("%d\n", sizeof(*arr));//arr是首元素的地址,*arr就是首元素 1
	//printf("%d\n", sizeof(arr[1]));//arr[1]就是第二个元素  1
	//printf("%d\n", sizeof(&arr));//&arr 是数组的地址,数组的地址也是地址,就是4/8字节
	//printf("%d\n", sizeof(&arr + 1));//&arr 是数组的地址,&arr+1 是跳过整个数组后的地址 4/8
	//printf("%d\n", sizeof(&arr[0] + 1));//&arr[0] + 1 是第二个元素的地址,4/8
    
    const char* p = "abcdef"; 
	printf("%d\n", strlen(p));//6
	printf("%d\n", strlen(p + 1));//5
	//printf("%d\n", strlen(*p));//err
	//printf("%d\n", strlen(p[0]));//err
	printf("%d\n", strlen(&p));//随机值
	printf("%d\n", strlen(&p + 1));//随机值
	printf("%d\n", strlen(&p[0] + 1));//5
	//printf("%d\n", sizeof(p));//p是一个指针变量 4/8
	//printf("%d\n", sizeof(p + 1));//p+1 是字符b的地址 4/8
	//printf("%d\n", sizeof(*p));//1
	//printf("%d\n", sizeof(p[0]));//p[0]-->*(p+0) --> *p 1
	//printf("%d\n", sizeof(&p));//4/8
	//printf("%d\n", sizeof(&p + 1));
	//printf("%d\n", sizeof(&p[0] + 1));
    
    
   
    
    
}


  • 二维数组
int a[3][4] = { 0 };
	printf("%d\n", sizeof(a));
	printf("%d\n", sizeof(a[0][0]));
	printf("%d\n", sizeof(a[0]));//a[0]是第一行的数组名,数组名单独放在sizeof内部
	printf("%d\n", sizeof(a[0] + 1));//4/8
	//arr[0]是第一行的数组的数组名,并没有单独放在sizeof内部,也没有&,所以arr[0]表示首元素的地址
	//就是第一行这个数组第一个元素的地址
	//a[0] + 1就是第一行,第二个元素的地址

	printf("%d\n", sizeof(*(a[0] + 1)));//*(a[0] + 1)就是第一行第二个元素 4
	printf("%d\n", sizeof(a + 1));//4/8
	//数组名a,并没有单独放在sizeof内部,也没有&,所以a表示首元素(第一行)的地址
	//所以a+1,就是第二行的地址
	//int(*)[4]
	//a+1 -> &a[1]

	printf("%d\n", sizeof(*(a + 1)));//*(a+1)就是第二行 - 16
	//*(a + 1) --> a[1]

	printf("%d\n", sizeof(&a[0] + 1));//4/8
	//a[0]是第一行的数组名
	//&a[0] 拿到的是第一行的地址
	//&a[0]+1,就是第二行的地址
	//int(*)[4]

	printf("%d\n", sizeof(*(&a[0] + 1)));//16
	//*(&a[0] + 1) - 第二行 -- a[1]
	//*(&a[0]+1) --> *(&a[1]) --> a[1]

	printf("%d\n", sizeof(*a));
	//a表示首元素(第一行)的地址
	//*a - 第一行 - 第一行的数组名
	//*a -> *(a+0) ->a[0]

	printf("%d\n", sizeof(a[3]));
	//a[3]假设存在,就是第四行的数组名,sizeof(a[3]),就相当于把第四行的数组名单独放在sizeof内部
struct Test
{
	int Num;
	char *pcName;
	short sDate;
	char cha[2];
	short sBa[4];
}*p;
//已知,结构体Test类型的变量大小是20个字节
int main()
{
	p = (struct Test*)0x100000;
	printf("%p\n", p + 0x1);//00100014  1*16^1+0*16^0=20 p+0x1相当于加上一个结构体的大小
	printf("%p\n", (unsigned long)p + 0x1);//00100001 
	printf("%p\n", (unsigned int*)p + 0x1);//00100004 整形指针+1 跳过4
	return 0;
}
int main()
{
	int a[4] = { 1, 2, 3, 4 };
	int *ptr1 = (int *)(&a + 1);
	int *ptr2 = (int *)((int)a + 1);  
	printf("%x,%x", ptr1[-1], *ptr2); 
	return 0;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-X4Z0CNgU-1674720201669)(C:\Users\75935\Desktop\csdn照片\指针练习1.png)]

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;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6hOCcjC5-1674720201670)(C:\Users\75935\Desktop\csdn照片\指针-指针.png)]

int main()
{
char *c[] = {"ENTER","NEW","POINT","FIRST"};
char**cp[] = {c+3,c+2,c+1,c};
char***cpp = cp;
printf("%s\n", **++cpp);
printf("%s\n", *--*++cpp+3);
printf("%s\n", *cpp[-2]+3);
printf("%s\n", cpp[-1][-1]+1);
return 0;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9mTFYzHW-1674720201670)(C:\Users\75935\Desktop\csdn照片\字符指针.png)]

结构体

结构是一些值的集合,这些值叫做成员变量,每个成员变量可以是不同类型

结构体在传参的时候传地址

  • 函数传参的时候,参数是需要压栈的,传一个结构体对象时,参数压栈开销比较大,会导致性能下降
#include<string.h>
struct Student{
	char name[10];
	int age;
	char ID[5];
}two; //声明类型并且定义结构体变量two


struct Node{
	struct Node* next;
	struct Student Stu;
};

void Print(struct Student *p)
{
	printf("%s\n", p->name);//结构体指针通过->访问结构体成员
}

void Print1(struct Student *p1)
{
	printf("%s\n", p1->name);
}

void print2(struct Student p2)
{
	printf("%s\n", p2.name); 
}

int main()
{
	struct Student one={ "xiaoming",18,00001 };//定义结构变量并初始化
	struct Node node1 = { NULL, "xiaoming", 18, 00001 };//结构体可以嵌套初始化
	struct Student three;
	three.age = 20; //通过.结构体变量访问结构体成员
	strcpy(three.name, "xiaoming");
	Print(&three);

	//结构体传参
	Print1(&one);
	print2(one);

	return 0;
}

结构体定义

//结构体
struct book
{
	char name[20];
	char author[30];
	int pirce;
}book1,book2;//全局

struct book book3;//全局

int main()
{
	struct book book4;//局部
}




//匿名结构体 只能使用一次,这里只能定义s1,s2
struct
{
	char name[30];
	int age;
}s1, s2;


struct 
{
	int a;
	int b;
	char c;
}s1;

struct
{
	int a;
	int b;
	char c;

}*sp;

int main()
{
	//ps = &s1;//是错误的 匿名结构体虽然里面的变量是一样的,编译器认为是2个结构体
}




struct Node
{
	int date;
	//struct Node n;  error 会死循环 每个n里面含有一个date又一个n,n里面又有...
};

struct Node
{
	int date;
	struct Node* node;
};


//error  先使用才定义
typedef struct
{
	int date;
	Node* node; 
}Node;

//正确的
typedef struct Node
{
	int date;
	Node *node;
}Node;


结构体内存对齐

规则

  1. 第一个成员在与结构体变量偏移量为0的地址处。

  2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
    对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
    VS中默认的值为8

  3. 结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。

  4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整
    体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

struct S1
{
	char c1;
	int i;
	char c2;
};

struct  S2
{
	char c1;
	char c2;
	int i;
};

struct S3
{
	double d;
	char c;
	int i;
};

struct S4
{
	char  c1;
	struct S3 s3;
	double d;
};
int main()
{
	printf("%d\n", sizeof(struct S1));//12
	printf("%d\n", sizeof(struct S2));//8
	printf("%d\n", sizeof(struct S3));//16
	printf("%d\n", sizeof(struct S4));//32
	return 0;
}

为什么会存在结构体对齐?

结构体的内存对齐是拿空间来换取时间的做法

  • 平台原因(移植原因):
    不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特
    定类型的数据,否则抛出硬件异常
  • 性能原因:
    数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问

默认对齐数修改


//#pragma pack(4)//设置默认对齐数为4
//struct S1
//{
//	char c1;
//	int i;
//	char c2;
//};
//
//#pragma pack() //取消设置默认对齐数,还原为默认
//
//int main()
//{
//	printf("%d\n", sizeof(struct S1));
//	return 0;
//}


offsetof 计算结构体某变量相对于首地址的偏移


#include<stddef.h>
//offsetof 计算结构体某变量相对于首地址的偏移
struct S1
{
	char c1;
	int i;
	char c2;
};

int main()
{
	printf("%d\n", offsetof(struct S1,c1)); //0
	printf("%d\n", offsetof(struct S1, i)); //4
	printf("%d\n", offsetof(struct S1, c2)); //8
	return 0;
}

位段

  1. 位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型
  2. 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的
  3. 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段
struct test

{
	int _a : 2; //占用2个比特位
	int _b : 5;
	int _c : 10;
	int _d : 30;  //共47  6bytes
};


int main()
{
	printf("%d\n", sizeof(struct test)); //8bytes
	return 0;
}

位段理解

struct S
{
	char a : 3;
	char b: 4;
	char c :5;
	char d : 4;
};

struct S s = { 0 };

int main()
{
	s.a = 10; //01010
	s.b = 12; //01100
	s.c = 3; //0011
	s.d = 4; //00100
	return 0;
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-973Negtm-1674720201671)(C:\Users\75935\Desktop\csdn照片\位段理解.png)]

位段跨平台问题

  1. int 位段被当成有符号数还是无符号数是不确定的。
  2. 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机
    器会出问题。
  3. 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
  4. 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是
    舍弃剩余的位还是利用,这是不确定的

枚举

枚举特点

  1. 增加代码的可读性和可维护性
  2. 和#define定义的标识符比较枚举有类型检查,更加严谨。
  3. 防止了命名污染(封装)
  4. 便于调试
  5. 使用方便,一次可以定义多个常量
//枚举
enum day
{
	mon, //0 枚举常量
	tues,//1
	wed,
	thur,
	fir,
	sat,
	sun,
};

enum Color
{
	RED = 1,
	GREEN = 2,
	BLUE = 5,
};

//枚举应用
enum Option
{
	EXIT,//0
	ADD,//1
	SUB,//2
	MUL,//3
	DIV//4
};

void menu()
{
	printf("******************************\n");
	printf("**** 1. add     2. sub    ****\n");
	printf("**** 3. mul     4. div    ****\n");
	printf("**** 0. exit              ****\n");
	printf("******************************\n");
}


int main()
{
	int day1 = mon;
	printf("%d\n", day1);
	enum Color clr = GREEN; //只能拿枚举常量给枚举变量赋值,才不会出现类型的差异。
	int input = 0;
	do
	{
		menu();
		printf("请选择:>");
		scanf("%d", &input);
		switch (input)
		{
		case ADD:
			//加法
			break;
		case SUB:

			break;
		case MUL:

			break;
		case DIV:

			break;
		case EXIT:

			break;
		}
	} while ();
}


联合(共用体)


union Un
{
	char c;
	int i;
};

//联合的成员是共用同一块内存空间的,这样一个联合变量的大小,
//至少是最大成员的大小。
int main()
{
	union Un un;
	printf("%d\n", sizeof(un)); //4
	printf("%p\n", &(un.c));//0074FC78
	printf("%p\n", &(un.i));//0074FC78
	return 0;
}

大小端

int check_sys()
{
	union U
	{
		char c;
		int i;
	}u;
	u.i = 1;
	//返回1 - 小端
	//返回0 - 大端
	return u.c;
}

int main()
{
	int a = 1;//0x 00 00 00 01
	//低---------------------> 高
	//01 00 00 00 - 小端存储
	//00 00 00 01 - 大端存储
	//
	//char* pc = (char*)&a;
	//if (*pc == 1)
	//{
	//	printf("小端\n");
	//}
	//else
	//{
	//	printf("大端\n");
	//}

	/*union U
	{
		char c;
		int i;
	}u;
	u.i = 1;
	if (u.c == 1)
	{
		printf("小端\n");
	}
	else
	{ 
		printf("大端\n");
	}*/
	if (check_sys() == 1)
	{
		printf("小端\n");
	}
	else
	{
		printf("大端\n");
	}
	return 0;
}

联合体大小计算

  • 联合的大小至少是最大成员的大小。
  • 当最大成员大小不是最大对齐数的整数倍的时候,就要对齐到最大对齐数的整数倍
union Un1
{
char c[5];
int i;
};
union Un2
{
short c[7];
int i;
};

printf("%d\n", sizeof(union Un1));//8
printf("%d\n", sizeof(union Un2));//16

栈帧

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IHBHbV51-1674720201671)(C:\Users\75935\Desktop\csdn照片\栈帧1.png)]

调试

  • Debug 通常称为调试版本,它包含调试信息,并且不作任何优化,便于程序员调试程序。

  • Release 称为发布版本,它往往是进行了各种优化,使得程序在代码大小和运行速度上都是最优

    的,以便用户很好地使用

数组越界死循环

Debug,release下把i放到arr前面不会出现这个问题

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FtxpngHk-1674720201672)(C:\Users\75935\Desktop\csdn照片\调试死循环问题.png)]

解释

局部变量i和数组arr都是存储在栈区中的,栈区有个特点先使用高地址的空间再使用低地址的空间,而数组的存储是由低地址到高地址往下连续存续的,当越界访问arr[12]时的地址刚好与i的地址重合的,把i的值也改为0,所以程序死循环了(数组的越界需要停下来才能检查出来,这里死循环进行)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nmeVlOLr-1674720201672)(C:\Users\75935\Desktop\csdn照片\死循环解释.png)]

OJ题1

字符串各种输入

while(gets(str))
while (scanf_s("%d%d", &y, &m) != EOF)    
 

喝汽水

1. 20元首先可以喝20瓶,此时手中有20个空瓶子
2. 两个空瓶子可以喝一瓶,喝完之后,空瓶子剩余
   empty/2(两个空瓶子换的喝完后产生的瓶子) + empty%2(不够换的瓶子)
3. 如果瓶子个数超过1个,可以继续换,即重复2
*/
int main()
{
	int money = 0;
	int total = 0;
	int empty = 0;
	scanf("%d", &money);
	//方法1
	total = money;
	empty = money;
	while(empty>1)
	{
		total += empty/2;
		empty = empty/2+empty%2;
	}
 	//方法2
    if(total<=0)
    {
        return toatal;
    }
 	else{
        return money*2-1;
    }
	return 0;
}
 
 

杨辉三角


int main()
{
	int arr[10][10] = { 0 };
	int i = 0;
	for (i = 0; i < 10; i++)
	{
		int j = 0;
		for (j = 0; j <= i; j++)
		{
			if (j == 0)
				arr[i][j] = 1;
			if (i == j)
				arr[i][j] = 1;
			if (i >= 2 && j >= 1)
				arr[i][j] = arr[i - 1][j - 1] + arr[i - 1][j]; 

		}
	}
	for (i = 0; i < 10; i++)
	{
		int j = 0;
		for (j = 0; j <= i; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

奇数偶数互换

#include<stdlib.h>
//调整数组使奇数全部都位于偶数前面
void Depart_Arry(int arr[], int size)
{
	int left = 0;
	int right = size - 1;
	while (left < right)
	{
		//从前到后找偶数
		while (left < right && arr[left] % 2 != 0)
		{
			left++;
		}
		//从后到前找奇数
		while (left < right && arr[right] % 2 == 0)
		{
			right--;
		}
		//交换
		if (left < right)
		{
			int tmp = arr[left];
			arr[left] = arr[right];
			arr[right] = tmp;
		}
	}
}


//打印数组
void Print(int arr[], int size)
{
	for (int i = 0; i < size; i++)
	{
		printf("%d ", arr[i]);
	}
}


int main()
{


	int arr[] = { 2, 3, 4, 6, 5, 8, 7, 10, 9, 1 };
	int size = sizeof(arr) / sizeof(arr[0]);
	Depart_Arry(arr, size);
	Print(arr, size);	
	return 0;
}

杨氏矩阵

int find(int a[][4], int x, int y, int num)
{
	int i = 0; int j =y - 1; //最右边开始找
	while (j >= 0 && i<x)
	{
		if (a[i][j] < num)//大向下找
			i++;
		else if (a[i][j] < num) //小向左找
			j--;
		else
			return 1;
	}
	return 0;
}
int main()
{
	int a[4][4] = { { 1, 2, 3, 4 }, { 5, 6, 7, 8 }, { 9, 10, 11, 12 }, { 13, 14, 15, 16 } };
	int ret = find(a, 4, 4, 16);
	if (ret == 1){
		printf("找到啦\n");
	}
	else
		printf("找不到\n");
	return 0;
}

左旋n个字符

//字符串左旋
//实现一个函数,可以左旋字符串中的k个字符。
//例如:
//ABCD左旋一个字符得到BCDA
//ABCD左旋两个字符得到CDAB
#include<stdio.h>
char Left_hand(char* arr, int k)
{
	int right = 0;
	int i = 0;
	int len = strlen(arr);//求出字符串长度
	char ret;
	while (k)
	{
		ret = arr[0];
		for (i = 0; i < len; i++)
		{
			if (3 == i)
			{
				arr[i] = ret;
			}
			if (i <= 2)
			{
				arr[i] = arr[i + 1];
			}
		}
		k--;
	}
}
//4.
void Left_hand(char* arr, int k)
{
	int i = 0;
	int len = strlen(arr);
	for (i = 0; i < k; i++)
	{
		//1.保留第一个字符
		char tmp = *arr;
		//2.把后序的字符依次向前移动
		int j = 0;
		for (j = 0; j < len - 1; j++)
		{
			*(arr+j) = *(arr + j+1);
		}
		//3.把保存的第一个字符放在最后
		*(arr+len-1)= tmp;
	}
}

//2.
void leftRound(char * src, int time)
{
	int len = strlen(src);
	int pos = time % len; //断开位置的下标
	char tmp[256] = { 0 }; //更准确的话可以选择malloc len + 1个字节的空间来做这个tmp
	
	strcpy(tmp, src + pos); //先将后面的全部拷过来
	strncat(tmp, src, pos); //然后将前面几个接上
	strcpy(src, tmp); //最后拷回去
}

//3.
void reverse_part(char *str, int start, int end) //将字符串从start到end这一段逆序
{
	int i, j;
	char tmp;
 
	for (i = start, j = end; i < j; i++, j--)
	{
		tmp = str[i];
		str[i] = str[j];
		str[j] = tmp;
	}
}
 
void leftRound(char * src, int time)
{
	int len = strlen(src);
	int pos = time % len;
	reverse_part(src, 0, pos - 1); //逆序前段
	reverse_part(src, pos, len - 1); //逆序后段
	reverse_part(src, 0, len - 1); //整体逆序
}

int main()
{
	char arr[] = "ABCD";
	int k = 0;
	scanf_s("%d", &k);
	Left_hand(arr, k);
	printf("%s\n", arr);
	return 0;
}

一个字符串是否为另外一个字符串旋转之后的字符串

void reverse(char* l, char* r)
{
	assert(l && r);

	while (l < r)
	{
		char tmp = *l;
		*l = *r;
		*r = tmp;

		l++;
		r--;
	}
}

void left_move(char* arr, int k)
{
	assert(arr);
	int len = strlen(arr);
	k %= len;

	reverse(arr, arr+k-1);//逆序左边
	reverse(arr+k, arr+len-1);//逆序右边
	reverse(arr, arr+len-1);//逆序整个字符串
}

//1.
int is_left_move(char arr1[], char arr2[])
{
	int len = strlen(arr1);
	int i = 0;
	for (i = 0; i < len; i++)
	{
		leftRound(arr1, 1);
		if (strcmp(arr1, arr2) == 0)
			return 1;//是
	}

	return 0;//不是
}

//2.
int findRound(const char * src, char * find)
{
	char tmp[256] = { 0 }; //用一个辅助空间将原字符串做成两倍原字符串(里面包含所有旋转情况)
	strcpy(tmp, src); //先拷贝一遍
	strcat(tmp, src); //再连接一遍
	return strstr(tmp, find) != NULL; //看看找不找得到
}

int main()
{
	char arr1[] = "AABCD";
	char arr2[] = "BCDAA";
	int ret = is_left_move(arr1, arr2);
	if (ret == 1){
		printf("相等!\n");
	}
	else
		printf("不相等!\n");

}

OJ题2

有序判断

#include <stdio.h>
//输入:
//5
//1 6 9 22 30
//输出:sorted
int main()
{
    int n = 0;
    int arr[50] = {0};
    scanf("%d", &n);
    int i = 0;
    int flag1 = 0;
    int flag2 = 0;
    for(i=0; i<n; i++)
    {
        scanf("%d", &arr[i]);
        if(i>0)
        {
            if(arr[i]>arr[i-1])
                flag1 = 1;
            else if(arr[i]<arr[i-1])
                flag2 = 1;
        }
    }
    //flag1 和 flag2 都为1是乱序的
    if(flag1+flag2 > 1)
        printf("unsorted\n");
    else
        printf("sorted\n");
    return 0;
}
 

删除指定的数字

#include <stdio.h>
//输入:6 
//1 2 3 4 5 9
//4
//1 2 3 5 9
int main()
{
    int n = 0;
    int arr[50] = {0};
    int del = 0;
    scanf("%d", &n);
    int i = 0;
    for(i=0; i<n; i++)
    {
        scanf("%d", &arr[i]);
    }
    scanf("%d", &del);//要删除的元素
    int j = 0;
    for(i=0; i<n; i++)
    {
        if(arr[i] != del)
        {
            arr[j++] = arr[i];
        }
    }
    
    for(i=0; i<j; i++)
    {
        printf("%d ", arr[i]);    
    }
    return 0;
}

有序合并

//5 6
//1 3 7 9 22
//2 8 10 17 33 44
//输出:
//1 2 3 7 8 9 10 17 22 33 44
#include <stdio.h>
int main()
{
    int n = 0;
    int m = 0;
    int arr1[100] = {0};
    int arr2[100] = {0};
    //输入
    scanf("%d %d", &n, &m);
    int i = 0;
    for(i=0; i<n; i++)
    {
        scanf("%d", &arr1[i]);
    }
    for(i=0; i<m; i++)
    {
        scanf("%d", &arr2[i]);
    }
    //处理
    int j = 0;
    i = 0;
    while(i<n && j<m)
    {
        if(arr1[i] < arr2[j])
        {
            printf("%d ", arr1[i]);
            i++;
        }
        else
        {
            printf("%d ", arr2[j]);    
            j++;
        }
    }
    if(i == n)
    {
        for(; j<m; j++)
        {
            printf("%d ", arr2[j]);
        }
    }
    else
    {
        for(; i<n; i++)
        {
            printf("%d ", arr1[i]);
        }
    }
    return 0;
}

//2.我写的
#include<stdio.h>
int main()
{
    int n,m;
    scanf("%d%d",&n,&m);
    int arr1[n];
    int arr2[m];
    int tmp[n+m+1];
    int i=0,j=0,z=0,a=0,b=0;
    for(i=0;i<n;i++)
        scanf("%d",&arr1[i]);
     for(j=0;j<m;j++)
        scanf("%d",&arr2[j]);
   
    while(a<n&&b<m)
    {
        if(arr1[a]<arr2[b])
        {
            tmp[z++]=arr1[a++];
        }
        else 
        {
            tmp[z++]=arr2[b++];
          
        }
    }
    while(a<n)
    {
      tmp[z++]=arr1[a++];
    }
    while(b<m)
    {
       tmp[z++]=arr2[b++];
    }
    for(int i=0;i<n+m;i++)
    {
        printf("%d ",tmp[i]);
    }
    return 0;
}

复习时候的错题

填空题的错误1-10

1.
    int i=3;
	int k=(i++)+(i++)+(i++);
	printf("%d %d",k,i);  k=9,i=4 3个i++同时运算
        
2.设x、y均为float型变量,则以下不合法的赋值语句是 b    
A) + +x;                      B)y = (x % 2) / 10;
C)x * = y + 8;                D)x = y = 0;
 float是可以进行++--运算的,不能进行取模运算

3.int i = 010, j = 10, k = 0x10;
printf("%d, %d, %d\n", i, j, k); //  8=1*8^1+0*8^0  1 0   16
							//从左往右计算的,左边是最高次
4.字符0的ASCii是48,大写字母比小写字母少32

5.int k=0;
while(k=0)  k=k-1;
则以下说法中正确的是 C 。
C)循环体语句一次也不执行  //while(表达式要为真的才进去)

6.x= -1;  do  { x=x * x;}    while(!x);  //此程序段 只执行一次
  //不管怎么样子,先把do执行了再去while   
7.
int  n = 0;while (n++ <= 1);
printf("%d\n", n); //n=3 把循环在n=2的时候判断完之后就把n变为3了

8.在C语言中,引用数组元素时,其数组下标的数据类型允许是:整型常量或整型表达式

9.若有二维数组a[m][n],则数组中a[i][j]之前的元素个数为 i*n+j
    //a[m][n] 默认表示有m+1行,n+1列
    
10.字符数组打印
    char a[] = "acbde";
	printf("%s", a[0]);//错误
	printf("%s", a);//对
	puts(a);//对

填空题错误


经典问题

1.斐波那契数列


//递归版本
int fibonacci(int n)
{
	if (n == 1 || n == 2)return 1;
	return fibonacci(n - 1) + fibonacci(n - 2);	
}
//非递归版本
int fibonacci1(int n)
{
	int i = 1;int a = 1;int b = 1;int last = 0;
	if (n <= 2)return 1;
	else{
		for (i = 3; i <= n; i++){
			last = a + b;a = b;b = last;}}
	return last;
}


2.素数

//判断素数
//只需要从2-根号m中判断就可以了
int main()
{	
	int i = 0,n=10;
	int end = (int)sqrt(n);
	for (int i = 2; i <= end; i++)
	{
		if (n % 2 == 0)break;
	}
	//判断
	if (i > end) printf("yes!\n");
	else printf("No!\n");
	return 0;
}
//输出1-100素数
	int i = 0;int count = 0;
	for (i = 1; i <= 100; i++)
	{	int j = 0;
		for (j = 2; j <= (int)sqrt(i); j++){
			if (i%j == 0)break}
		if (j>(int)sqrt(i){	count++;
			printf("%d ", i)}}
	printf("\ncount=%d\n", count);
	return 0;
}

3.猴子偷桃问题

//递归版本
int peach(int n, int day)
{
	int sum = 0;
	if (n == day)
		sum = 1;
	else
	sum = 2 *( peach(n + 1, day) + 1);   //(剩下的+1)*2是前一天的桃子数目
	return sum;		
}
int main(){	
	/*int res = 1int sum = 0;
	for (int i = 1; i < 5; i++){
		sum = (res + 1) * 2;res = sum;}printf("%d", sum);*/
	int ret = peach(1, 5);	printf("%d ", ret);
	return 0;
}

4.打印图像

//输出 *
//    ***
//   *****
//  *******
//   *****
//	  ***
//     *

int main()
{
	//打印上四层
	int n = 4;
	for (int i = 1; i <=n; i++)
	{
		//打印空格,是上面4行所以k<=4-i
		//第一行 4个 第二行 3个 所以第n行是n-1个空格
		for (int k=n-i;k>=1;k--)
		{
			printf(" ");
		}
		//按1,3,5,7打印*
		for (int j = 1; j <= 2 * i - 1; j++)
			printf("*");
		printf("\n");
	}
	//打印下三层
	 n = 3;
	 for (int i = 1; i <=n; i++)                 
	 {
		 //空格是逐渐增加的
		 for (int j = 0; j < i; j++)             
		 {
			 printf(" ");
		 }
		 //2*n - (2 * i-1) 不知道怎么来的
		 for (int j = 0; j <2*n - (2 * i-1); j++)
			 printf("*");
		 printf("\n");
	 }
}




//******
//*****
//****
//***
//**
//*
//int main()
//{
//	for (int i = 0; i < 6; i++)
//	{
//		for (int j =6-i; j > 0; j--)
//		{
//			printf("*");
//		}
//		printf("\n");
//	}
//}
//



//     *
//	  **
//   ***
//  ****
// *****
//******
//int main()
//{
//	int n = 6;
//	for (int i = 1; i <=n; i++)
//	{
//      //第n行有n-1个空格
//		for (int k = n - i; k >= 1; k--)
//			printf(" ");
//		for (int j =1; j<=i; j++)
//		{
//			printf("*");
//		}
//		printf("\n");
//	}
//}




5.判断单词个数

int main()
{
	char ch[100] = "I am a boy";
	int i, count = 0, flag = 0;
	for (i = 0; (ch[i]) != '\0'; i++)
	{
		if (ch[i] == ' ') flag = 0; 
		else if (flag == 0)
		{
			flag = 1;
			count++;
		}
	}
	printf("%d", count);
	return 0;
}

6.获取最大值最小值

///试编程从键盘输入10个整数并保存到数组,输出10个整数中的最大值及其下标、最小值及其下标。
int main()
{
	int arr[10];
	int i = 0;
	for (i = 0; i < 9; i++)
	{
		scanf("%d", &arr[i]); 	
	}
	int max=arr[0], min=arr[0];//默认第一个为最大/最小,不是的话就替换
	int ret_max, ret_min;
	for (i = 0; i < 9; i++)
	{
		int tmp;
		if (arr[i] > max)
		{
			tmp = arr[i];
			arr[i] = max;
			max = tmp;
			ret_max = i; //返回的下标
		}
		if (arr[i] < min)
		{
			tmp = arr[i];
			arr[i] = min;
			min = tmp;
			ret_min = i;
		}
	}
	printf("max值%d下标%dmin值%d下标%d", max, ret_max, min, ret_min);

}

i = 010, j = 10, k = 0x10;
printf(“%d, %d, %d\n”, i, j, k); // 8=1*81+0*80 1 0 16
//从左往右计算的,左边是最高次
4.字符0的ASCii是48,大写字母比小写字母少32

5.int k=0;
while(k=0) k=k-1;
则以下说法中正确的是 C 。
C)循环体语句一次也不执行 //while(表达式要为真的才进去)

6.x= -1; do { x=x * x;} while(!x); //此程序段 只执行一次
//不管怎么样子,先把do执行了再去while
7.
int n = 0;while (n++ <= 1);
printf(“%d\n”, n); //n=3 把循环在n=2的时候判断完之后就把n变为3了

8.在C语言中,引用数组元素时,其数组下标的数据类型允许是:整型常量或整型表达式

9.若有二维数组a[m][n],则数组中a[i][j]之前的元素个数为 i*n+j
//a[m][n] 默认表示有m+1行,n+1列

10.字符数组打印
char a[] = “acbde”;
printf(“%s”, a[0]);//错误
printf(“%s”, a);//对
puts(a);//对




## 填空题错误



```c

经典问题

1.斐波那契数列


//递归版本
int fibonacci(int n)
{
	if (n == 1 || n == 2)return 1;
	return fibonacci(n - 1) + fibonacci(n - 2);	
}
//非递归版本
int fibonacci1(int n)
{
	int i = 1;int a = 1;int b = 1;int last = 0;
	if (n <= 2)return 1;
	else{
		for (i = 3; i <= n; i++){
			last = a + b;a = b;b = last;}}
	return last;
}


2.素数

//判断素数
//只需要从2-根号m中判断就可以了
int main()
{	
	int i = 0,n=10;
	int end = (int)sqrt(n);
	for (int i = 2; i <= end; i++)
	{
		if (n % 2 == 0)break;
	}
	//判断
	if (i > end) printf("yes!\n");
	else printf("No!\n");
	return 0;
}
//输出1-100素数
	int i = 0;int count = 0;
	for (i = 1; i <= 100; i++)
	{	int j = 0;
		for (j = 2; j <= (int)sqrt(i); j++){
			if (i%j == 0)break}
		if (j>(int)sqrt(i){	count++;
			printf("%d ", i)}}
	printf("\ncount=%d\n", count);
	return 0;
}

3.猴子偷桃问题

//递归版本
int peach(int n, int day)
{
	int sum = 0;
	if (n == day)
		sum = 1;
	else
	sum = 2 *( peach(n + 1, day) + 1);   //(剩下的+1)*2是前一天的桃子数目
	return sum;		
}
int main(){	
	/*int res = 1int sum = 0;
	for (int i = 1; i < 5; i++){
		sum = (res + 1) * 2;res = sum;}printf("%d", sum);*/
	int ret = peach(1, 5);	printf("%d ", ret);
	return 0;
}

4.打印图像

//输出 *
//    ***
//   *****
//  *******
//   *****
//	  ***
//     *

int main()
{
	//打印上四层
	int n = 4;
	for (int i = 1; i <=n; i++)
	{
		//打印空格,是上面4行所以k<=4-i
		//第一行 4个 第二行 3个 所以第n行是n-1个空格
		for (int k=n-i;k>=1;k--)
		{
			printf(" ");
		}
		//按1,3,5,7打印*
		for (int j = 1; j <= 2 * i - 1; j++)
			printf("*");
		printf("\n");
	}
	//打印下三层
	 n = 3;
	 for (int i = 1; i <=n; i++)                 
	 {
		 //空格是逐渐增加的
		 for (int j = 0; j < i; j++)             
		 {
			 printf(" ");
		 }
		 //2*n - (2 * i-1) 不知道怎么来的
		 for (int j = 0; j <2*n - (2 * i-1); j++)
			 printf("*");
		 printf("\n");
	 }
}




//******
//*****
//****
//***
//**
//*
//int main()
//{
//	for (int i = 0; i < 6; i++)
//	{
//		for (int j =6-i; j > 0; j--)
//		{
//			printf("*");
//		}
//		printf("\n");
//	}
//}
//



//     *
//	  **
//   ***
//  ****
// *****
//******
//int main()
//{
//	int n = 6;
//	for (int i = 1; i <=n; i++)
//	{
//      //第n行有n-1个空格
//		for (int k = n - i; k >= 1; k--)
//			printf(" ");
//		for (int j =1; j<=i; j++)
//		{
//			printf("*");
//		}
//		printf("\n");
//	}
//}




5.判断单词个数

int main()
{
	char ch[100] = "I am a boy";
	int i, count = 0, flag = 0;
	for (i = 0; (ch[i]) != '\0'; i++)
	{
		if (ch[i] == ' ') flag = 0; 
		else if (flag == 0)
		{
			flag = 1;
			count++;
		}
	}
	printf("%d", count);
	return 0;
}

6.获取最大值最小值

///试编程从键盘输入10个整数并保存到数组,输出10个整数中的最大值及其下标、最小值及其下标。
int main()
{
	int arr[10];
	int i = 0;
	for (i = 0; i < 9; i++)
	{
		scanf("%d", &arr[i]); 	
	}
	int max=arr[0], min=arr[0];//默认第一个为最大/最小,不是的话就替换
	int ret_max, ret_min;
	for (i = 0; i < 9; i++)
	{
		int tmp;
		if (arr[i] > max)
		{
			tmp = arr[i];
			arr[i] = max;
			max = tmp;
			ret_max = i; //返回的下标
		}
		if (arr[i] < min)
		{
			tmp = arr[i];
			arr[i] = min;
			min = tmp;
			ret_min = i;
		}
	}
	printf("max值%d下标%dmin值%d下标%d", max, ret_max, min, ret_min);

}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值