沉淀后的符文会印在这儿,慢慢地,它们会亮起来...
post @ 2019-11-25

谈谈C语言中的悬挂式else

C语言并不像python这类语言有缩进的限制,所以存在悬挂式else

什么是悬挂式else

我们先看下例子。

首先说明,下面例子中的代码,都是能够在笔者主机平台(x86)使用gcc编译通过,并且每部分都可以正常运行的。

为了说明并聚焦问题,并没有使用宏来简化代码。

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
50
51
52
53
54
55
56
57
58
59
#include <stdio.h>
#include <string.h>

static void test_entry_01(int a, int b){
if (a)
if (b)
printf("%s> b if\n", __func__);
else
printf("%s> b else\n", __func__);
else
printf("%s> who am I ? # a if ??? yes\n", __func__);
}

static void test_entry_02(int a, int b){
if (a)
if (b)
printf("%s> b if\n", __func__);
else
{
printf("%s> a else ? no I'm b else.", __func__); // no new line
printf("more info...\n");
}
else
printf("%s> who am I ? # I'm a else.\n", __func__);
}

static void test_entry_03(int a, int b, int c){
if (a)
printf("%s> nothing to be done...\n", __func__);
if (b)
printf("%s> b if, but not nest in a.\n", __func__);
else
printf("%s> b else ? yes\n", __func__);
if (c)
printf("%s> who am I? I don't belong to any of the control blocks above.\n", __func__);
else
printf("%s> who am I? I'm c else.\n", __func__);
}

int main( int argc, char **argv){

int a, b, c;
if (argc != 4)
{
printf("Usage: <APPNAME> a b c\n");
return 0;
}

a = strcmp(argv[1], "1") == 0 ? 1: 0;
b = strcmp(argv[2], "1") == 0 ? 1: 0;
c = strcmp(argv[3], "1") == 0 ? 1: 0;
printf("a=%d, b=%d, c=%d\n", a,b,c);

test_entry_01(a,b);
test_entry_02(a,b);
test_entry_03(a,b,c);

return 0;
}

编译并查看输出:

1
2
3
4
5
6
7
8
9
10
11
$ gcc -o test ./test_else.c
$ ./test
> Usage: <APPNAME> a b c
$ ./test 1 1 1
>
a=1, b=1, c=1
test_entry_01> b if
test_entry_02> b if
test_entry_03> nothing to be done...
test_entry_03> b if
test_entry_03> who am I? I don't belong to any of the control blocks above.

这里并没有列出所有的情况,感兴趣的小伙伴可以自己编译试试。

其实源码中,printf包含的字符串已经说明了所有相关的可能。

小结:

  1. 悬挂式else的语法并不具有很好的可读性,请在使用C语言的时候加上控制块{}
  2. 添加控制块{}后,可以避免你认为的这种歧义性
  3. if-else在不添加控制块符号{}的时候,表示后面很简短,简短到只能允许一条指令
  4. 请在书写代码的时候注意缩进,虽然C语言很自由,但请体谅有可能阅读你代码的其他人。

如何写出好看的C语言if-else控制流

当你阅读比较好的C项目代码的时候,你可能会感慨为何人家的代码写的这么好,一看就懂,或者控制流程很是流畅?

这个时候你其实应该停下来,等等你的灵魂,仔细花时间琢磨下为什么他们写的好。

我写一个例子,看看为什么这样写

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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
#include <stdio.h>
#include <string.h>

#define TRUE 1
#define FALSE 0


int do_it_01(int cond){
if (cond != 1)
{
printf("%s> normal branch\n", __func__);
return TRUE;
}
else
{
printf("%s> exception branch\n", __func__);
return FALSE;
}
}

int do_it_02(int cond){
if (cond != 2)
{
printf("%s> normal branch\n", __func__);
return TRUE;
}
else
{
printf("%s> exception branch\n", __func__);
return FALSE;
}
}

int do_it_03(int cond){
if (cond != 3)
{
printf("%s> normal branch\n", __func__);
return TRUE;
}
else
{
printf("%s> exception branch\n", __func__);
return FALSE;
}
}

int do_it_04(int cond){
if (cond != 4)
{
printf("%s> normal branch\n", __func__);
return TRUE;
}
else
{
printf("%s> exception branch\n", __func__);
return FALSE;
}
}

