编写无警告的代码

本文内容版权归原作者(作者e5Max)所有,我不对本文内容的真实性做任何保证。
原文地址:编写无警告的代码

今天把项目的Qt版本从Qt4.6.3升级到Qt4.8.4,重新编译项目代码的时候,特别关注了一下编译器的警告。于是找到《C++编程规范——101条规则、准则与最佳实践》翻了翻,重温了一下第1条 在高级别警告干净利落地进行编译。
如果编译器对某个构造发出警告,一般表明代码中存有潜在的问题。警告就好比代码的“肿瘤”,可能是良性的也可能恶性的——作为代码医生的我们不能对其视而不见。必须“把它弄清楚”,然后通过“改写代码以排除警告”!
典型的编译器警告示例:
1.”Unused function parameter”(未使用的函数参数):
编译器会对没有使用到的函数参数报告警告。如果确实不需要,那直接删除函数参数名就行了。
2. “Variable defined but never used”(定义了从未使用过的变量):
如果确实不需要,可以直接将之删除。 如果该变量的实例化是为了启动某项服务,则经常可以通过插入一个变量本身的求值表达式,使编译器不再报警。(这种求值不会影响运行时的速度)
3. “Variable may be used without being initialized”(变量使用前可能未经初始化):
这种警告通常意味着你的代码存在问题,请慎重处理之。
4. “Missing return”(遗漏了return语句)。:
这可能是一种好现象。即使你认为控制永远都不会运行到结尾或某个分支,则应该加上执行assert(false ) 的语句。
像下面这样(兼有注释作用的断言):

  default:  
     assert( !"should never get here!" );  // !“string” 的求值结果为false  
  
  1. “signed/unsigned mismatch”(有符号数/无符号数不匹配):
    通常需要插入一个显式的强制转换。
  2. “warning: type qualifiers ignored on function return type”(忽略了函数返回类型的类型限定符):
    通常是因为在函数前面的返回值面前添加了多余的 const 限定符。
    例如,const int getAge() const { return m_age; } ,最前面的const 是多余的,编译器会报告警告。
  3. “warning: deprecated conversion from string constant to ‘char*'”(不推荐的转化用法):
    函数原型:void setName(char *name);

    函数调用:setName(“personName”); // 编译器报告警告

释义:这是因为 char * 是一个指针,其背后的含义是:给我一个字符串,我可以修改它。

而如果我们传给函数一个“字符串常量”,这应该是没法被修改的。所以说,比较合理的办法是把参数类型修改为const char * ,而这个类型背后的含义是:给我一个字符串,我只要读取它(const意味着只读)。
8. “warning: “MAX_PATH” redefined” (重复定义) :

  #ifndef MAX_PATH
  #define MAX_PATH(260)
  #endif
  
  1. 第三方头文件。

  2. “auto-importing has been activated without –enable-auto-import specified on the command line”

在WPF中无操作后返回

在很多时候,我们需要使用WPF来实现一些程序,例如展示程序。最近我就做了这样的程序。但是有一个问题,就是如果做展示程序(类似触摸屏的那种),那么有必要让程序在无操作后返回到一个类似屏保的界面(可以参考windows的屏幕保护或者自动锁定)。很遗憾,我没有在wpf中发现具有这样功能的类/函数,只能转向求助于windows API。

在windows api中(“user32.dll”),有一个名为“ GetLastInputInfo”的API,在MSDN上,它的解释是这样的:

This fundtion is useful for input idle detection. However, GetLastInputInfo does not provide system-wide user input information across all running sessions. Rather, GetLastInputInfo provides session-specific user input information for only the session thar invoked the function.

The tick count when the last input event was received (see LastInputInfo) is not guaranteed to be incremental. In some cases, the value might be less than the tick count of a prior event. For example, this can be caused by a timing gap between the raw input thread and the desktop thread or an event raised by SendInput, which supplies its own tick count.

