sscanf 和正则表达式
有一篇很好的入门的帖子:sscanf 函数和正则表达式
“%
表示选择,%*
表示过滤”,这是一个很精辟的定义。建议先看上面的链接!当然如果要在项目中正确的使用 sscanf
做字符串的筛选、提取,单单看上面一篇帖子是远远不够的。以下:
sscanf()
- 从一个字符串中读进与指定格式相符的数据
不值得读
以前写书的人都是高手。写专业书籍首先要求技术够牛,其次文笔也要可以。进入新世纪,什么杂七杂八、牛鬼蛇神都可以出版书了,市场上充斥着大量的垃圾,浪费纸张浪费木材不说,万一被市场、群众(被大众所选择的不一定是对的)欺骗买了这样的书你真是想死的心都有。也有一些书,处在尴尬的层次——(尤其是专业书籍)算不上佳作,匆匆一瞥也挑不出问题,可是当你真正深入阅读时慢慢发现细节惨不忍睹(尤其是计算机领域渣渣们为了名利职称等翻译国外著作)。
别看《C++ Primer Plus》
这本书是带“光环”的。在国内一些博客网站上,程序员们对这本书的评价还是可以的,豆瓣的评分也蛮高的。所谓群众的选择!但我接触过之后,开始怀疑“给予这本书肯定评价的程序员难道都是渣渣吗?或者都是门外汉拿这本书入门,觉得学到东西了所以给予肯定?”。以下是以前的记录,可以得出结论:原作者的水平就值得怀疑,翻译之后不会比原作更好。英文原著、译文都不值得看!
2016/2/17 9:52:41
因为网上关于《C++ Primer》的电纸书资源有限,好吧,只找到英文原版第五版的mobi格式,而恰巧搜索资源的时候找到了《Plus》第五版的中文资源,亚马逊官方的,排版也不错,所以就在kindle 上看了,当时是打算重点学习后面的章节:函数重载、类的设计与使用、继承、异常处理等。
到目前读了20%,因为前四章的内容都比较基础,所以并没有详细地一页页读过来,而是拣选不熟的知识点学习。但已经有点烦了,确实好些地方娓娓道来,让人豁然开朗(跟我水平低也有关),但更多的细节上的不注意让人慢慢地烦躁起来,从网上找到两篇评论,恰巧说到了我的感觉。无论如何,打算放弃了,而且我绝对不会推荐别人读这本书的。
小错误太多,很不负责任,66/71 认为此评论有用。这足够说明问题了。
还看到一个犀利的评价:
甚至会怀疑,那些让我豁然开朗,觉得学到东西的文字叙述是否真的就是那么回事,作者没有诓我吧。
最后一根稻草,是指针这一块的。
4.7.1节 讲到声明和初始化指针,提到声明指针时,*
操作符两边的空格问题,本身说的就不清楚,再加上难以自圆其说 指针声明中 *
和 解引用 *
的区别,甚至在作者的描述中两者是一致的。
我完全被打败了,弄糊涂了。所以去翻《C++ Primer》,很清晰,一点就明。
更多的错误就不说了,算是跳读的,虽说到了20%的进度,但就实际阅读的可能不到5%,我标注了8个明显的上下文不一致错误、翻译错误,让人费解的地方。
所以,打算去啃《Primer》英文原版了。
别看《C++ Concurrency in Action》译作
原著是值得肯定的,是 C++ 领域少有的介绍并发编程、多线程的书。但 国内翻译的版本 被喷的一无是处,不再多说。
另外,GitBook 上有一份陈晓伟翻译的 《C++并发编程(中文版)》,我起初抱着很大的期望去读的。目前(2016/7/14 10:07:18 ) 快读到第二章结尾了,发现其中也有好多不尽人意之处,甚至有些完全翻译错误,将读者带跑带偏。某句话末尾多出个词汇,文字输入时音正字错(“人为”写成“认为”)的现象更是频繁出现。读起来磕磕绊绊,最主要的还是担心译者水平,翻译错误——很明显的错误能够发现,然后去找原文矫正;可是意识不到就代表没有错误了吗?——肯定有,译者的英文水平其实挺一般的。
所以,打算去看英文原版了。好在作者用的都是日常词汇,读起来并不“卡”,只是会慢些而已。
追评:原著写得挺好的,真正地去读英文原版会发现也没想象中那么困难,已经向第 4 章迈进了。目前阅读进度缓慢甚至停滞,主要是因为阅读目的(并发入门)已经达到,后面的进阶内容在目前用不到,所以读书的动力就不大了。
静态绑定 & 动态绑定
从理解静态绑定 & 动态绑定开始,没想到又扯出静态类型 & 动态类型,编译期多态 & 运行期多态。但这些本来就是一体的。
静态绑定 & 动态绑定
刚开始只是看完网上的几篇帖子啰嗦几句心得,落笔时什么都没参考。重新整理笔记时硬是把以前保存在 chrome 书签中帖子给塞进来,也是够难受的。C++中的静态绑定和动态绑定
- 静态绑定(statically bound),又名前期绑定(early binding);
- 动态绑定(dynamically bound),又名延期绑定(late binding)。
ps 英文名称摘自《Effective C++》 条款37。此条款中有关于“静态类型、动态类型”的描述。
在 C 语言中并没有“静态绑定”、“动态绑定”的概念(至少我没有查到)。
字符(串)之间的转换
字符串之间的转换分为两类:
- 底层字符集之间的转换,表现为
char
wchar_t
之间的转换,string
wstring
之间的转换; - 在此之上牵扯到 Windows 平台下的各种字符串之间的转换;去伪存真,归根到底还是第一种转换。
为了“知其然,也要知其所以然”,我们在描述相互转换之前,先介绍一些相关的类型。上层所有的字符串类型归根到底都是对底层 char
或者 wchar_t
类型的封装,如果真有刨根问底的兴趣,就需要了解 char
wchar_t
的关联和区别。我们比较熟悉 char
,接下来看一下 wchar_t
。
C、C++ 和 VC++ 中的字符串
我们先从使用的角度,从最直观的数据类型说起。
ps 这一篇笔记并不显式地涉及 wchar_t
,也不包含字符(串)之间如何转换。更多内容请跳转 字符(串)之间的转换
类型:char[]\char*\string\CString
转帖来源:C++中的字符串
C++支持两种字符串:C风格字符串和string。之所以抛弃char*的字符串而选用C++标准程序库中的string类,是因为它和前者比较起来,不必担心内存是否足够、字符串长度等等,而且作为一个类出现,它集成的操作函数足以完成我们大多数情况下(甚至是100%)的需要。我们可以用 =
进行赋值操作,==
进行比较,+
做串联(是不是很简单:-D)。我们尽可以把它看成是C++的基本数据类型。此外,CString类在MFC中广泛应用,简化了字符串的处理。
进一步理解 extern
首先,要区分全局变量和局部变量。
全局变量和局部变量
在 C 和 C++ 中,局部变量意义相同;全局变量略有出入。
在《C++ Primer》中很明确的表示:(p44)
名字
main
定义于所有花括号之外,它和其他大多数定义在函数体之外的名字一样拥有全局作用域。一旦声明之后,全局作用域内的名字在整个程序的范围内都可使用。
在 C语言中文网-C语言局部变量和全局变量 中指出:
在所有函数外部定义的变量称为全局变量(Global Variable),它的作用域是整个源程序。
《嗨翻C语言》(上)
2016/2/23 14:41:06
《嗨翻C语言》,本书分为三个部分。
本书分为三个部分:第 1 章到第 4 章是基础知识,包括基本语法、指针、字符串、小工具和源文件;第 5 章到第 8 章为进阶内容,有结构、联合、数据结构、堆、函数指针、动/静态链接;最后四章是高级主题,内容涵盖了系统调用、进程间通信、网络编程和多线程。(加粗部分的笔记在下篇)
以下笔记没有严格按照章节进行整理,因为书中在多个章节可能提到同一类知识点,比如(略)
说明
在此笔记中,不记录不同 C 标准的差异,具体细节查看原书;不记录不同 OS 下编写 C 时的差异,具体细节查看原书或者 google。笔记中只对不同标准、不同 OS 下共性的语言原理、语法细节做出整理。
此上篇整理了 1-190 项标注和笔记。
课外
三种C标准
ANSI C 始于 20 世纪 80 年代后期,适用于最古老的代码;1999 年开始的 C99 标准有了很大的改进;在 2011 年发布的最新标准 C11 中……想知道编译器支持哪种标准,可以查看编译器的文档。
“指定初始化器”按名设置结构和联合字段,它属于 C99 标准;
C99 标准支持“指定初始化器”,C++ 不支持。
在代码中间的位置声明新变量,只有 C99 和 C11 标准才允许这样做,在 ANSI C(在此特指 C89)中,必须在函数的顶部声明局部变量;
更多的区别:1. 在早期的 ANSI C 标准中,main()
函数可以是 void
类型。但是在 C99 中 main
函数的返回类型必须是 int
。2. ANSI C标准没有用来表示真和假的值,C语言把0这个值当做假处理,把非0的任何值当做真处理。C99标准则允许在程序中使用true和false关键字。但编译器还是会把他们当做1和0这两个值来处理。
扩展阅读:
gcc
gcc 是 *inux 下的一款编译器,上述三种标准它都支持。
ps 这种说法太笼统,请阅读 GCC对C标准的支持、GCC的默认C标准、哪个版本的gcc才支持c11、有哪些支持C11标准的编译器)
- 在不指定C标准的情况下,GCC默认使用GNU C,所以如果你想让编译器遵循C99标准,需要使用 -std=99 选项。
- gcc是到了4.7,才真正支持c11的。
windows下使用gcc
虽然在本书中作者提供了以下两种方法,但是在后续的介绍中也多有提及其不足之处。而且在接触之后,我还是放弃了 Cygwin,转而在虚拟机上安装了 debian。
如在Windows操作系统上使用gcc(GNU编译器套装),有两种选择:一种是 Cygwin,它可以完全模拟UNIX环境,自然也就包括了gcc;如果你只是想创建能够在Windows下运行的程序,MinGW 可能更符合你的需要。
摘两条在书中描述的 Cygwin 项目不完美的地方:
一些Cygwin的gcc版本允许修改字符串字面值,不会报错,但这样做常常是错的。
Cygwin的很多版本中,在多个信号的发送顺序和接收顺序问题上,做了不恰当的假设。
其他
面向对象是一种对抗软件复杂性的技术。
基础知识
表达式
在C语言中,几乎每样东西都有返回值:表达式
x = 4
本身也有一个值,这个值是4,即赋给x的值。Q1:这个赋值表达式能否进行再次赋值?例如
(x=3)=4
A1:在C中编译无法通过,类似
3=4
;在C++中正常编译,运行,类似x=4
。注意:以下这种再次赋值是成立的。因为无论
y=4
(C语言) 还是y=x
(C++) 都是成立的。1
2
3
4
5
6
7
8
9
10
11int main()
{
int x = 9;
int y = 8;
// 方式一
y = x = 4;
// or 两者意义相同
// y = (x = 4);
printf("x is %d, y is %d\n", x, y);
return 0;
}关键是理解 C/C++ 中左值的概念,而且 C 和 C++ 中是不一样的。
使用
switch
语句的好处之一是,可以用下落逻辑在不同的分支之间复用代码。
存储器
如果真的想玩转C语言,就需要理解C语言如何操纵存储器。掌握指针和存储器寻址对成为一名地道的C程序员很重要。
函数(例如 main() 函数)中声明变量,计算机会把它保存在一个叫栈(Stack)的存储器区段中;函数以外的地方声明变量,计算机则会把它保存在存储器的全局量段(Globals);堆用于动态存储。
存储器的分布图:参考位置#1022、位置#1322、位置#1448
- 存储器是进程的
sizeof
是运算符,好比+
、&
,它不是库函数。程序是在编译期间计算sizeof
的。sizeof
运算告知某样东西在存储器中占多少字节,既可以对数据类型使用,也可以对某条数据使用。
指针
- 指针就是存储器中某条数据的地址
- 指针做了两件事:避免副本和共享数据。
- 指针是一种间接形式的地址(怎么理解?)
*
运算符可以读取存储器地址中的内容。*
运算符还可以设置存储器地址中的内容。
字符串
指向字符串字面值(string literal)的指针变量不能用来修改字符串的内容
在存储器的非只读区域创建了字符串的副本,就可以修改它的字母
如果你想把指针设成字符串字面值,必须确保使用了
const
关键字:1
2
3// bus error 运行时崩溃
char *card = "JQK";
card[1] = 'A';编译错误,比在运行时崩溃好太多了!
1
2
3// 编译不通过
const char *s = "some string";
s[0] = 'S';加不加
const
,字符串字面值都是只读的,const
修饰符表示,一旦你试图用const
修饰过的变量去修改数组,编译器就会报错1
2
3
4// 通过jimmy 修改内容就会报编译错误,
// 但是通过masked_raider 修改就可以成功。
char masked_raider[] = "Alive";
const char *jimmy = masked_raider;
数组
数组的索引值是一个偏移量
数组变量好比指针……
1
char quote[] = "Cookies make you fat";
计算机为字符串的每一个字符以及结束字符
\0
在栈上分配空间,并把首字符的地址和 quote 变量关联起来。函数传参时传给函数的是指针。数组变量与指针又不完全相同(区别:重点理解2、3点)
sizeof(数组)
是……数组的大小;sizeof(指针)
返回4或8。数组的地址……是数组的地址;指针的地址是另一个地址。
1
2&s == s; // 数组变量
&t != t; // 指针数组变量不能指向其他地方。
计算机会为数组分配存储空间,但不会为数组变量分配任何空间。
指针退化
把数组赋给指针变量,指针变量只会包含数组的地址信息,而对数组的长度一无所知,相当于指针丢失了一些信息。我们把这种信息的丢失称为退化。
结构(结构体)
结构可以像数组那样在结构中保存字段,但读取时只能按名访问。
Q1:数组变量就是一个指向数组的指针,那么结构变量是一个指向结构的指针吗?
A1:不是,结构变量是结构本身的名字。 数组变量的地址是数组变量自身;结构变量的地址是指针,不是自身。
为结构变量赋值相当于叫计算机复制数据。
重点在函数传参时很可能浪费较多存储资源;而数组作为函数参数传的是指针。
用
typedef
为结构创建别名。用typedef
定义结构时可以省略结构名,只写类型名。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18// 定义结构体
struct call_phone {
int cell_no;
const char *wallpaper;
float minutes_of_charge;
};
// 创建别名
typedef struct call_phone {
int cell_no;
const char *wallpaper;
float minutes_of_charge;
} phone;
// 省略结构名
typedef struct {
int cell_no;
const char *wallpaper;
float minutes_of_charge;
} phone;关于“对齐”,可以查看原文或者进一步从网上学习
在C语言中,参数按值传递给函数。
作者要表达的就是字面意思!@但这句话在国内出版物中经常作为前半句出现—“值传参和“指针传参”,以至于之前理解都有偏差。其实“指针传参”还是按值传递给函数的。
联合
定义一种叫“量”的数据类型,然后根据特定的数据决定要保存个数、重量还是容积。
每次创建结构实例,计算机都会在存储器中相继摆放字段,联合则不同。当定义联合时,计算机以其中最大的字段分配空间,然后由你决定里面保存什么值。
1
2
3
4
5typedef union {
short count;
float weight;
float volume;
} quantity;指定初始化器(designated initializer)按名设置结构和联合字段,它属于C99标准
1
2quantity q = {.weight=1.5};
phone p= {.cell_no=15210, .minutes_of_charge=1.2};联合提供了一种让你创建支持不同数据类型的变量的方法。
联合经常和结构一起用。创建联合相当于创建新的数据类型。
编译器不会记录你在联合中设置或读取过哪些字段。
Q1:可以在
union
中保存任何字段(count
、weight
或者volume
)的值,这些不同类型的值保存在存储器中相同的位置……既然如此,你怎么知道我保存的是float
还是short
?要是我保存了float
字段,却读取了short
字段呢?A1:解决方法:只要用枚举或其他东西记录一下就行了。
枚举
(略)
位字段
位字段(bitfield)的初衷是节省存储器空间:真/假的值只需要一位就能表示;月份等小范围的数字……
只有当多个位字段出现在同一个结构中,才能节省空间。
如果编译器发现结构中只有一个位字段,还是会把它填充成一个字,这就是为什么位字段总是组合在一起。
可以用位字段指定一个字段有多少位。位字段应当声明为
unsigned int
1
2
3
4
5
6
7
8
9
10
11
12typedef struct {
// 是否第一次参观?
unsigned int first_visit:1;
// 还会再来吗?
unsigned int come_agin:1;
// 被咬掉了几根手指?
unsigned int fingers_lost:4;
// 被鲨鱼袭击过吗?
unsigned int shark_attack:1;
// 一周来几天?
unsigned int days_a_week:3;
} survey;
Q1:为什么C语言不支持二进制字面值?
A1:因为二进制字面值占了很大空间(质疑?),而且十六进制通常写起来更快。
递归
为了保存可变数量的数据,需要一样比数组更灵活的东西,即链表。
链表是一种抽象数据结构。链表是通用的,可以用来保存很多不同类型的数据,所以被称之为抽象数据结构。
与数组相比,链表还有一个优点:插入数据非常快。
Q1:如何在C语言中创建链表?
A1:通过创建递归结构实现。
如果一个结构包含一个链向同种结构的链接,那么这个结构就被称为递归结构。
在递归结构中,需要包含一个相同类型的指针,C语言的语法不允许用typedef别名来声明它。个人猜测,应该和声明顺序有关。毕竟如果
typedef 新类型 newtype
,然后newtype * pointer
创建指针,是可行的。1
2
3
4
5
6
7// 在递归结构中,必须为结构体命名。别名可选
typedef struct island {
char *name;
char *opens;
char *closes;
struct island *next; // 此处不允许使用别名来声明
} island;C语言需要知道结构在存储器中占的具体大小,如果在结构中递归地复制它自己,那么两条数据就会不一样大。指针的大小是确定的。
在C语言中,
NULL
的值实际上为0
分而治之
小工具
问:什么是小工具?——其实就是其字面意思,重点是要有这个概念。
操作系统都有小工具。类Unix的操作系统为完成工作会大量使用工具,Windows用的少一些。
C语言小工具执行特定的小任务,例如读写文件、过滤数据。如果要完成更复杂的任务,可以把多个工具链接在一起。
小工具是一个C程序,它做一件事情并把它做好。
当你想解决一个大问题时,可以把它分解成一连串的小问题,然后针对每个小问题写一个小工具。
问:为什么小工具要使用标准输入和标准输出?
答:有了它们,就可以轻易用管道将小工具们串连起来。
问:如果两个程序用管道相连,第二个程序要不要等第一个程序执行完后才能开始运行?
答:不需要,两个程序可以同时运行,第一个程序一发出数据,第二个程序马上就可以处理。
输入输出
使用
scanf()
时要小心:限制scanf()
能读入的字符数1
2
3
4
5
6
7
8
9// 注意字符数组的长度,和scanf 读取的限制
int main()
{
char card_name[3];
puts("输入牌名:");
scanf("%2s", card_name);
// other code
return 0;
}如果忘了限制
scanf()
读取字符串的长度,用户就可以输入远远超出程序空间的数据,多余的数据会写到计算机还没有分配好的存储器中如果你要向
fgets()
函数传递数组变量,就用sizeof
;如果只是传指针,就应该输入你想要的长度。操作系统控制数据如何进出标准输入、标准输出。
scanf()
和printf()
函数,只管从标准输入读数据,向标准输出写数据进程有一只耳朵(标准输入)和两张嘴(标准输出和标准错误)
printf()
其实只是fprintf()
函数的特例,可以用
>
重定向标准输出,2>
重定向标准错误。用管道连接输入与输出
创建自己的数据流
程序运行时,操作系统会为它创建三条数据流:标准输入、标准输出和标准错误。但有时你需要创建自己的数据流。
每条数据流用一个指向文件的指针来表示,可以用
fopen()
函数创建新数据流。创建数据流后,可以用
fprintf()
函数往数据流中打印数据;可以用fscanf()
函数从数据流中读取数据。当用完数据流,别忘了使用
fclose()
函数关闭它。虽然所有的数据流在程序结束后都会自动关闭,但你仍应该自己关闭它们:
命令行参数
十个程序有九个需要选项。聊天程序有“系统设置”,游戏有调整难度的选项,而命令行工具需要有命令行选项。
很多程序都会用到命令行选项,因此有一个专门的库函数,可以使用它来简化处理流程。这个库函数叫做
getopt()
,每一次调用都会返回命令行中下一个参数。有关函数的具体使用方法,翻阅原书或者 google 都行。
unistd.h头文件不属于C标准库,而是POSIX库中的一员。POSIX的目标是创建一套能够在所有主流操作系统上使用的函数。
使用多个源文件
当编译器看到尖括号,就会到标准库代码所在目录查找头文件;用引号把文件名括起来,编译器就会在本地查找文件。
C语言是一种很小的语言,共有32+n个保留字。以下列出部分:
auto register static extern typedef union volatile
使用头文件
(函数)声明与定义分离
将声明和定义分离之后也不用再严格调整函数定义之间的顺序。
把声明放到一个独立的头文件中有两大优点,第一是主代码变短了,第二可以共享代码(被两个以上源文件 include 时)。
如此,就可以在不同的文件之间共享函数了,但如果你想共享变量呢?
为了防止两个源文件中的同名变量相互干扰,变量的作用域仅限于某个文件内。如果你想共享变量,就应该在头文件中声明,并在变量名前加上
extern
关键字(需要进一步的学习):ps:原文表述不太恰当。改为:
- 为了防止两个源文件中的同名变量相互干扰,变量的作用域仅限于某个文件内——(这句话存在异议,使用 gcc 实际编译时不同文件使用同名变量会报错“重复定义”)。
- 如果你想共享变量,就应该在头文件中声明。——声明变量,即在变量名前使用
extern
关键字,不应该用“并”。 - 关于
extern
,见《进一步学习extern》笔记。
动态存储
- ——另起篇幅
在C++类中创建线程函数
在这篇学习总结中,我们先给出在C++类中定义线程函数的方法,然后讲述在多线程下很容易发生的资源竞争的特例——析构竞态。
在C++类中定义线程函数
在多线程的开发中,一般都是把线程函数写成全局函数来使用。但是如果要把线程操作写成类,线程函数放在类里面呢?
下意识地会把线程函数写作普通的类函数,但这样子是有问题的。因为在创建线程的 api 中传入的线程函数需要在编译时确定地址,如果是普通的类函数,编译时不能确定地址,需要创建类的对象才能获取。我们可以把线程的执行函数写成 static 函数,或者是全局函数,因为这两者的函数地址在编译时是确定的。
两者的区别:static 在形式上能够体现“包装”性,能够保全类的封装性;全局函数貌似通过命名空间也能提供“包装性”呢,破坏类的封装性。实际业务中,我个人更倾向于前者——使用类的静态函数作为线程入口函数。
C++中int型和std::string互相转换
每次用到时,都要到网上搜索一下的感觉很不好。尤其是经常用到,每次搜索时你完全能认识到你已经查过很多遍了。另外,和不自信(拿不准的心理)以及养成了这种坏习惯都有关系,查得多了自然知道调用 C 标准库 atoi(itoa 不是 C 标准库函数)以及使用 stringstream 流来解决问题,但每每觉得差那么一点意思,不够简洁。每次用到时都要搜索一下,可能是希望找到一种让内心舒坦的转换“手法”吧。
参考 C++11 中的 string - atoi/itoa,岂止是参考,根本就是抄袭。可是好不喜欢原文的排版。