int deal_exception(){
printf("deal exception...\n");
return TRUE;
}

int main (int argc, char ** argv){
int cond;
static int exception = 1;

if (argc != 3){
printf("Usage: <appname> cond exception\n");
return 0;
}

// simple implement, just for test.
cond = *(argv[1]) - '0';
exception = *(argv[2]) - '0';
printf("cond: %d, exception: %d\n", cond,exception);


if (!do_it_01(cond))
{
printf("do_it_01 err ? do some thing.\n");
return -1;
}
else if (!do_it_02(cond))
{
printf("do_it_02 err ? do some thing.\n");
return -2;
}
else if (!do_it_03(cond))
{
printf("do_it_03 err ? do some thing.\n");
return -3;
}
else if (!do_it_04(cond))
{
printf("do_it_04 err ? do some thing.\n");
return -4;
}
else
{
if (exception){
deal_exception();
return -5;
}
else
{
printf("default..., do some process..\n");
}
}

return 0;
}

执行与输出结果,同学可以自行尝试,本文代码可以正常编译运行

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
$ ./test 5 0
>
cond: 5, exception: 0
do_it_01> normal branch
do_it_02> normal branch
do_it_03> normal branch
do_it_04> normal branch
default..., do some process..
$ ./test 5 1
>
cond: 5, exception: 1
do_it_01> normal branch
do_it_02> normal branch
do_it_03> normal branch
do_it_04> normal branch
deal exception...
$ ./test 3 0
>
cond: 3, exception: 0
do_it_01> normal branch
do_it_02> normal branch
do_it_03> exception branch
do_it_03 err ? do some thing.

这里没有列出所有的输出结果,不过以从源码上,已经能够说明问题了。

这样写的好处有哪些:

  1. 易读,流程一条线,异常很明显,都是在判断失败后处理错误逻辑
  2. 函数设计结构统一,返回值是精心设定的
  3. 好看的输出格式

设计要点:

  1. 前文提到,函数的返回值需要精心设计,当然,如果返回的TRUE、FALSE不能满足,其实其他的值也是可以的,只不过要多加判断
  2. 书写逻辑一条线,异常都在判断if失败的时候处理
Read More
post @ 2019-11-25

说说C语言中的小事

本文记录C语言中了一些你比较疑惑,或者奇怪的问题。

printf(“%s\n”);会输出什么?

这看起来很奇怪不是么?
依你来看这就是一个错误的语法呀?

没错,但也错了,对于GCC编译器来说,这是一个warning。

我们看个例子:

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>

int main(int argc, char ** argv){
char * test_str = "Hi Bro";
printf("%s\n");

printf("%s\n", test_str);
printf("%s\n");

return 0;
}

输出:

1
2
3
乱码或者其他不可读的内容
Hi Bro
Hi Bro

这个例子需要看printf的实现源码才能知道为什么会这样。

TBD…

你考虑过C语言符号的可见性么?

符号的可见性,比如这样

1
struct list_head students = { &students, &students };

关于符号students,为什么可以还没有定义完成就可以被使用?

其实这可以从编译原理的角度分析:
由于students是编译时确定的,不论他是global还是static,已经在image中分配了它的空间,那么它的地址也就确定了,所以我们看到&students可以被使用。

关于结构体初始化和赋值的一些问题

初始化结构体操作,像这样:

1
2
3
4
struct list_head students = {
.next = &students,
.prev = &students,
};

这没有问题,但像下面这样就会出现问题:

1
2
3
4
5
6
7
8
9
10
11
12
struct inner {
int id;
};

struct outter {
struct inner i;
};

{
struct outter * out = malloc(sizeof(struct outter));
out.i = {100};// 错误的
}

根本原因有两个:

  1. 一个是,结构体只能在初始化的时候赋值,并且不能是malloc分配方式,只能是编译时分配
  2. 另一个是,编译时确定的结构体内存,除了在初始化的时候可以像students那样使用,在之后的赋值动作都不可以这样,只能按照成员变量挨个赋值。

正确做法是:

1
2
3
4
{
struct outter * out = malloc(sizeof(struct outter));
out.i.id = 100;
}

你使用过#include “xxx.c”这样的宏么?

有时候在编译C语言的时候,你会遇到重复定义的问题,但我想说的并不是普通的重复定义问题。

