算法回顾序(学习方法,第一个程序,《程序设计导引及在线实践》习题,移位运算)

时间:2021-12-01 23:38:52

作为一只想脱离低级趣味的猿,离不开数学和算法,可偏偏这些东西很难捡回来。以前可能是因为姿势不对,或是因为工作或是坚持不下去。现在找到了学习方式的我,再次尝试捡回他们,后面想想要不要把高数顺便捡回来。

工欲善其事必先利其器,以前积累了很多资料结果硬盘坏了,再也找不到了,泪奔ing。不过算法刷题的地方还在那,就是北京poj:http://poj.org/。据说是几个学生搞出来了,实在是贡献太大了。然后发现了推荐的《程序设计导引及在线实践》,正好没有资料了,就用它了,去网上搜一下pdf下载下来。练习有了,指引有了,剩下就是工具了,由于这本书是c/c++语言的,所以还是用回c语言,安装一个visual studio作为开发工具。这里还要教大家一个最牛自学方法,就是要学什么,你就去加一个这方面的QQ群,找到一伙人,就不会寂寞了。顺便喊喊最近想到的一个口号:多学一点点,我比昨天更优秀。

1.第一个程序
写这小节是因为刚才跑起来visual studio懵逼了,太久没用不知道咋用的了。
1.1 首先,正常的创建项目
算法回顾序(学习方法,第一个程序,《程序设计导引及在线实践》习题,移位运算)

1.2 创建一个visual c++的控制台应用程序,控制台相当于windows上的cmd开出的命令行窗口,一般都是用它查看下最后运行结果,为了验证算法结果我们用它就行了。
算法回顾序(学习方法,第一个程序,《程序设计导引及在线实践》习题,移位运算)

1.3 介绍一下界面
算法回顾序(学习方法,第一个程序,《程序设计导引及在线实践》习题,移位运算)
ConsoleApplication2是刚才需要命名的主程序,我跳过了。
看着结构是c语言的写法

int _tmain(int argc, _TCHAR* argv[])
{
return 0;
}

这个函数是主程序入口,这个程序一跑起来,就开始运行这个函数,参数代表命令行的参数列表,如果运行这个程序需要外部参数的话可以用。

1.4 hello world
接着就是编写c语言的代码了,需要自行去学习c语言的基础。这里不介绍了,上一个hello world的源码

#include "stdafx.h"
#include <stdio.h> //printf函数需要
#include <stdlib.h> //system函数需要


int _tmain(int argc, _TCHAR* argv[])
{
printf("hello world!\n"); //在控制台打印
system("pause"); //可以去掉试试,为了让控制台不要闪掉,停留在“任意键继续”
return 0;
}

运行结果如下:
算法回顾序(学习方法,第一个程序,《程序设计导引及在线实践》习题,移位运算)

2.习题练习
基础部分不会自己搜一下资料学习,来看习题:

2.1

思考题:1.3.1:假定char类型的变量c中存放着一个’w’之前的小写字母,请写一条赋
值语句,使得c变为其后的第4个字母(比如,将c从’a’变成’e’)。解答见后。
提示:小写字母的Ascii码是连续的。

显然上面介绍数据类型之间的转换就是为这个做铺垫的,字母后的第4个字母,因为char类型可以和int类型兼容,所以可以互相转换,将char字符转换成int做运算,最后再转换回char字符即可。所以答案是:c = c+4;(a-z的ascii码是连续的)
注:算法是非常严谨的东西,很多时候都必须考虑完整。像这一题26个字母如果c=’z’,那后4位的字母是不存在的,所以题设部分说道,“c中存放着一个’w’之前的小写字母”排除了这个问题。如果没有这句话这里的答案可能就不是这个了。强调这个是因为不注意细节在poj的训练中,你的算法看起来没问题,但最后可能是通不过的。

2.2