上面第一段话的意思是,GetLastInputInfo并不提供全局(在整个桌面环境中)的输入信息,而是提供特定的会话的输入信息。

第二段的意思是,接收到的输入时间的tick count并不是完全准确的,在某些情况下,可能会比实际的值要小,这种情况可能会由纯输入线程和桌面线程之间的时间差,或者是由SendInput引起。

_注:由于个人水平有限,以上引语的翻译可能不准确,所以最好阅读原文。另外,以上内容不会随着其出处更新而更新,用时请参考其出处_

所以最终我选用这个GetLastInputInfo,在C#/WPF中写好引用即可。
下面是本功能的核心代码

using System.Runtime.InteropServices; // 如果需要调用外部的dll的话,这个引用是必要的
using System.Windows;
using System.Windows.Threading;  // 此处是为了使用WPF的DispatcherTimer,
                                                                 // 如果你需要使用其他的Timer,请自行using
namespace
{
    public class TestClass
    {
        private DispatcherTimer timer; // 我们需要使用的timer,在timer里面我们要检测上一次操作的信息
        private readonly static int defultIdleAllowed = 5; // 默认的允许idle时间(单位为s),在这个时间里,如果没有操作,那么就返回,或者执行其他的操作
        private static int currentIdleTimeRemained = defaultIdleAllowed; // 当前剩余的可以idle的时间,单位为s

        public TestClass()
        {
            timer = new DispatcherTimer();
            timer.Tick += new EventHandler(Timer_Tick);
            timer.Interval = new TimeSpan(0, 0, 1);
            timer.Start();
        }

        private void Timer_Tick(object sender, EventArgs e)
        {
            if(HasActionInLastSecond())
            {
                if(--currentIdleTimeRemained == 0)
                {
                    currentIdleTime = defaultIdleAllowed;
                    timer.Stop();

                    // 在这里你可以做一些类似返回主屏幕的操作

                    timer.Start();
                }
            }
            else
            {
                currentIdleTime = defaultIdleAllowed;
            }
        }

        /// <summary>
        /// 未操作时间超过1s才开始计数
        /// </summary>
        /// <returns>bool,表示未操作时间是否超过1s</returns>
        private bool HasActionInLastSecond()
        {
            return GetIdleTime() / 1000 > 1;
        }

        /// <summary>
        /// 通过window api获得上次键鼠操作的时间,返回值为ms
        /// </summary>
        /// <param name = "pLastInputInfo">ref, 存放时间的一个结构体,在下文中会有定义</param>
        /// <returns></returns>
        [DllImport("user32.dll")]
        static extern bool GetLastInputInfo(ref PLASTINPUTINFO pLastInputInfo);

        private static long GetIdleTime()
        {
            PLASTINPUTINFO pLastInputInfo = new PLASTINPUTINFO;
            pLastInputInfo.cbSize = Marshal.SizeOf(pLastInputInfo);
            if(!GetLastInputInfo(ref pLastInputInfo))
            {
                return 0;
            }
            return Environment.TickCount- pLastInputInfo.dwTime;
        }