我们知道.h和.c只不过是用来给编译器区分的文件类型,本质上都是源文件,只不过人为的把它分成:头文件和源文件

也就是说我们可以这样使用include

1
#include "demo.c" // 就像包含.h文件一样。

而如果碰到使用这个技巧的源文件的时候,你有故作聪明的在编译的时候加了源文件 demo.o (demo.c),
这个时候就会重复定义。

那么总结的就是,如果看到有.c文件,但是在编译的时候没有在编译源文件列表中添加,那么肯定是使用了本文说的这个技术。

看C源代码的时候,你有这样的思维定式么?

在C语言编程的时候,与大多数语言一样,你要在不断的层次转换中知道自己所处的位置,
不过我想说的是,很多同学当基础知识不够扎实的时候,就会不知道自己在哪里

在C语言中

1
dev->mii.dev = dev->net

这样简单的一句话说明:
符号’->’表示前面的操作数是指针,并且他结构中有mii成员,而成员mii是是么,需要后面的符号’.’来说明,
原来它是一个结构体内嵌成员,它包含dev成员,不过dev是指针还是内嵌成员并不知道。
在左值中’->’符号有同样的解释,不过这里之所以可以这样赋值,就说明前面的’mii.dev’中的dev是个指针,
而且’dev->net’也是一个指针。

Read More

Python 常见的几种处理路径问题

Python 虽然很方便,文档资料也是随处可见,本文内容也不是创新的内容,只是希望简单的总结一下编程中常见的处理路径的问题,希望对大家有所帮助。

如何获得当前文件的路径

一般这里指的路径分两种,一个是绝对路径,另一个是相对路径。

那么我们简单先介绍下几种路径的获得方法,最后看看例子。

__file__

这是python内置的变量,表示包含文件名称的路径,可以肯定的是,
这个变量中一定包含文件名称,至于路径因执行 python 的方法不同而不同。

os.getcwd()

这是os模块的函数,不是os.path。

它返回的是平台支持的文件路径,并不包含文件名称,而是路径的绝对值。

os.path.realpath(__file__)

返回指定文件名的规范路径,消除路径中遇到的任何符号链接,
一般大部分系统都支持它。

os.path.abspath(__file__)

返回路径名路径的标准化绝对化版本。在大多数平台上,这等效于按如下方式:
os.path.normpath(os.path.join(os.getcwd(), __file__)) 等价于 os.path.abspath(__file__)。

sys.path[0]

sys.argv[0]|获得模块所在的路径,一般由系统决定是否是全名。

前面介绍了5种方法,我们一起看看在不同条件下的输出情况:

文件内容:

1
2
3
4
5
6
7
8
9
10
#! /usr/bin/env python3
#filename: filename_test.py

import os,sys

print("filename:[__file__] >", __file__)
print("filename:[os.getcwd()] >", os.getcwd())
print("filename:[os.path.realpath(__file__)] >",os.path.realpath(__file__))
print("filename:[os.path.abspath(__file__)] >", os.path.abspath(__file__))
print("filename:[sys.path[0]] >", sys.path[0])

输出内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[cmd] > python filename_test.py
filename:[__file__] > filename_test.py
filename:[os.getcwd()] > /home/rex/C_LD/common_Makefile/scirpts
filename:[os.path.realpath(__file__)] > /home/rex/C_LD/common_Makefile/scirpts/filename_test.py
filename:[os.path.abspath(__file__)] > /home/rex/C_LD/common_Makefile/scirpts/filename_test.py
filename:[sys.path[0]] > /home/rex/C_LD/common_Makefile/scirpts

[cmd] > python ../scirpts/filename_test.py
filename:[__file__] > ../scirpts/filename_test.py
filename:[os.getcwd()] > /home/rex/C_LD/common_Makefile/scirpts
filename:[os.path.realpath(__file__)] > /home/rex/C_LD/common_Makefile/scirpts/filename_test.py
filename:[os.path.abspath(__file__)] > /home/rex/C_LD/common_Makefile/scirpts/filename_test.py
filename:[sys.path[0]] > /home/rex/C_LD/common_Makefile/scirpts

