数据类型
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的取出
-
E不为全0或者全1
8位-127,11位-1023就可以得出真实的值,再将有效数字M前加上第一位的1
-
E为全0
浮点数的指数E等于1-127(或者1-1023)即为真实值,
有效数字M不再加上第一位的1,而是还原为0.xxxxxx的小数。这样做是为了表示±0,以及接近于
0的很小的数字 -
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);
}
常量
- 整形常量:10进制,八进制(017 -0开头的就是8进制),16进制那些数
- 实型常量:浮点型常量float,double,e与E,表示以10为底数的幂数,且e与E后面必须跟整数,若是小数,也是错误的
- 字符型、字符串常量:如 ‘ a’,“ab” 必须加这个符号
- 转义字符常量:’ \0 ’
- 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 )
-
sep参数是个字符串,定义了用作分隔符的字符集合
-
第一个参数指定一个字符串,它包含了0个或者多个由sep字符串中一个或者多个分隔符分割的标
记。
-
strtok函数找到str中的下一个标记,并将其用 \0 结尾,返回一个指向这个标记的指针。(注:
strtok函数会改变被操作的字符串,所以在使用strtok函数切分的字符串一般都是临时拷贝的内容
并且可修改。)
-
strtok函数的第一个参数不为 NULL ,函数将找到str中第一个标记,strtok函数将保存它在字符串
中的位置。
-
strtok函数的第一个参数为 NULL ,函数将在同一个字符串中被保存的位置开始,查找下一个标记。
-
如果字符串中不存在更多的标记,则返回 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修饰指针变量的时候
- cosnt 放在*号的左边const int *** 修饰的指针指向的内容,保证指针所指向的内容不会被修改但**指针本身可以修改
- 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本身的值没有改变
-
右移
- 算术右移:左边的值用符号位补,右丢弃
- 逻辑右移:左补零,右丢弃
位操作符
& 按位与 :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 3:
printf(" 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)空闲进行编址
指针类型的意义
- 指针类型决定了指针向前或向后走一步的距离是多大
- 指针的类型决定了,对指针解引用的时候有多大的权限(能操作几个字节)。
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.指针所指向的内存给释放掉了
避免野指针
- 指针使用前检查有效性并初始化
- 小心越界访问
- 指针指向的内存释放前把指针置空
- 避免返回局部变量的地址
二级指针
int a = 10;
int *pi = a;
int **ppi = π
printf("%p\n", &ppi);
printf("%p\n", &pi);
printf("%d\n", pi);
printf("%d\n", *ppi);
- 二级指针能接受指针数组传参
指针数组
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;
结构体内存对齐
规则
-
第一个成员在与结构体变量偏移量为0的地址处。
-
其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
VS中默认的值为8 -
结构体总大小为最大对齐数(每个成员变量都有一个对齐数)的整数倍。
-
如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整
体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。
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;
}
位段
- 位段的成员可以是 int unsigned int signed int 或者是 char (属于整形家族)类型
- 位段的空间上是按照需要以4个字节( int )或者1个字节( char )的方式来开辟的
- 位段涉及很多不确定因素,位段是不跨平台的,注重可移植的程序应该避免使用位段
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)]
位段跨平台问题
- int 位段被当成有符号数还是无符号数是不确定的。
- 位段中最大位的数目不能确定。(16位机器最大16,32位机器最大32,写成27,在16位机
器会出问题。- 位段中的成员在内存中从左向右分配,还是从右向左分配标准尚未定义。
- 当一个结构包含两个位段,第二个位段成员比较大,无法容纳于第一个位段剩余的位时,是
舍弃剩余的位还是利用,这是不确定的
枚举
枚举特点
- 增加代码的可读性和可维护性
- 和#define定义的标识符比较枚举有类型检查,更加严谨。
- 防止了命名污染(封装)
- 便于调试
- 使用方便,一次可以定义多个常量
//枚举
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);
}