        // PLASTINPUTINFO结构体定义
        [StructLayout(LayoutKind.Sequential)]
        private struct PLASTINPUTINFO
        {
            [MarshalAs(UnmanagedType.U4)]
            public int cbSize;
            [MarshalAs(UnmanagedType.U4)]
            public uint dwTime;
        }

在Android中进行Socket编程

【此处应该有关于Socket编程和网络模型的介绍】

Java中常用的有关网络的类

针对不同的网络通信层次,Java给我们提供的网络功能有四大类:

InetAddress: 用于标识网络上的硬件资源
URL: 统一资源定位符,通过URL可以直接读取或者写入网络上的数据
Socket和ServerSocket: 使用TCP协议实现网络通信的Socket相关的类
Datagram: 使用UDP协议,将数据保存在数据报中,通过网络进行通信

Android 中的Socket编程模型

安卓中的Socket编程模型
在android中,实现Socket通信需要四个步骤:
1. 创建ServerSocket 或者Socket, 前者是作为服务器所需要的,而后者是作为客户端所需要的。在创建相应的Socket的时候,需要制定相应的地址和端口(或者侦听端口)
2. 打开Socket对应的输入/输出流
3. 按照协议对输入/输出流进行操作
4. 按照顺序关闭输入/输出流,以及Socket

Socket服务端

如果需要在安卓上建立服务端,那么我们需要这样做:
1. 建立ServerSocket,指定要侦听的IP地址和端口(注意IP地址不能是Localhost对应的地址,很多时候localhost对应的地址是127.0.0.1,有时会侦听不到)
2. 调用accept()方法来监听
3. 连接建立后,通过输入流读取客户端发来的消息;
4. 通过输出流响应客户端;
5. 按照顺序关闭输入/输出流,以及Socket来结束通信
示例代码:

public void DoServer(string IP, int port)
{
    ServerSocket serverSocket = new ServerSocket(port); // 创建ServerSocket
    Socket socket = serverSocket.accept() // 侦听端口,接受请求
    InputStream inStream = socket.getInputStream(); //创建InputStream
    OutputStream outStream = socket.getOutputStream(); //创建OutputStream
    InputStreamReader inputStreamReader = new InputStreamReader(inStream, "UTF-8"); //创建InputStreamReader,并配置编码,此处的编码应该和客户端发送的时候的编码一致,否则会出现乱码
    BufferedReader bufReader = new BufferedReader(inputStreamReader); //创建缓冲读取,这样我们就可以从缓冲区读取数据,而不用担心数据丢失
    while(string data = bufReader.readLine() != null) //循环读取缓冲区
    {
        // do something you want
        System.out.println(data);
    }
    socket.shutdown();
    socket.close();
}

Socket客户端

在客户端,需要做的事情就会少一些(不用侦听),一共需要大概四步:
1. 创建Socket对象,说明服务端的IP和端口号;
2. 通过输出流向服务端发送信;
3. 通过输入流获取服务端发来的消息;
4. 关闭有关的Socket;
示例代码:

public void DoClient(string IP, int port)
{
    Socket socket = new Socket(IP, port); // 创建Socket,指明服务端的IP和端口
    OutputStream outStream = socket.getOutputStream(); // 从socket创建输出流
    PrintWriter printWriter = new PrintWriter(outStream); //创建流写入器
    // do something with printWriter
    printWriter.write("Hello, server!");
    printWriter.flush(); // 这一步是必要的,有时向printWriter写入的数据不会立即发送,这时我们就需要flush一下
    socket.shutdownOutput(); //关闭输出流
    socket.close();

Note

  • 需要注意的一点是,在Android中不允许在主线程中进行网络操作,如果这样做了,那么程序会在进行网络链接的时候崩溃/闪退/Crash,崩溃的原因是抛出了“NetworkOnMainThreadException”异常参见此处

    The explanation as to why this occurs is well documented on the Android developer’s site:

    A NetworkOnMainThreadException is thrown when an application attempts to perform a networking operation on its main thread. This is only thrown for applications targeting the Honeycomb SDK or higher. Applications targeting earlier SDK versions are allowed to do networking on their main event loop threads, but it’s heavily discouraged. Some examples of other operations that JellyBean, ICS and HoneyComb and won’t allow you to perform on the UI thread are:

    1. Opening a Socket connection (i.e. new Socket()).
    2. HTTP requests (i.e. HTTPClient and HTTPUrlConnection).
    3. Attempting to connect to a remote MySQL database.
    4. Downloading a file (i.e. Downloader.downloadFile()).

    If you are attempting to perform any of these operations on the UI thread, you must wrap them in a worker thread. The easiest way to do this is to use of an AsyncTask, which allows you to perform asynchronous work on your user interface. An AsyncTask will perform the blocking operations in a worker thread and will publish the results on the UI thread, without requiring you to handle threads and/or handlers yourself.