[cmd] > ./filename_test.py
filename:[__file__] > ./filename_test.py
filename:[os.getcwd()] > /home/rex/C_LD/common_Makefile/scirpts
filename:[os.path.realpath(__file__)] > /home/rex/C_LD/common_Makefile/scirpts/filename_test.py
filename:[os.path.abspath(__file__)] > /home/rex/C_LD/common_Makefile/scirpts/filename_test.py
filename:[sys.path[0]] > /home/rex/C_LD/common_Makefile/scirpts

一般获得当前模块或者你称之为脚本文件的绝对路径,使用:

1
os.path.dirname(os.path.realpath(__file__))

看到了说出应该差不多就明白了,不过具体的细节请查看官方文档
当然看看源码也是有好处的,如果你有时间的话。

其他常用的路径操作手法

获得文件名称

1
os.path.basename(__file__)

获得文件名称和绝对路径

1
os.path.split(os.path.realpath(__file__))

其他的路径裁剪,都可以使用本文提供的方法,
加上适当的os.path模块中的处理函数,来满足你的要求。

Read More

什么叫不同版本的python?

一般对于开发人员来说,工具的版本并不陌生。
python 大家或者熟悉或者听说过,用的人自然懂,这里也就不过多介绍了。
不过,对于linux的开发人员来说,安装不同版本的python轻而易举,经常和命令行打交道的他们可以分分钟完成不同版本的配置。
对于windows开发人员也不例外,他们一样熟悉着自己的工作环境,并且熟练的掌握着吃饭的家伙(偷笑)。

本文主要是给那些不太熟悉windows操作系统,且想要开发的用户,用以来解决一些在windows开发环境上的疑惑。

python发展历史我不想追究,随便使用个搜索引擎都能轻松了解到,这里只告诉您,python有两个重要的版本:

  1. python2 历史版本,至今还有很多大项目在用,所以还没有废弃,python社区还在做兼容性维护
  2. python3 革新版本,从python2跃迁而来,添加了很多新的特性,有些基础内容已经从根本上改变

在windows上安装不同版本python的方法

其实在windows上,安装不同版本python,笔者感觉主要有三种方法:

  1. 通过python 官网,下载不同版本的python,然后通过安装配置,最后做一些手脚就可以顺利完成配置。
  2. 通过 anaconda,配置你想要的版本,它自带版本管理系统,可以方便帮助隔离不同的python版本环境。
  3. 通过python第三方版本管理库,完成python版本的隔离控制

本文主要介绍在windows上使用前两种方法。

官网下载,安装自定义

下载python的不同版本

python 官网中有两个版本的下载地址,仔细找找很容以找到。
本文实例以图示版本为例,如果安装其他版本,你应该知道怎么找。

这里windows用户喜欢下载安装包,方便快捷的下载安装。

步骤如图:
step.1 找到官网下载地址

01

step.2选择你需要的安装包下载它
02

step.3双击安装,你懂的
03

定制化自己的python版本环境并安装

本人比较喜欢干净的工作环境,所以一般安装的python也是自己使用,并不影响其他开发者。
你需要对自己安装的版本及路径了如指掌,后续做一些修改的时候也方便一些。

以python3安装为例,python2雷同。
图示步骤如下:

step.1 选择工具自动添加环境

04

step.2 手动选择你想要安装的目录
05

step.3 安装进行中
06

安装过程中,可能会提示兼容以往的 DOS 最大路径字符长度限制,请 enable 。

安装完成后配置环境路径,如图:

07

为了使用命令行,修改以做兼容

修改python2安装路径下的执行文件为:python2.exe
修改python3安装路径下的执行文件为:python3.exe

修改执行入口程序的名字,操作类似如下:
08

当然,这里你可以复制原来的可执行文件然后修改名字也是可以的,不过python这命令在cmd中按照PATH配置顺序执行的,所以最好指定python版本。

这里有一个小技巧,如果你想要python命令,默认使用哪一个版本,那就保留该版本的两个可执行文件,比如:
对于python3来说,保留python.exe和python3.exe,但是python2中的可执行文件只保留python2.exe。
这样,我们得到了三个命令,你可以自由切换:

  1. python – 表示python3
  2. python3 – 表示python3
  3. python2 – 表示python2

重启命令行,操作验证:
09

不同版本的pip管理器的使用

正常来说,pip在笔者下载的这两个版本中都有默认支持了。
对于老版本的python2如果没有,可以简单的在搜索引擎上找到安装python2的pip的解决方法。

由于之前我们修改了python.exe以支持想要的版本区分,不过也使得对应版本的pip.exe无法使用了。

