LeetCode 2. Add Tow Numbers

题目:

You are given two linked lists representing two non-negative numbers. The digits are stored in reverse order and each of their nodes contain a single digit. Add the two numbers and return it as a linked list.

Input: (2 -> 4 -> 3) + (5 -> 6 -> 4)

Output: 7 -> 0 -> 8

简单的加法模拟, 代码如下:

/**
* Definition for singly-linked list.
* struct ListNode {
*     int val;
*     ListNode *next;
*     ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        //如果有一个链表为空,则直接返回另一个
        if(l1 == nullptr)
            return l2;
        if(l2 == nullptr)
            return l1;
            
        ListNode *p1 = l1, *p2 = l2;
        while(p1->next != nullptr || p2->next != nullptr){
            //填充较短的数, 高位添加0, 这一步可以用计算后再处理的办法替代
            if(p1->next == nullptr)
                p1->next = new ListNode(0);
            if(p2->next == nullptr)
                p2->next = new ListNode(0);
            p1 = p1->next, p2 = p2->next;
        }
        
        ListNode *head = new ListNode(0), *p = head;
        int jw = 0;//jw保存上一位计算后的进位
        for(p1 = l1, p2 = l2; p1 != nullptr && p2 != nullptr; p1 = p1->next, p2 = p2->next){
            //模拟每一位的加法
            int t = p1->val + p2->val + jw;
            jw = (t > 9 ? 1 : 0);
            
            t = t % 10;
            p->next = new ListNode(t);
            p = p->next;
        }
        if(jw == 1){
            //最后有进位的处理
            p->next = new ListNode(1);
        }
        
        return head->next;
    }
};

LeetCode 1. Two Sum

LeetCode的第一题,题目如下:

Given an array of integers, return indices of the two numbers such that they add up to a specific target.

You may assume that each input would have exactly one solution.

Example:

Given nums = [2, 7, 11, 15], target = 9,
Because nums[0] + nums[1] = 2 + 7 = 9,
return [0, 1].

首先是简单粗暴的使用STL Map的方法. 基本思路是把nums中的数据与其下标建立一个映射关系, 这样就可以在logn的时间里通过数值获得它的下标:

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        map<int, int> a;
        for(int i = 0; i < nums.size(); i++){
            a[nums[i]] = i;
        }
        for(int i = 0; i < nums.size(); i++){
            auto p = a.find(target - nums[i]);
            if(p != a.end() && p->second != i){
                vector<int> re = {i, p->second};
                return re;
            }
        }
        return vector<int>();
    }
};

以上方法有两个循环, 其中第一个循环遍历了一遍nums,同时对于每个nums中的数据都进行了map的插入/修改操作, 假设nums的数据个数为n, 复杂度约为O(nlogn). 第二个循环中也是遍历了一遍nums, 然后每一遍循环都执行了一次map的find()操作,最好的情况下只需要循环一次,最坏的情况下需要循环n次,因此平均复杂度约为O((n/2)logn), 总的复杂度在O(nlogn)级别.

这段程序运行时间28ms.

但是这个代码使用了STL中的Map容器, 可能有较大的额外开销, 接下来使用排序 + 二分搜索的方法:

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        vector<int> re;
        vector<int> oldNums = nums;
        sort(nums.begin(), nums.end());
        for(int i = 0; i < nums.size(); i++){
            int t = target - nums[i];
            if(t < nums.front() || t > nums.back()) continue;
            int j = binSearch(nums, t);
            if(j == -1 || j == i) continue;
            else {
                int num1 = nums[i], num2 = nums[j];
                for(int k = 0; k < oldNums.size(); k++){
                    if(oldNums[k] == num1 || oldNums[k] == num2) re.push_back(k);
                    if(re.size() == 2) break;
                }
                return re;
            }
        }
        return re;
    }
    
    int binSearch(vector<int> &nums, int t){
        int l = 0, r = nums.size(), m = (l + r) / 2;
        while(l < r){
            if(nums[m] == t) return m;
            else if(nums[m] > t) r = m;
            else l = m + 1;
            m = (l + r) / 2;
        }
        return -1;
    }
};

第二个方法的运行时间缩短为12ms.

还有另一种更加简洁的双指针方法:

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        vector<int> re;
        vector<int> oldNums = nums;
        sort(nums.begin(), nums.end());
        int l = 0, r = nums.size() - 1;
        while(l < r){
            int sum = nums[l] + nums[r];
            if(sum == target){
                for(int i = 0; i < nums.size() && re.size() < 2; i++){
                    if(oldNums[i] == nums[l] || oldNums[i] == nums[r]) re.push_back(i);
                }
                break;
            }
            else if(sum > target){
                r--;
            }
            else{
                l++;
            }
        }
        return re;
    }
};

川大江安校区宽带破解

0 前言

四年大学生活马上就要结束了,想一想这四年在江安的生活还真是相当不错的;不过对于我来说,网络大概是江安学习生活中最烦人的东西了。从我2012年入学开始到现在的2016年,川大江安在宿舍所能使用的网络接入方式有三种:校园网、移动CMCC-EDU和电信宽带。其中校园网虽然免费,这四年来雷打不动的512kbps的带宽,基本是处于看图片都是煎熬的水平;CMCC-EDU我基本没用过,速度大概是4Mbps左右,可惜无线不稳定;剩下的电信宽带虽然非常贵(一个月79元,带宽6Mbps,但是可以抵扣话费,奈何我不用电信手机),但是矮子里面拔将军,是唯一比较好用的了。

但是,这是建立在电信不限制路由器的基础上的。其实在2012年下半年的时候,电信宽带是不限制路由器的,使用普通的pppoe拨号方式就可以上网了。但是这只是电信认证系统的bug而已(还有另一种阴谋论的说法,就不详细说了。另外江安这个认证系统bug极多,后面我会详细说说),2013年上半年的时候修复了,导致路由器失效,接着电信更新了拨号器(就是本文将要破解的),导致飞扬俱乐部的在线算号器失效(方法是粗暴的禁止Linux版拨号器,至今再没有可用的官方Linux拨号器),当时在学生中算是引起了很大的反弹,可是学校方面不知道出于什么原因一直避而不谈这件事(呵呵,这其中必然有肮脏的**交易),电信抬出什么公安部教育部要求大学生上网一人一号的文件(监控之心不死)来当挡箭牌,这件事后来也不了了之了。但是不能用路由器你让手机iPad怎么上网?许多同学选择通过电脑发射无线信号(其实这也是被拨号器所屏蔽的,不过比较好突破),但是这个方法非常繁琐,要保持电脑一直开机。除了这种方法以外就只能放弃电信宽带了,不过我是一个网速多快都不嫌快,慢一点就无法忍受的人,所以就只能自己动手来破解电信的拨号器了。

在此要特别感谢软件学院的HZY同学,在我破解的过程中提供了很多帮助。

我的宽带帐号是12年就有的,用户名是学号,跟后来的用户名是手机号的不一样,所以不保证对后来的账号有效。

完整代码与原版Mac拨号器程序请移步Github

1 2.21版Mac协同拨号器拨号流程

我在13年的时候破解的是2.21版的Mac拨号器,Windows版的拨号器的程序逻辑要相对复杂许多,这个版本的算法直到目前(16年6月)都还是可用的。PPPoE的介绍和普通的PPPoE拨号流程可以参考Wikipedia:https://www.wikiwand.com/zh-cn/PPPoE和RFC文档:https://tools.ietf.org/html/rfc2516。计算机自带的PPPoE拨号器无法连接电信宽带的原因主要出在电信使用一种“二次验证”的机制,即计算机第一次拨号的时候是必然失败的,同时认证服务器返回一个字符串,拨号器根据这个字符串通过算法生成一个新的用户名,再次进行拨号才能成功。其中第一次拨号的用户名也是通过算法生成的,不过与服务器无关。

通过抓包就可以比较直观的看到拨号器拨号的整个流程,我已经很久没有装过协同拨号器了,在这里只贴一下我的路由器模拟的拨号过程。

这是第一次拨号的过程,可以看到CHAP认证失败并且返回16进制字符串:37f13ef44a72。

1
2
3
4
5
6
7
8
9
10
11
12
Jan  1 00:34:12 PandoraBox daemon.info pppd[2435]: Plugin rp-pppoe.so loaded.
Jan 1 00:34:12 PandoraBox daemon.info pppd[2435]: RP-PPPoE plugin version 3.8p compiled against pppd 2.4.5
Jan 1 00:34:12 PandoraBox daemon.notice pppd[2435]: pppd 2.4.5 started by root, uid 0
Jan 1 00:34:12 PandoraBox daemon.info pppd[2435]: PPP session is 30874
Jan 1 00:34:12 PandoraBox daemon.warn pppd[2435]: Connected to 00:25:9e:08:b8:3e via interface eth2.2
Jan 1 00:34:12 PandoraBox daemon.info pppd[2435]: Using interface pppoe-wan
Jan 1 00:34:12 PandoraBox daemon.notice pppd[2435]: Connect: pppoe-wan <--> eth2.2
Jan 1 00:34:15 PandoraBox daemon.info pppd[2435]: syncppp not active
Jan 1 00:34:15 PandoraBox daemon.info pppd[2435]: CHAP authentication failed: 37f13ef44a72
Jan 1 00:34:15 PandoraBox daemon.err pppd[2435]: CHAP authentication failed
Jan 1 00:34:15 PandoraBox daemon.notice pppd[2435]: Connection terminated.
Jan 1 00:34:15 PandoraBox daemon.info pppd[2435]: Exit.

这是第二次的拨号流程,可以看到认证成功并分配了IP地址等信息。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Jan  1 00:34:23 PandoraBox daemon.info pppd[2447]: Plugin rp-pppoe.so loaded.
Jan 1 00:34:23 PandoraBox daemon.info pppd[2447]: RP-PPPoE plugin version 3.8p compiled against pppd 2.4.5
Jan 1 00:34:23 PandoraBox daemon.notice pppd[2447]: pppd 2.4.5 started by root, uid 0
Jan 1 00:34:23 PandoraBox daemon.info pppd[2447]: PPP session is 19277
Jan 1 00:34:23 PandoraBox daemon.warn pppd[2447]: Connected to 00:25:9e:08:b8:3e via interface eth2.2
Jan 1 00:34:23 PandoraBox daemon.info pppd[2447]: Using interface pppoe-wan
Jan 1 00:34:23 PandoraBox daemon.notice pppd[2447]: Connect: pppoe-wan <--> eth2.2
Jan 1 00:34:26 PandoraBox daemon.info pppd[2447]: syncppp not active
Jan 1 00:34:26 PandoraBox daemon.info pppd[2447]: CHAP authentication succeeded: Authentication success,Welcome!
Jan 1 00:34:26 PandoraBox daemon.notice pppd[2447]: CHAP authentication succeeded
Jan 1 00:34:26 PandoraBox daemon.notice pppd[2447]: peer from calling number 00:25:9E:08:B8:3E authorized
Jan 1 00:34:26 PandoraBox daemon.notice pppd[2447]: local IP address 220.167.43.208
Jan 1 00:34:26 PandoraBox daemon.notice pppd[2447]: remote IP address 220.167.40.1
Jan 1 00:34:26 PandoraBox daemon.notice pppd[2447]: primary DNS address 61.139.2.69
Jan 1 00:34:26 PandoraBox daemon.notice pppd[2447]: secondary DNS address 202.98.96.68
Jan 1 00:34:26 PandoraBox daemon.notice netifd: Interface 'wan' is now up

3 关于如何破解

主要使用IDA作为反汇编工具,最关键的就是阅读反汇编拨号器出来的汇编代码和IDA生成的反编译代码。编写自己的拨号器的使用的是C语言,当然也可以自由选择其他的语言。使用libpcap在程序内进行抓包。我在破解的最后阶段还用了GDB在命令行模式下直接调试官方拨号器,在没有调试信息的情况下跟踪程序运行真是很酸爽。

这个破解程序是差不多三年前写的了,当时接触编程和C语言不到一年,代码写的很烂,但是因为一直能用就没有再做什么改动了。

4 生成第一次拨号的用户名

这个生成第一次拨号用户名的算法是完全模拟的协同拨号器的工作方式。首先获取系统时间,再和一个特殊字符串、用户名、密码拼接成一个新字符串:

1
2
3
time1=time(NULL);
sprintf((char*)strTime,"%08x",time1);
sprintf((char*)first,"%s%s%s%s",strTime,"m2o=crE54nyNUht[",username,psw);

对该字符串计算MD5值并转换成16进制表示的字符串:

1
2
3
4
5
6
7
8
9
10
MD5_CTX md5;
MD5Init(&md5);
MD5Update(&md5,first,strlen((char*)first));
MD5Final(&md5,md5Result);
//把md5转换成字符串
for(int j=0;j<16;j++)
{
sprintf((char*)pMd5Str,"%02x",md5Result[j]);
pMd5Str+=2;
}

然后取该字符串的前19个字符,再与其他字符串一起拼接成最终的用户名:

1
2
char md5Str19[20]={0};
memcpy(md5Str19,md5Str,19); sprintf((char*)userName,"%s%s%s%s%s",strTime,"M","2021",md5Str19,username);

5 进行拨号

获得了第一个用户名之后就可以进行拨号了。这个拨号的方法是根据不同的平台而不同,我在Windows和Linux系统(主要是路由器运行的OpenWRT)上都移植过这个程序,可以说唯一需要修改的地方就是这里了。我在这里放一下我在路由器上运行的版本的拨号函数:

1
2
3
4
5
6
7
8
9
10
int PPPoeDial(char *user,char *pwd,char *name,char *device)
{
char pppoe_cmd[1024];
char ifname[40]="pppoe-";
sprintf(pppoe_cmd,"/usr/sbin/pppd nodetach ipparam %s ifname %s nodefaultroute usepeerdns persist maxfail 1 user %s password %s ip-up-script /lib/netifd/ppp-up ipv6-up-script /lib/netifd/ppp-up ip-down-script /lib/netifd/ppp-down ipv6-down-script /lib/netifd/ppp-down mtu 1492 mru 1492 plugin rp-pppoe.so nic-%s &",\
name,strcat(ifname,name),user,pwd,device);
system(pppoe_cmd);
puts("拨号中。。。");
return 1;
}

这里的pppoe_cmd的内容要根据对应的平台调整。Windows平台本身就提供了拨号函数,不需要运行pppd之类的命令了。

6 获取服务返回的参数

在我的程序中是通过使用libpcap库进行抓包来获取服务器返回的字符串的。libpcap库相关的信息请访问官网http://www.tcpdump.org。对每个数据包的回调函数是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void packet_handler(u_char *param, const struct pcap_pkthdr *header, const u_char *pkt_data)
{
if(valid && pkt_data[22]==0x04&&pkt_data[23]==0x01)
{
pcap_breakloop(adhandle);
valid = 0;
char seed[10]={0};
for(int i=0;i<8;i++)
{
sprintf(&seed[i],"%c",pkt_data[30+i]);
}
printf("seed:%s\n",seed);
int time2=((unsigned int)rand())<<16|rand();
char result[50]={0};
getSecondUserName(result,seed,g_userName,g_pwd,time1,time2);
printf("%s\n",result);
sleep(8);
PPPoeDial(result,g_pwd,wan,d->name);
}
}

其中pkt_data[22]==0x04&&pkt_data[23]==0x01是表示这个数据包是服务器返回的认证失败的数据包,从中取得字符串seed,然后产生一个随机数time2,作为产生第二个用户名的参数。sleep(8)的目的是等待第一次拨号的进程完全退出以免造成冲突。 ​

7 生成第二次的用户名

生成第二次用户名的函数比较复杂,具体还是参见代码中的实现吧。主要思路仍然是对字符串进行处理后计算MD5值再进行拼接。

8 运行平台

在破解出这个算法后,我写过Windows和OpenWRT版本的拨号器,我相信大多数同学都是想使用路由器的,使用路由器很简单,只需要一个刷了OpenWRT的路由器就可以了,下载相应的工具链之后进行交叉编译,把得到的可执行文件上传到路由器上,再添加启动脚本就可以实现开机自动连接宽带了。

9 后记与扯扯淡

江安校区的电信宽带真的是相当奇葩,贵而难用,而且BUG多多,单单我知道的就有:把用户名最后几位去掉就可以使用无上限的带宽(取决于你寝室的出口物理带宽);同样是去掉几位就可以进行无限制的多拨(带宽叠加);绑定的手机号注销之后宽带帐号还在等等。靠着前两个bug我还是享受了将近两年的50M以上宽带的,虽然到现在这些bug已经修复了,但是说不定还是有其他bug,这就交给学弟学妹们来开发啦。

有了OpenWRT路由器之后可玩的东西就变得挺多了,完全可以当一个24h运行小服务器来用。比如以前我还在用多拨叠加宽带的时候因为有一定概率拨号失败(挺低的),所以就写了一个shell脚本,拨号完成后发邮件通知我有没有没连上的连接。或者写一个桥接CMCC信号并且自动登陆的脚本等等。

在Qt中使用Botan库进行加解密

在Qt中使用Botan库进行加解密

在毕设项目中需要使用RSA和AES加解密, 因为Qt自带Botan库, 所以使用Botan库来实现. Botan项目网站http://botan.randombit.net

在项目中使用

因为Qt Creator自带Botan库, 所以很容易就可在Qt项目中使用. 首先到Qt Creator的Github页面https://github.com/qtproject/qt-creator, 找到qt-creator/src/libs/3rdparty/botan/目录, 下载其中的文件. 然后在项目的pro文件中使用include(botan/botan.pri)引入Botan的源代码.

RSA加解密

引入头文件 #include “botan/botan.h”

生成密钥对

Botan::AutoSeeded_RNG rng;
Botan::RSA_PrivateKey privateKey(rng, 1024);

输出密钥

qDebug() << Botan::X509::PEM_encode(privateKey).c_str() << Botan::PKCS8::PEM_encode(privateKey).c_str();

对字符串加密

Botan::PK_Encryptor_EME enc(privateKey, "EME1(SHA-256)");
char msg[] = "Test";
Botan::SecureVector<Botan::byte> en = enc.encrypt(msg, 6, rng);

解密

Botan::PK_Decryptor_EME dec(privateKey, "EME1(SHA-256)");
Botan::SecureVector<Botan::byte> re = dec.decrypt(en);

Windows平台poppler-qt5预编译库

项目中要对PDF文件进行预览, 搜索与Qt搭配的库之后选择poppler库, 官网地址: https://poppler.freedesktop.org/, 没有找到win32平台的二进制文件, 所以只能下载源码自行编译, 由于依赖库众多, 经常报莫名其妙的编译错误, 在耗费了十几个小时后终于编译出了可用的dll文件, 在Qt工程中使用正常. 为了备份和方便后来人, 提供编译好的poppler-qt5的库下载.

poppler版本:

0.41.0

编译器

  • gcc 5.1.0 32-bit
  • mingw32-make GNU make 3.82.90

目录结构

libpoppler.zip
|
|--bin
|   |--freetype6.dll
|   |--jpeg62.dll
|   |--libpoppler.dll
|   |--libpoppler-qt5.dll
|   |--libtiff3.dll
|   |--openjpeg.dll
|   |--pdfinfo.exe
|   |--zlib1.dll
|
|--include
|   |--poppler
|       |--qt5
|           |--poppler-annotation.h
|           |--poppler-export.h
|           |--poppler-form.h
|           |--poppler-link.h
|           |--poppler-media.h
|           |--poppler-optcontent.h
|           |--poppler-page-transition.h
|           |--poppler-qt5.h
|
|--lib
    |--libpoppler.dll.a
    |--libpoppler-qt5.dll.a

下载地址:

Google Drive: https://drive.google.com/open?id=0BzmeJgHvo4mIV1V1bW9zd2pRaWs

百度云: http://pan.baidu.com/s/1jHlxTOU

C++&Qt操作Word模版基础

项目中要对Word模版进行操作, 使用C++&Qt进行开发, 将学到的东西记录如下.

0 参考链接

  1. https://en.wikipedia.org/wiki/OLE_Automation

  2. http://www.cgoakley.org/prog/oleaut.html

  3. https://zh.wikipedia.org/wiki/ActiveX

  4. http://www.oschina.net/question/1243014_120926

  5. http://blog.csdn.net/csxiaoshui/article/details/47333989

1 基础知识

1.1 OLE Automation

在Windows应用开发中, OLE Automation是一种进程间通信机制. 它基于COM和MFC, 因此在我的项目中并不适用.[1][2]

OLE(Object Linking and Embedding,对象链接与嵌入),是能让应用程序创建包含不同来源的复合文档的一项技术。OLE不仅是桌面应用程序集成,而且还定义和实现了一种允许应用程序作为软件“对象”(数据集合和操作数据的函数)彼此进行“链接”的机制,这种链接机制和协议称为部件对象模型(Component Object Model),简称COM。OLE可以用来创建复合文档,复合文档包含了创建于不同源应用程序,有着不同类型的数据,因此它可以把文字、声音、图像、表格、应用程序等组合在一起。

1.2 ActiveX

根据Wikipedia[3], ActiveX的含义是:

ActiveX在广义上是指微软公司的整个COM架构,但是现在通常用来称呼基于标准COM接口来实现对象链接与嵌入(OLE)的ActiveX控件。[1]后者是指从VBX发展而来的,面向微软的Internet Explorer技术而设计的以OCX为扩展名的OLE控件。通过定义容器和组件之间的接口规范,如果编写了一个遵循规范的控件,那么可以很方便地在多种容器中使用而不用修改控件的代码。同样,通过实现标准接口调用,一个遵循规范的容器可以很容易地嵌入任何遵循规范的控件。由于OLE在ActiveX控件中的应用的普及,现在OLE技术中只有少数独立于ActiveX技术,例如复合文档。

2 Qt中的ActiveX[5]

Qt中提供QtActiveX模块来支持ActiveX, 有两种开发方式:

  1. 将已有的COM或者ActiveX空间引入到Qt的应用程序中
  2. 将Qt应用程序或者Qt的对象导出成COM对象或者ActiveX控件供他人使用

Qt是通过两个模块来支持上述所说的两种方式的:

  1. 使用QAxContainer模块, 通过QAxObject和QAxWidget分别支持COM对象和ActiveX控件的开发, 可以通过这两个对象将外部的COM或者ActiveX组件接入到Qt应用程序.
  2. 使用QAxServer模块, 通过QAxAggregated, QAxBindable和QAxFactory类, 通过了进程内和可执行程序exe两种方式的COM Server模式, 用来将Qt写的内容导出为COM或者ActiveX供他人使用.

3 Qt中使用ActiveX[4]

Qt版本5.4.2, 编译器: MinGW 4.9.1 32-bit.

3.1 准备工作

创建一个Word模版, 插入一个表格和三个书签: pos1, pos2, pos3. 保存为Doc1.dot.

3.2 在项目的.pro文件中增加如下一行

QT   += axcontainer`</pre>

3.3 新建Qt项目,输入如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include "mainwindow.h"
#include <QApplication>
#include <QAxWidget>
#include <QAxObject>

int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();

//新建一个word应用程序
QAxWidget *word = new QAxWidget("Word.Application", 0, Qt::MSWindowsOwnDC);
//设置为不可见
word->setProperty("Visible", false);
//获取所有工作文档
QAxObject *docs = word->querySubObject("Documents");
//新建一个文档
docs->dynamicCall("Add(QString)", QString::fromLocal8Bit("E:\\Study\\Design\\WordTempTest\\Doc1.dot"));
//获取激活文档
QAxObject *activeDoc = word->querySubObject("ActiveDocument");
//获取pos1标签
QAxObject *bookmarkPos1 = activeDoc->querySubObject("Bookmarks(QVariant)", "pos1");
//选中并插入字符
if(!bookmarkPos1->isNull()){
bookmarkPos1->dynamicCall("Select(void)");
bookmarkPos1->querySubObject("Range")->setProperty("Text", "pos1");
}
//获取pos2标签
QAxObject *bookmarkPos2 = activeDoc->querySubObject("Bookmarks(QVariant)", "pos2");
//选中并插入字符
if(!bookmarkPos2->isNull()){
bookmarkPos2->dynamicCall("Select(void)");
bookmarkPos2->querySubObject("Range")->setProperty("Text", "pos2");
}
//获取pos3标签
QAxObject *bookmarkPos3 = activeDoc->querySubObject("Bookmarks(QVariant)", "pos3");
//选中并插入字符
if(!bookmarkPos3->isNull()){
bookmarkPos3->dynamicCall("Select(void)");
bookmarkPos3->querySubObject("Range")->setProperty("Text", "pos3");
}
//文档另存为Doc1.doc
activeDoc->dynamicCall("SaveAs (const QString&)", QString("E:\\Study\\Design\\WordTempTest\\Doc1.doc"));
activeDoc->dynamicCall("Close (boolean)", false);
word->dynamicCall("Quit()");

return a.exec();
}

3.4 运行结果

程序运行后Doc1.doc文件的内容:

Windows 10 自动锁屏问题解决

问题

升级到Windows10以后我遇到的各种小毛病不少,比如中文输入法失灵,锁屏无法显示幻灯片还有这次要说的自动锁屏问题。前两个问题基本上都已经解决了,但是这个自动锁屏的问题却还是顽固地出现。

具体的症状表现就是每半小时就会自动锁屏,无论在干什么都是这样。不过幸好这个问题不是一直不停地出现,一般都是在睡眠唤醒之后半小时出现一次,虽然很烦但是对于我这样的懒人还不是无法忍受。可是今天这种情况反复不停出现,每半小时一次,所以我不得不想办法把它彻底解决了。

问题原因

晚上这个问题又出现了,所以这并不是直接原因

由于是精确的每半小时出现一次,所以我就到事件查看器里看看到底是出了什么状况。很容易就找到了相应的事件: 所对应的具体信息为:

应用程序-特定 权限设置并未向在应用程序容器 不可用 SID (不可用)中运行的地址 LocalHost (使用 LRPC) 中的用户 NT AUTHORITY\SYSTEM SID (S-1-5-18)授予针对 CLSID 为 {D63B10C5-BB46-4990-A94F-E40B9D520160}、APPID 为 {9CA88EE3-ACB7-47C8-AFC4-AB702511C276}

解决方案

同样这也不是解决方法

Google之后,根据这篇文章所描述的方法设置了权限之后重启,问题解决。

可能的其他原因

根据MS Community的这篇帖子(http://answers.microsoft.com/en-us/windows/forum/windows_10-security/windows-10-auto-locks-every-30-minutes-even-when/b53e7ff7-7644-4d51-8038-62f8f2eb9a26),可能的原因是由于锁屏幻灯片导致的,而我的锁屏幻灯片也确实是有问题的,按照帖子的说法把锁屏幻灯片关掉就好,可是我并不想关,所以我先把设置->个性化->锁屏界面->高级幻灯片放映设置->自动关闭屏幕设置为不自动关闭(因为这里原来设置为30分钟,跟锁屏的频率吻合)。接下来就要看看还会不会出现同样的问题了。

2015腾讯实习笔试+面试+offer

本来我是已经决定要保研,但是这个学期还是投了一些实习简历,主要是想看一下我的水平到底如何,顺便也能感受一下找工作的感觉。寒假快要结束的时候给阿里投了内推,在寒假的最后一天接到电面,面试过程相当坑爹,我基本是裸面,结果是毫无悬念的跪了。来到学校之后又尝试着投了腾讯和阿里的正式校招,也参加了微软的在线笔试(到现在都没有动静估计也是没戏了)。上个星期腾讯面试完,今天拿到offer,简单记录一下腾讯实习的面试经历。

笔试

笔试在电子科大在郫县的校区进行,人数还是不少了,占用了不少阶梯教室。基本上所有报技术职位的都使用一张笔试试卷,很长的一张,正反一共十面,不过有大概三分之一是各个事业群的介绍。有20(还是25道?)选择题(都是不定项选择题),和四道简答题。总体来说难度不是很小,考察了js,C/C++,计算机网络,数据库,运维和一些概率统计方面的问题。我感觉至少有一半的题目不确定,印象比较深刻的知识点有字节对齐,基类有虚函数的派生类的大小等等。。。记忆已经有些模糊了。大题第一题是存储了很多很多QQ号的vector、set和map其中的两个(= =我已经记不清了),删除其中是奇数的QQ号,写算法。第二题是经典的斐波那契数列问题。第三题和第四题已经忘记了,只记得做了第三题,第四题和第一题一样是空白。

一面

笔试似乎刷人不多,跟我一起笔试的同学基本都进入面试了。面试是在成都的明宇尚雅饭店进行,就在锦江边上,4月7号晚上发短信要第二天早上11点面试。

第二天早上先坐67路再转地铁到华西坝站,出地铁站走几百米就到了。到那里发现人还是很多的,大概是一面的人比较多的缘故,还遇到了CJW君去霸面(而且还霸面成功了。。。)。签到之后等了没几分钟就让去楼上面试。面试官是一个瘦瘦的眼镜小哥。主要问的问题有

  • 结构体字节对齐(C语言字节对齐问题详解)
  • 要求不对齐有哪些方法
  • 异构网络中怎么解决不同主机对齐不同的问题(没答出来)
  • C可变参数(【C++基础之二十】可变参数的函数)
  • 笛卡尔积(数据库)
  • 左右连接
  • tcp建立拆除(三次和四次握手,这问题几乎是必问,阿里问了,后面的二面也问了)
  • md5冲突(md5重复的问题)
  • 实习地点、时间
  • C++只问了会不会没有具体问

一共大概二十多分钟就出来了,没有要求写代码。

还是一面。。。

我自认为第一次面试回答的还是不错的,只有一个没答出来。可是回学校的时候一查。。。岗位不合适。。。= =#,结合后面的面试经历我猜测面试官一开始就没打算让我进微信事业群(我报的微信事业群后台开发),基本没有问什么有实际意义的东西。

到了晚上十一点多的时候又接到短信,再去初试(一面),看来是换了个部门。第二天下午6点半又到了饭店,这次等待面试的地方人明显少了很多(这个时候一面已经基本结束了)。这次直接到顶楼,面试官年纪比上一个要大一些。上来没有自我介绍,先问我会啥,我说C/C比较熟,于是就问:“说一说C的多态吧”。C++这方面的东西还是前几天恶补的,只能硬着头皮扯。扯到了动态绑定、虚函数等等,然后面试官问虚函数的继承是怎么实现的,有什么意义。我只说出来了虚函数表。。。又问为什么基类的析构函数一般都是虚函数,这个我是看过的,可是一下子想不起来。。。这个话题结束了。。。下一个问题是new和malloc的区别,这个我还有点印象;然后问到堆与栈的区别;静态变量存储在哪个区域。这些都答上来了。然后问到从数据库中读取了很多联系人数据,在内存中用什么数据结构来管理,要求搜索,排序,添加删除都比较快,数组添加删除太慢,链表搜索排序慢,最后我也只扯出来了平衡二叉搜索树这种,本来想说B+树可是实在不熟。接下来是三个数据结构题:

  • 两个单向链表可能相交也可能不相交,求相交的节点(暴力遍历不行)
  • 一个字符串中单词顺序反转(反转两次)
  • 交换单链表的相邻两个节点(这个要手写代码,前面两个讲思路)

这三个题答得都还不错。面试官说可以进入复试,然后我问了一下现在面的是什么部门,结果是SNG(社交网络事业群)的移动客户端。。。就是手机QQ、QQ空间等。然后面试结束,让我明天早上来复试。

二面

坐电梯刚到一楼接到面试官的电话,说我现在就可以去二面,于是又坐电梯上去。这次的面试官气场有点强。。。先问了问项目经历、成绩等等。然后开始写代码,先是经典的上台阶问题,然后要求实现strlen,我只考虑了空指针的问题,然后用遍历来求长度,然后面试官问怎么优化。。我是没想出来(回来之后才知道要优化一要分配寄存器,二要考虑字节对齐);然后是一个很长的字符串,求第一个只出现了一次的字符。接下来问了一个智力题:两个外观大小重量都完全相同的金球和铅球在不破坏它们的情况下怎么区分开。。。我一开始想的是硬度,可是这被认为破坏了,然后是导电性,被说不好操作,其他的我也没想出来。最后问了数据库的问题,谈到了项目中用的框架,于是又聊到了框架对SQL的封装等等。。。最后得到的评价是还不错,但是项目经验太少。

HR面

二面之后我基本确定能进HR面了。HR面没有技术问题,问了籍贯、家庭背景、爱好、对成都怎么看、能不能去深圳实习等等问题。HR小哥明显跟前面的技术面试官不一样,也热情的多。

offer

一个星期后接到电话说拿到了实习名额,但是啊。。但是,考虑再三还是决定保研为重,学院还要求必须七月去实训,时间也不够还是决定拒掉实习offer了。下个月的阿里面试估计也不会再去了。。。

总结

校招时对于本科生的技术要求不是太高,面对面的面试中也不太可能出现复杂的问题,时间上也来不及,所以问的问题多集中在语言的特性(对语言的熟练程度)和数据结构上。还有简历不要乱写,写在上面的每一条都有可能被刨根问底,不是很熟悉的项目还是不要写为妙。我都没有敢说对Python比较熟,因为我只是用它但是却没有极其深入的研究,随随便便就能被问倒。

重点还是在计算机专业的核心科目上:C/C++,计算机网络,数据结构与算法、操作系统和数据库等。

说实话能拿到实习offer还是很幸运的,我的水平还是不够啊。。。

year=2015

2014又过去了,感觉上很快,但是又好像经历了很多事情。从大二到大三,说不上什么巨大的变化,但是为什么却让我觉得这一年中充斥着许许多多的事情呢?而且很多事情仿佛才过去没有多久而已。

年初的时候,那就是大二上的期末考试吧,当时是怎么考的,考了什么竟然印象全无。甚至寒假的时候在家里干了什么也没有印象呢。嗯,只记得大概练了一些题吧,然后就糊里糊涂的来了学校。然后四月份大创立项,不过也是直到暑假之前都没有怎么动手。还有参加蓝桥杯的省赛,拿了一个省一等奖,这也算是我现在为止最高的奖了吧。象ACM那样的团队比赛我似乎总是卡在“没有队友”这一点,是因为不善交际吗。。不过我的身边似乎也没有能和我一起去学习的人。班里的其他几位学霸除了上课也没有什么交集。所以我从上大学开始学习的路上就一直是一个人呢。

五月,我又真正的再次回到孤独一人的状态。很对不起,让你在这一年多的时间里忍受我的愚钝和低情商,现在真的只希望你能快乐的继续走下去。

暑假的前夕,在学校上着小学期的时候竟然入了二次元这个坑。说起来真的是很幸运,否则我真的还找不到什么学习和写程序以外的爱好了。记得以前在知乎看到一个答案,说人需要一种类似“能量”的东西,外向的人通过和他人接触从他人那里获得“能量”;而内向的人却可以自己产生这种“能量”,所以他们没有什么与别人打交道的欲望。对于这个观点我还是比较赞同的,像我这样对于和他人接触没有什么兴趣的人却可以从代码、小说或者动漫中获得满足。不知道喜欢幻想世界的人是不是都有逃避现实的倾向,但是幻想中的美好世界确实比现实中这个更有吸引力。

暑假基本都在学车,真的很坑,每天都相当累,而且科目二训练了接近二十天,从不会到会又到了不会。不过好在科目二通过了,一个月的辛苦没有全白费。暑假里面把自己的大创项目开了个头,这个项目全部都是我自己一个来完成,说实话我对于我校计算机专业的学生的平均水平基本已经不抱什么希望了。

下半年开始的一个月把项目差不多赶完了,虽然还是很粗糙但是总算所有功能都完成了。而且通过这个项目和上个学期研究开发实践的项目我对于WEB开发的流程和基本功能的开发已经比较熟悉了,前端后端都能写,怎么说也是向着所谓“Full Stack”发展吧:-)。

接下来就是日复一日的上课、学习。这学期我倒是把睡眠调整了过来,十一点之后睡觉,八点之前就能起来,算是一个好习惯吧。就这样一直到了期末,让我说这个学期学到了什么呢?嗯貌似除了课堂上学习的网络、数据库、软件工程还真没有学到什么。不过这些课都是程序员的安身立命之本,也不算荒废吧。

2015年又要到了一个人生的十字路口了,工作/保研该怎么选择我还没有下定决心。在我的意识之中现在的IT界对于本科以上学历的高低的区别已经不大了,在校园中学到的东西在工作中能用到多少还是个问题。但是看着互联网尤其是移动互联网的疯狂发展,我总是觉得有点担忧,如果国内的互联网产业真的有泡沫,那么如果一旦破了,普通程序员就是最先遭殃的一部分人。而且看着国内IT业界一片纷纷扰扰的乱象总是不像是有着多模巨大的发展潜力,我只看到了互相倾轧,各种口水战,无下限的营销手段,把普通用户当作待宰的羔羊。我如果明年就结束校园生活,那么这种环境我又能发展的什么地步呢?我真的没有信心。

现在我越来越感到自己相比于其他更优秀的人的差距,要说什么突出的技能,傲人的奖项、出众的科研能力我都没有,我拿什么去竞争呢?前几天看到中山大学超算冬令营的通知,看到要学术讨论,有论文或者科研项目,我深深的觉得我差的还太多,没有怎么接触过科研,也就无从谈起什么科研能力。我现在会的只不过是基础中的基础而已,距离优秀还远得很,而且有那么多比我努力的多的人。

现在的计划大概是先参加保研夏令营,看看能不能拿到名额,如果可以的话就继续读研究生。如果没有理想的大学就还是去找工作吧。上半年的时候尝试一下BAT或者外企的实习。

2014年的末尾时我又一次的感觉到了这个国家的寒意。政府对于“墙”的不断加强已经让我感到前方的黑暗。2014年一系列的打击海外文化的举措,一遍遍的用政治力强行控制人民已经让我彻底的失望了,执政者的想法总是向着更坏的方向发展。希望有朝一日能够走出这堵墙,这是我现在最大的愿望了吧。