思考题 1.4: 什么样的常量在程序运行期间会象变量一样,需要用一片内存空间来存
放,什么样的常量不需要?

果断被这一题震住了,这一题跟他描述的完全没关系,我这曾经接触c的猿也不知道答案。最后看到了网上的一篇内容,稍稍有点能理解http://blog.csdn.net/ecocn/article/details/9406601,当然了那const是c++的用法。里面有这样的描述得注意一下:“#define仅仅是在编译时做了替换,没有在内存中分配.在最后生成的代码中,#define被保存为若干个立即数,占用的是ROM”。而我们常说的内存指的是RAM,这两者的区别可以去搜索一下,学过计算机原理的可能立刻就能理解了。所以#define宏的使用不需要内存空间。而《程序设计导引及在线实践》中说的另一种常量就是1,‘a’,”abc”这样的字面值,这些是否需要呢?我比较相信这一篇的结论http://zhidao.baidu.com/question/1927024131984437507,所以有的需要,有的不需要,具体得看运行时汇编的处理过程。

2.3

思考题 2.5.5.3:如何用异或运算对一串文字进行加密和解密?进一步,如果只使用一
个字符做密钥,恐怕太容易被破解,如何改进?

这一题得能理解前面的异或运算,25^18=7。怎么来的?
首先转换为二进制
25 : 10101
18 : 10010
——————————
00111 -> 7
而异或的特点是非常神奇的,反过来也成立7^18=25 7^25=18
所以如果用密文18来加密数字25最后得到7,反过来用7来用密文18解密也能得到25。
因此,对一串文字“ab12”可以用任意字符来加密解密。比如’a’,然后循环异或

int _tmain(int argc, _TCHAR* argv[])
{

char str[] = "hello world!";
printf("加密前:%s\n", str);
char c = '1';
for (size_t i = 0; i < sizeof(str); i++)
{
str[i] = str[i]^c;
}
printf("加密后:%s\n",str);
for (size_t i = 0; i < sizeof(str); i++)
{
str[i] = str[i] ^ c;
}
printf("解密后:%s\n", str);
system("pause");
return 0;
}

执行结果:
算法回顾序(学习方法,第一个程序,《程序设计导引及在线实践》习题,移位运算)
加密成了什么鬼- -!

第二个问题,一个字符太简单了,可以用其他方式,比如再加密一次,或者字符串做加密,或者其他。
算法回顾序(学习方法,第一个程序,《程序设计导引及在线实践》习题,移位运算)

2.4 移位运算
这里面有坑,先用它的两个栗子了解下细节

#include <stdio.h> 
main() {
//1.移位运算要转换为二进制才行,这里有坑我不说,猜猜是什么?
//另外,int 4字节32位,short 2字节16位,unsigned short 2字节16位,unsigned char 1字节8位
int n1 = 15; //0000 0000 0000 0000 0000 0000 0000 1111
short n2 = 15; //0000 0000 0000 1111
unsigned short n3 = 15; //0000 0000 0000 1111
unsigned char c = 15; //0000 1111
//2.移位运算
n1 <<= 15; //左移15,0000 0000 0000 0111 1000 0000 0000 0000
n2 <<= 15; //左移15,1000 0000 0000 0000
n3 <<= 15; //左移15,1000 0000 0000 0000
c <<= 6; //左移6,1100 0000
//3. 输出,这里有坑
printf( "n1=%x,n2=%d,n3=%d,c=%x,c<<4=%d",
n1, //%x表示小写16进制,上面二进制转16进制,0 0 0 7 8 0 0 0,即78000
n2, //%d表示10进制整型值,坑就在这里,上面看起来像2^15,其实不对,因为short是有符号的,
//高1位是符号位,0为正,1为负,这里是一个负数,所以-0?也不对,整型值在内存中是以补码形式
//存在,所以1000 0000 0000 0000是一个负数补码,而且是个特殊值不能按照取反+1来求原值
//按照约定其值是-2^15代表最小负数,即-32768。
//所以一开始的那个坑是啥,能想到吗?
n3, //unsigned short是无符号的,取值为正,所以1000 0000 0000 0000是正数补码,其原码是其
//本身,转为10进制为2^15,即32768
c, //unsigned char是无符号的,1100 0000也是正数补码,不过这里求的是%x,所以是c0
c << 4); //这里还有坑,char的位移是要先转成int才能进行的,所以补全高位成32位
//00 00 00 c0左移4位,后为00 00 0c 00,即c00,其10进制数为3072
//发现坑没有?为啥上面c<<=6没有补全,这里c<<4要补全,因为<<=是个赋值操作,被赋值的对象
//是unsigned char限定在1个字节里,所以移位也限定在1字节里。而c<<4并没有被赋值,无类型
//的存在,所以默认4个字节,所以要补全。如果这里写的是c<<=4,结果会发生变化,就是0。
}