这里提供简单的操作方法:

C:\Users\imcat>python2 -m pip --version
pip 18.1 from C:\Python2.7\lib\site-packages\pip (python 2.7)

C:\Users\imcat>python3 -m pip --version
pip 19.0.3 from C:\python3.7\lib\site-packages\pip (python 3.7)

结果类似这样:
10

这样,你就可以使用pip安装你想要的库到指定版本路径中。

使用anaconda自带以的版本控制

anaconda 是一个比较有意思的开源项目,它对于python的版本控制非常到位,并且内置了很多有用的功能。

由于它的强大,省去了我们的配置时间,接下来简单看看如何使用 anaconda。

anaconda 的下载与安装

anaconda 的官网下载下来python3版本,其实我们只需要下载python3版本,也就是比较前卫的版本。
之所以这样,是因为conda管理器可以帮助我们自动下载python2的内容,而且也方便切换。

下载安装步骤如图:

step.1 找到官网下载地址并下载
11

step.2 双击安装
12

step.3 选择一个独立的路径
13

step.4 前面安装过默认的python,因为anaconda是独立的环境,所以去掉图示勾选
14

step.5安装过程有些枯燥,由于不是mini版本,需要一些时间,去喝点水吧
15

由于默认安装了一些应用,所以安装时间稍微长一些,当然你可以下载它的mini版本,这里不做赘述了。

安装完成后,验证方法只需查看开始菜单中的anaconda即可,如图:
16

如果你喜欢gui,那就启动gui,笔者一般喜欢使用命令行,所以使用shell接口就足够了,本文也主要介绍shell接口的相关配置,也就是anaconda prompt

运行效果如图:
17

conda 命令的简单使用

anaconda 的管理器接口是 conda 应用,从名字看就很容易理解。
这里列出一些常用的 conda 管理器的常用命令,如果想要更详细的内容,可以自行搜索官网文档。

以下命令都是在anaconda的shell中完成的,也就是anaconda prompt

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# 帮助:
conda -h
# 查看conda版本:
conda -V
# 查看虚拟环境列表:
conda env list 或 conda info -e 或 conda info --envs
# 创建python虚拟环境:
conda create -n your_env_name python=X.X(2.7、3.6等)
your_env_name >> 就是你的环境名字,识别号而已
python=X.X >> 就是你要安装的虚拟环境使用的真实环境版本
# 向指定虚拟环境中安装额外的库:
conda install -n your_env_name [package]
其实在虚拟环境中,通过正常的安装流程也是一样可以办到的,当然需要你切到虚拟环境下。
# 开启虚拟环境:
activate your_env_name
# 关闭虚拟环境:
deactivate
# 删除虚拟环境:
conda remove -n your_env_name(虚拟环境名称) --all
# 删除环境中的某个包:
conda remove --name your_env_name package_name

简单操作与验证

安装python2

conda create -n python27 python=2.7

查看安装好的虚拟环境

conda env list

切换到指定的虚拟环境

activate python27

验证版本

python --version

安装卸载包

python -m pip install openpyxl
python -m pip uninstall openpyxl

退出环境

deactive

简单、直接、粗暴,很符合有强迫症的人士。

两种方法的利与弊

其实就方法而言,并没有什么高低之分,只不过针对应用场景来说,有的方法稍微有点别手而已。

比如,笔者在工作中就遇到的情况,这里简单描述下:
项目同时使用python2和python3的内容,因为一早就有的工具,需要你去继承使用;
当然有些同学会说,自己可以将python2的项目修改成python3兼容的,不过这需要花费很多成本,
一般在项目中,除非逼不得已,一般都不会这么做,而是一起使用python3和python2,这个时候,
貌似纯粹的虚拟环境就有点劣势,所以配置干净的系统环境,也就是使用第一种方法,就成了符合当前策略的主要方案。

而一般一个项目的开发可能支持不同的python版本,设计之初,就可能考虑到不同版本的向后兼容性,
需要不停的切换版本验证,这种时候,虚拟环境的特长就显露出来了。

当然,这也是笔者个人所思,百家想法,各有观点,这里就不过多的讨论了。

好了,终于在windows上,不断的卸载和安装,完成了这篇文章。
^_^ 祝贺下(拍手)。

Read More
⬆︎TOP