一、关于计算机用补码方式存储数据
有坑的代码如下:
int main(void)
{
char a,b;
char c;
a = -100;
b = -100;
c = a + b;
printf("c=%d", c);
return 0;
}
其结果是: c=56
先把相关知识点列一下:
原码表示法是整数的一种简单的表示法,符号位用0表示正号,用1表示负号,数值一般用二进制形式表示。
整数的反码可由原码得到,如果是正数,则反码与原码一样;如果是负数,则反码是对它的原码(符号位除外)各位取反而得到的。
整数的补码可由原码得到。如果是正数,则补码与原码一样;如果是负数,则补码是在反码基础上,末位加1而得到。
详细解释链接: https://zhuanlan.zhihu.com/p/36036038
过程: 先找到100的二进制表示,然后根据按位取反再加一,得到-100的二进制表示,然后在加起来,转成十进制,就是结果。
今天新学了一种转二进制更快的办法: 比如a,a是个正数
从左到右依次写下: 128 64 32 16 8 4 2 1,然后用a去除以每一位,能除就写1,然后保存余数,不能出就写0,原数不变;下次就用原数或者余数来除下一位,除完为止。最后从左到右就是二进制表示。
以100为例子 ()内表示余数
128 64 32 16 8 4 2 1
0 1(36)1(4)0(4)0(4)1(0)0(0)0(0) 100所以是0110 0100 所以-100 就是1001 1011 +1 =1001 1100
-100+(-100)=0011 1000
转成十进制也可以用这个方法:
0 0 1 1 1 0 0 0
128 64 32 16 8 4 2 1 先这样一位一位的写出来,有1的 就把下面数字加起来,32+16+8=56。
就这样一个很神奇的东西。
补充一个比较坑的点:
这个地方还有一点坑:
int main(void)
{
char a,b;
char c;
a = -100;
b = -100;
c = a + b;
printf("%d", a+b);
return 0;
}
其运行结果是:-200
二、指针进阶和命名法则
int main(void)
{
int a [5]={1,2,3,4,5};
int *p = (int *)(&a+1);
printf("%d %d ", *(a+1), *(p-1));
return 0;
}
其运行结果是 2 5
a与&a是不一样的,虽然两者地址相同,但意义不一样,&a是整个数组对象的首地址,而a是数组首地址,也就是a[0]的地址
解释 2: a是以int型为位移基准 a+1是第二个元素的地址,*取值后即为2
5: 在这个定义中 int *p = (int *)(&a+1); &a是以一个数组为位移基准,&a+1已经越界,是5后面的长度为5个int型的数组的第一个元素的地址(在数值上等于),然后强制转换成int *型,也就是变成按照int型为移动基准,p-1也就是移动到了5的位置, *(p-1)即为5。 注意此处强制转换
int main(void)
{
int a [5]={1,2,3,4,5};
int (*p)[5] = (&a+1);
printf("%d %d ", *(a+1), **(p-1));
return 0;
}
其运行结果是 2 1
看这个定义方式: int (*p)[5] = (&a+1);
命名法则分析: 顺时针按照token分析,p遇到(),不变,然后遇到*,*p是个指针,然后遇到[ ] ,指向一个数组,然后遇到int,数组类型是个int 型,然后遇到5,说明个数为5,综合:p是一个指向有5个整型元素的数组的指针。
2不需要解释 1 :和上面一样,&a是以一个数组为位移基准,&a+1已经越界,是5后面的长度为5个int型的数组的第一个元素的地址(在数值上等于),然后p本身就是以5个单位为操作基准的,然后p-1又向前移动回来了,所以没变化,就是首地址,取值后就是1。
为什么这个地方是**p取值呢?
int main(void)
{
int a [5]={1,2,3,4,5};
int (*p)[5]=&a;
printf("%d %d %d %d",p[0],*p[0],*p,**(p));
printf("%d %d %d %d",p[0]+1,*(p[0]+1),*(p)+1,*(*(p)+1));
return 0;
}
其运行结果为 6422264 1 6422264 1
6422268 2 6422268 2
int (*p)[5]=&a; p是一个数组指针, *p是第一个元素的地址,p[0]也是第一个元素的地址,
取到第一个元素的值也就得 **p 和 *p[0]
进行位移操作时,注意 p[1] 是(以5个整型为操作基准的下)5后面的位置区域,等价于*(p+1)
所以在一个数组内进行位移操作时,必须是p[0]+1,*(p)+1,对应取值是*(p[0]+1), *(*(p)+1))
三、typedef
A:typedef不是简单的无脑替换,而是引入了一个新的类型。
例如:
typedef char* pchar;
int main(void)
{
const pchar cstr = "aaaa";
cstr = "bbbb"; // 这一行会报错
return 0;
}
为什么会报错呢?!
如果是简单的无脑替换,那么相当于
const char* cstr = "aaaa";
cstr = "bbbb";
其实不是。
const pchar cstr; 等价于 char* const cstr;
而不是 const char* cstr
关于const一个简单的判定方式:把定义中类型都去掉,看修饰什么
typedef是引入的新类型,pchar和 char*是一个等级的、等同的独立的类型,判定时候作为一个类型去掉,则剩下就是const str,即str本身不可变。
B:typedef可以只有一个参数,不一定是两个
样例代码:
typedef char(*p_to_char10)[10];
int main(void)
{
char chs[10];
p_to_char10 spz=&chs;
printf("%ld %ld", chs , spz+1);
return 0;
}
其运行结果: 6422274 6422284
可见这样typedef的新类型p_to_char10 是可以的
上题:比较有难度 结合了指针和typedef
求输出结果:
#include <stdio.h>
#include <stdlib.h>
typedef char(*AP)[5];
AP foo(char* p){
for(int i=0;i<3;i++)
{
p[strlen(p)] = 'A';
}
return (AP)p+1;
}
int main()
{
char s[]="FROG\0SEAL\0LION\0LAMB";
puts(foo(s)[1]+2); // *(foo(s)+1)+2
return 0;
}
其运行结果是: ONALAMB
分析:主函数中,把s地址传入foo函数,然后char*p这个指针接到首地址,进行for循环,strlen[p]第一次等于4(到第一个\0),所以p[4]='A',所以把第一个\0填上‘A’,下次运行strlen[p]=9,恰好把第二个\0变成‘A’,所以for循环跑完后,字符串变成了FROGASEALALIONALAMB,然后返回一个AP型的指针还要+1,AP是强制转换,因为函数的返回类型就是AP型。AP是证明类型呢,看typedef char(*AP)[5]; 是一个,指向有五个字符型元素的数组的数组指针,所以p就由原来的char *型转化成了AP型 ( char (*)[5] )然后p+1,也就是按照新的位移基准,向后移动1个单位(5个char型),也就是由F指向了S,然后看puts里面的东西变成了puts(p[1]+2); p[1]相当于*(p+1) 也就是又向后移动1个单位(5个char型),现在指着L,然后+2,这地方有隐含的类型转换,*(foo(s)+1)+2这地方隐含着把AP型又转成了char* 型,因为+1 +2不是一个等级,这是一个数组指针,*(foo(s)+1)只表示元素的地址,+2后,要按照自己的基准(隐含转换)进行位移,所以指向了O,puts当前位置到\0处,所以是ONALAMB。
四、函数指针
样例代码:char *(*fp) (int , float *);
分析意义:fp是一个指向函数的指针 ,函数的参数是int和 float*,返回值是一个指针 指向char型
命名法则:返回类型 + (*p) (传入参数类型)
补充:sizeof(函数名)没有意义 默认是1。
五、C++
之前没有基础,关于引用问题没听,需要课下去b站找视频看。