第二个栗子是这样的:

#include <stdio.h>
main(){
//同样要转为二进制
int n1 = 15; //0000 0000 0000 0000 0000 0000 0000 1111
short n2 = -15; //这里就会发现我说的第一个坑是啥了,-15的二进制怎么写?
//1000 0000 0000 1111?不对,还记得刚才说的内存中是以补码形式存在的吗?
//所以这里的坑就是这个补码,正数补码=原码,负数补码=反码+1,刚才都是正数,
//所以看不出来,正确值是 1111 1111 1111 0001
unsigned short n3 = 0xffe0; //1111 1111 1110 0000
unsigned char c = 15; //0000 1111
n1 = n1>>2; //右移,正数高位补0。00 0000 0000 0000 0000 0000 0000 0000 11
//即0000 0000 0000 0000 0000 0000 0000 0011
n2 >>= 3; //右移,负数高位补1。111 1111 1111 1111 0
//即1111 1111 1111 1110
n3 >>= 4; //右移,注意unsigned short无符号,高位补0。0000 1111 1111 1110
c >>= 3; //右移,正数,低位舍掉,高位补0。000 0000 1,即0000 0001
printf( "n1=%x,n2=%d,n3=%x,c=%x", n1, n2, n3, c);
//结果n1=0x3,
//n2是负数补码,转10进制过程:符号位保留,其他按位取反(1)000 0000 0000 0001,然后+1变回
//(1)000 0000 0000 0010,即-2
//n3=0xffe
//c=0x1
}

这两个栗子看懂了很有益处啊,下面看看思考题

思考题 1.5.5.6:有两个int型的变量a和n(0 <= n <= 31),要求写一个表达式,使
该表达式的值和a的第n位相同。

这题不说了,就是求a的第n位的数值

2.5 中间两道题太简单,略过。看看字符串的这一题:

思考题 1.13.3:编写一个函数 
int MyItoa( char * s ) ;
其功能是将 s 中以字符串形式存放的非负整数,转换成相应整数返回。例如,如果 s
中存放字符串 “1234”,则该函数的返回值就是 1234。假设 s 中的字符全是数字,且不
考虑s是空串或s太长的情况。

很显然对每个字符做一个字符到数字的处理,’1’和1相差48,’1’-48=1。得到每一位的数字然后根据位数计算即可。

#include <math.h>
#include <string.h>
...
int MyItoa(char * s){
//唯一的坑在这里
// int length = sizeof(s); 这种写法不行。因为char *ss代表字符串的首字符的内存地址,
一般是324字节,相当于sizeof(int),length始终为4,这是不对的
int length = strlen(s); //这个方法是<string.h>库里的方法,可以得到正确的长度
long num = 0;
printf("length=%d\n", length);
for (size_t i = 0; i <length; i++)
{
printf("s[%d]=%c\n", i, s[i]);
num += (s[i] - 48)*_Pow_int(10,(length-i-1));
}
return num;
}