直接 ' 某个符号 ' ,代表其ASCII码值
#include
//小写字母判断闭区间['a','z']
int main()
{
char c;
scanf("%c", &c);
if (c >= 'a' && c <= 'z')//'a'和'z'可以表示其acci
{
printf("yes\n");
}
else
{
printf("no\n");
}
if (9 > '0')
{
printf("l");
}
else
{
printf("p");
}
return 0;
}
#include
int main()
{
int ret = 2 + '0';
printf("%d", ret);
//结果为50
return 0;
}
字符在内存中的存储形式?
字符型数据在内存中的存储形式是其ASCII码。字符型数据是将一个字符常量放到一个字符变量中,并不是把该字符本身放到内存单元中去,而是将该字符的相应的ASCII代码放到存储单元中。
判断字符类型 ?
#include
int main()
{
char n;
scanf("%c", &n);
if (n >= 'A' && n <= 'Z')
{
printf("Upper");
}
else if (n >= 'a' && n <= 'z')
{
printf("Lower");
}
else if (n >= '0' && n <= '9')
{
printf("Digit");
}
else
{
printf("Other");
}
}
1.解释char的取值范围为什么在 -128~127
如果放入char a = 128,会发生截断,是打不出128的
2.
3.
*内存中存的是补码
*运算是用补码运算
*输出是用原码输出
4.
这里写错了,应该是 i >= 0
5.
6.
个人理解
以int为例
unsigned范围为0~4294967295
一共4294967295个
以char为例
unsigned范围为0~255
一共256个
无符号char,负数会转化为非负数。
死循环
7.
char类型在内存中存储举例
-1是负数,都以补码形式储存,整数存到char里,又因为char类型只存放八个bit位,于是截断前八个bit位,再存放到a,b和c里。又因为printf有%d,所以a和b被整形提升,高位补充符号位。
而c是无符号整型提升(例如%u和unsigned int),高位补0。
有符号整形提升(例如%d和int),高位补符号位
*值得补充的是,未达到int类型大小的(char和short)在运算时都会进行整形提升
例如:char a, b, c
a = b + c
b和c都被提升为整形,再加法运算,运算完成后,结果被截断,再存于a中。
8.为什么内存中地址中用十六进制存储?
1.因为16进制和二进制转换很容易看出来。
2.一位就是4个二进制位,与或运算一眼就能看出来。
3.十进制转换如果要置位,要运算还得转换回去,因为最根本的还是二进制的。
4.另外内存地址用16进制也算是约定俗成,如果地址也用十进制你可能分不清哪个是地址哪个是值。
9.整形在内存中存储:存补码(也就是十进制转换为二进制),存入32个比特位(int类型是32个比特位)
10.char类型在内存中存储:把数字补码(也就是十进制转换为二进制)截断,取出补码后8位(因为char类型只有8个比特位),存入8个比特位。
11.浮点数类型在内存中存储
因为E是无符号整数,但科学计数法中的E可以为负数,那要怎么实现呢?所以要存入内存时,
E真实值 = E + 中间数(这个由是哪种类型而定,例如char 8 bit位中间数,double 11 bit位中间数)
总结:存入,s = 符号位(1或0),E真实值的二进制形式,M 存入 小数点后的数(不够补0)
如何取出:
5
如何对数组进行输入(以前还以为只能输入字符串呢,今天才知道数组是用这种办法输入的,以前都没想到,也没怎么钻研,这个坏毛病以后得改改了)
整形输入:
int main()
{
int arr[10];
int i = 0;
for (i = 0; i < 10; i++)
{
scanf("%d", &arr[i]);
}
for (i = 0; i < 10; i++)
{
printf("%d ", arr[i]);
}
return 0;
}
字符串输入
int main()
{
char arr[10];
int i = 0;
scanf("%s", arr);
printf("%s", arr);
return 0;
}
注意用法,前两个用%s可以打印完整字符串
#include
#include
#include
#include
#include
#include
#include
int main()
{
int a[] = { 1,2,3,4,5 };
int b[] = { 2,3,4,5,6 };
int c[] = { 3,4,5,6,7 };
int d[] = { 4,5,6,7,8 };
int* arr[4] = { a,b,c,d };
int i, j;
for (i = 0; i < 4; i++)
{
for (j = 0; j < 5; j++)
{
printf("%d ", *(arr[i] + j));
}
printf("\n");
}
printf("\n");
for (i = 0; i < 4; i++)
{
for (j = 0; j < 5; j++)
{
printf("%d ", arr[i][j]);//效果相同
}
printf("\n");
}
return 0;
}
存放了四个int*
int main()
{
int arr[10] = { 0 };
int* p1 = arr;
int(*p2)[10] = &arr;//数组指针,指向数组的指针
printf("%p\n",p1);
printf("%p\n",p1+1);//相差4个字节
printf("%p\n",p2);
printf("%p\n",p2+1);//+1是跳过了一个数组,一个数组十个元素,40个字节,故加了40
return 0;
}
存放了四个int
数组指针的应用例子
void Print(int(*pa)[5])//*pa相当于arr
{
int i = 0;
int j = 0;
for (i = 0; i < 3; i++)
{
for (j = 0; j < 5; j++)
{
printf("%d ", *(*(pa + i) + j));
//效果一样
//printf("%d ", (*pa + i)[j]);
}
printf("\n");
}
}
int main()
{
int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
Print(arr);//arr代表首元素地址,二维数组的首元素地址是第一行
//相当于a[5],也就是1,2,3,4,5
return 0;
}
总结
可以写成arr[],也可以写成arr[10],但这个10其实没有意义,因为不会另外开辟一个空间,只是传了首元素地址罢了
3
arr与&arr的区别
注意!sizeof算出的结果是unsigned int,-1也会转化为unsigned int,变成一个超级大的正数
注意!实参和形参的类型是相同的
以下是函数指针数组的应用
#include
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int Mul(int x, int y)
{
return x * y;
}
int Div(int x, int y)
{
return x / y;
}
int main()
{
//函数指针数组的应用
int (*Cul[4])(int, int) = { Add,Sub,Mul,Div };
int input;
printf("输入0~3,分别应用加法,减法,乘法,除法\n");
scanf("%d", &input);
printf("请输入你要算的两个数\n");
int x, y;
scanf("%d%d", &x, &y);
printf("%d\n", Cul[input](x,y));
return 0;
}
如何创建一个指针,指向函数指针数组?
理解数组小技巧:
注:这个库函数可以比较任意类型
比较结构体
e1-e2是升序排序,若要降序排序就改成e2-e1
模拟实现qsort
int cmp_down(const void* e1,const void* e2)//e1,e2是地址
{
return (*(int*)e2 - *(int*)e1);
//要强制类型转换成要比较的元素的类型
}
void Print(int arr[], int sz)
{
int i;
for (i = 0; i < sz; i++)
{
printf("%d ", arr[i]);
}
}
void Swap(char*buf1,char*buf2,int width)
{
//把两个元素各自每个字节的内容都交换,从而实现两个元素的交换
int i;
for (i = 0; i < width; i++)
{
char temp = *buf1;
*buf1 = *buf2;
*buf2 = temp;
buf1++;
buf2++;
}
}
void Bubble_sort(void* base,int sz,int width,int (*cmp_down)(const void*,const void*))
{ //接收函数要用函数指针
//模拟qsort函数实现
int i = 0;
int j = 0;
for (i = 0; i < sz - 1; i++)
{
for (j = 0; j < sz - 1 - i; j++)
{
//arr[j]与arr[j+1]比较
if (cmp_down((char*)base + j * width, (char*)base + (j + 1) * width) > 0)
//两个元素比较,传元素地址
//因为元素地址是void*类型,所以要强制类型转换
//用+j*width(因为char类型每次只访问一个字节,而整型有四个字节)
//的方式来访问各个要比较的元素
//为什么不直接转换成int*?为了适应别的类型的比较,例如long,short类型
//而char每次只访问一个字节,可以实现
{
Swap((char*)base + j * width, (char*)base + (j + 1) * width, width);
}
}
}
}
int main()
{
int arr[] = { 2,3,4,2,4,3,6,65,56,76,3,65,7,67 };
int sz = sizeof(arr) / sizeof(arr[0]);
int width = sizeof(arr[0]);
Bubble_sort(arr, sz, width, cmp_down);
Print(arr, sz);
return 0;
}
无论是什么类型数据的地址,地址的大小都是4或者8
(1)16,数组名代表整个数组,算整个数组的大小
(2)4,不是sizeof(数组名)的形式,故算的是数组首元素地址的大小
(3)4,*a==1,1是整形,大小为四个字节
(4)4,首元素后面一位的元素的地址的大小
(5)4,算的是2的大小
(1)4,计算整个数组的地址的大小
(2)16,*&a==a,计算整个数组的大小
(3)4,int (*p) [4] = &a,
&a+1是跳过了一整个数组后下一块空间的地址(因为&a代表整个数组的地址)
(4) 4,计算第一个元素的地址
(5)4,计算第二个元素的地址
4,4,1,1,4,4,4
(1)6,把首元素a的地址传进去,然后strlen从a往后读
(2)5,同理
(3)随机值,传了字符a进去,往后读到 ' \0 ',产生随机值
(4)随机值
(5)err,传进去整个字符串的地址,strlen没法计算地址
(6)err,跳过整个p,传进去p后面空间的地址,strlen没法计算地址
(7)5
注意!地址里完全有可能存在 ’ \0 ',例如地址0x00120048
例如这里蓝色的0,就是'\0'
1. 48,a代表整个数组地址
2. 4,第一个元素的值
3. 16,a[0]可以理解为第一行数组名,数组名a[0]单独放在sizeof内部,是计算第一行大小
1. 4,a[0]是个数组名,没有被单独放在sizeof里,于是a[0]代表第一行·第一个的地址
2. 4,代表了一个元素
3. 4,代表第二行元素地址
1. 16,计算第二行所有元素
2. 4,第二行的地址
3. 16,第二行所有元素
4. 16,第一行所有元素
5. 16,即使越界访问,也能通过类型计算大小,因为sizeof不会计算括号里的东西
就算这题变成sizeof(arr[-1]),结果依然是16
答案为2和5
注意!(int)a表示a被强制类型转换成整形类型,每次只能访问一个字节,故(int)a+1在内存中是往后读一个字节
而被强制类型转换成int*,+1或-1在内存中就是向前或向后读4个字节
下题同理
注意!如果访问a[1][0],可看成 *(*(arr+1)+0)
分析:首先,元素地址相减的结果是二者间有几个元素
其次,p[4][2]可以被看成 *(*(p+4)+2),而*(p+4)中的+4,每个+1都是跳过四个字节,
因为指针数组p里面存放了4个int,而arr+1的+1会跳过5个字节
答案为10和5
打印at
分析:pa存的是a数组的首元素地址
选D
分析:
A:首元素地址类型为int*,刚好匹配
B:数组地址用数组指针接收,正确
C:同A
D:数组地址要用数组指针接收,错误
小总结:
数组地址要用数组指针接收
函数地址要用函数指针接收
指针地址要用更高一级指针接收
选BD
选C
分析:首先,数组地址要用数组指针接收
其次,比如要访问arr[3][5],相当于访问 *(*(arr+3)+5) ,所以数组指针的 [ ] 里装的是列
再类别一维数组地址也是用D的形式接收,因为一维数组只有列没有行,所以 [ ] 里装的肯定是列
指针部分,完结撒花!!!
strstr(地址1,地址2),判断后者是不是前者的子串,
若不是,返回空指针NULL,若是,则返回有效指针
int main()
{
char arr1[10] = "ABCDEFG";
char arr2[10] = "CDE";
char* ret = strstr(arr1, arr2);
if (ret == NULL)
{
printf("不是子串");
}
else
{
printf("是子串");
}
return 0;
}
strcat可以追加除自己以外的字符串
int main()
{
char arr1[10] = "hello ";
char arr2[10] = "bit";
strcat(arr1, arr2);
printf("%s", arr1);
return 0;
}
strncat可以追加任意字符串,且能决定追加的长度
int main()
{
char arr1[10] = "hello ";
char arr2[10] = "bit";
strncat(arr1, arr2,2);
printf("%s", arr1);
return 0;
}
strcmp用于比较两个字符串
strcmp 函数的模拟实现
int my_strcmp(char* str1, char* str2)
{
while (*str1 == *str2)
{
if (*str1 == '\0')
{
return 0;
}
str1++;
str2++;
}
return *str1 - *str2;
}
int main()
{
char arr1[10] = "ABCDEFG";
char arr2[10] = "ABCDEF";
int ret = my_strcmp(arr1, arr2);
if (ret < 0)
{
printf("arr1 < arr2");
}
else if (ret == 0)
{
printf("arr1 = arr2");
}
else
{
printf("arr1 >arr2");
}
return 0;
}
strncmp的使用
如果连续两次对同一个数组进行gets,后一次的数据会覆盖前一次的,如图
设置字体颜色
#include
void color(int x) //自定义函根据参数改变颜色
{
SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), x);
//只有一个参数,改变字体颜色
}
int main()
{
int x;
scanf("%d", &x);
color(x);
printf("666");
scanf("%d", &x);
color(x);
return 0;
}
用system设置前景(可以理解为字体)颜色和背景颜色
核心就只有一行,system("color xx"),前面一个x是背景颜色,后面一个x是字体颜色
(注意不能用逗号隔开)
设置颜色规则如下
1.getch()
(1)键盘的输入,不放在缓冲区,getch直接从键盘读取
(2)不在屏幕上显示
(3)不以回车键为标志结束输入,而是也就是读取完立刻结束输入(也就是每次只能输入一个字符)
读取方式:
直接用getch();会等待你按下任意键,再继续执行下面的语句;
用ch=getch();会等待你按下任意键之后,把该字符所对应的ASCII码赋给ch,再执行下面的语句。
结束输入的方式:getch直接从键盘获取键值,不等待用户按回车,只要用户按一个键,getch就立刻结束输入了(也就是读取完立刻结束输入)
2.getchar()
(1) 键盘的输入, 放在缓冲区,然后getchar再从缓冲区读取
(2)在屏幕上显示
(3)以回车键为标志结束输入
3.getche()
(1)键盘的输入,不放在缓冲区,getche直接从键盘读取
(2)在屏幕上显示
(3)不以回车键为标志结束输入,而是也就是读取完立刻结束输入(也就是每次只能输入一个字符)
隐藏光标
void HideCursor()
{
CONSOLE_CURSOR_INFO curInfo; //定义光标信息的结构体变量
curInfo.dwSize = 1; //如果没赋值的话,光标隐藏无效
curInfo.bVisible = FALSE; //将光标设置为不可见
HANDLE handle = GetStdHandle(STD_OUTPUT_HANDLE); //获取控制台句柄
SetConsoleCursorInfo(handle, &curInfo); //设置光标信息
}
光标跳转
void gotoxy(int x, int y) //光标移动到(x,y)位置
{
HANDLE handle;
//HANDLE handle; 这里和COORD 是一样的,HANDLE是一个一定由系统定制的结构体,直接调用就可以。
//将获得的标准输出句柄给handle。
handle = GetStdHandle(STD_OUTPUT_HANDLE);
//GetStdHandle () 这个函数也是C语言内部已经设定好的,所以这里直接调用就行。
//GetStdHandle(STD_OUTPUT_HANDLE) 这里就是一个固定的函数格式,获得标准输出函数的句柄。
COORD pos;//COORD实际上是一个C语言内部做好的结构体,
//结构体中只包含两个元素,x和y,这里的x、y就是代表着光标移动的位置
//只不过这里不需要你再去定义一个结构体,直接调用就可以。
//这个结构体就是用来记录坐标。
pos.X = x;
pos.Y = y;
SetConsoleCursorPosition(handle, pos);
//移动光标的意思,也是由C语言直接提供给你的,直接使用就可以
}
如果枚举没有进行数值初始化,系统会自动初始化,从0开始递增1
因为初始化的数值都是整形,所以可以理解为枚举类型大小是int
例如使用switch语句时可以更加直观
枚举用法实例
注意,枚举后用逗号隔开
且要对d进行初始化,否则会报错
d也是一个枚举常量,值不能进行修改
联合也是一种特殊的自定义类型这种类型定义的变量也包含一系列的成员,特征是这些成员公用同一块空间(所以联合也叫共用体)。
//联合类型的声明
union Un
{
char c;
int i;
};
//联合变量的定义
union Un un;
//计算连个变量的大小
printf("%d\n", sizeof(un));
1.联合的成员是共用同一块内存空间的,这样一个联合变量的大小,至少是最大成员的大小(因为联合至少得有能力保存最大的那个成员)。
解释如下图
可以看见联合体成员的地址都一样,这是因为联合体成员共用一块空间,放置方法如下图
绿色是char,蓝色是int
所以占字节更大的成员的放置途径会经过小成员的放置途径,故联合体大小由最大的成员来决定
2.联合体只能共用一个值,也就是修改了其中一个成员的值,其他成员的值也会随之被修改
成员里占字节最多的类型就叫最大对齐数
例如:
最大对齐数是int类型的4,而最大成员大小是a[5]的5,
联合体大小要取最大对齐数的倍数,也就是8,下一题同理