C++作用域运算符

C++作用域运算符是C++运算符中等级最高的。::运算符的本质是:左操作数为域,是一个scope,右操作数是这个scope中的一个名字,它可以是一个scope、class、member、function或者variable等。

google style对域作用符的建议和说明可参考:

http://zh-google-styleguide.readthedocs.io/en/latest/google-cpp-styleguide/scoping/#namespaces

作用域运算符作用

::的作用主要包括以下三种:

  • 全局作用域符(::name)
  • 类作用域符(class::name)
  • 命名空间作用域符(namespace::name)

后两种比较常见,不做说明。全局作用符的作用是:如果程序中既定义了全局变量a,也定义了局部变量a,此时若要访问全局变量a,则需要使用::a来访问全局变量a。

当作为类作用符时可用来决议多继承中的重名成员,指定类成员及函数等。

当作为命名空间域作用符时可有效的防止全局作用域命名冲突。

Comment and share

类型转换操作符&&C++重载输出流运算符

在将一种类型转换为另一种类型的时候,直接使用强制类型转换往往不能做到无缝转换,C++提供了类型转换操作符将类转换为特定的类型。

类型转换操作符

使用实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Test{
public:
Test(int a, std::string c):_a(a), _c(c) {}
//类型转换操作符:将Test转换为int类型
operator int() const {return _a;}
//Test转换为string类型
operator std::string() const {return _c;}
private:
int _a;
std::string _c;
};
int main() {
Test t(1, "qwe");
std::cout << "t=" << t << std::endl; //t=1
std::cout << "t=" << std::string(t) << std::endl; //t=qwe
return 0;
}

分析

从上面可以看出,我们可以通过operator type()来进行类型转换。当同时定义两种类型转换的时候,默认会调用哪一种类型转换呢?
这个和C++输出流的实现有关。

C++重载输出流运算符

在实际运用中,我们通常有将自定义的一个类中的一些信息打印出来的需求,这个时候可以使用重载输出流运算符来实现。
而实现的当时有多种,最常见的是通过友元和全局函数重载输出流运算符

流输出运算符

在C++中类basic_ostream有成员函数operator<<(int),而没有成员函数operator << (const std::string),
故上面有限调用同名的成员函数,所以默认输出int,若同时定义了int和float的类型转换,这个时候就需要强制类型转换,否则会出现二义性。

使用友元方式重载流输出运算符

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Test{
public:
Test(int a, std::string c):_a(a), _c(c) {}
friend std::ostream& operator<< (std::ostream& os, const Test& t) {
os << t._a << " " << t._c << std::endl;
return os;
}
private:
int _a;
std::string _c;
};
int main() {
Test t(1, "qwe");
std::cout << t << std::endl;
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
24
25
#include <iostream>
#include <string>
#include <fstream>
class Test{
public:
Test(int a, std::string c):_a(a), _c(c) {}
public:
int _a;
std::string _c;
};
std::ostream& operator << (std::ostream& os, const Test& t) {
os << t._a << " " << t._c << std::endl;
return os;
}
int main() {
Test t(1, "qwe");
std::cout << t << std::endl;
std::cout << std::string(t) << std::endl;
std::cout << t << std::endl;
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
24
#include <iostream>
#include <string>
#include <fstream>
class Test{
public:
Test(int a, std::string c):_a(a), _c(c) {}
std::ostream& operator<< (std::ostream& os) {
os << _a << " " << _c << std::endl;
return os;
}
public:
int _a;
std::string _c;
};
int main() {
Test t(1, "qwe");
//注意此时为Test的成员函数,所以要通过成员函数的方法来调用
t.operator<<(std::cout) << std::endl;
return 0;
}

为什么重载+号运算符不需要使用类成员的方式调用,因为+号运算符的做操作数为当前类。

Comment and share

TCL脚本语言学习

TCL是一种脚本语言,它几乎可以在全部平台上运行,可移植行很强。
TCL语言设计的目的是提供程序与其他程序之间进行交互的功能,也是作为一个可嵌入的翻译互相作用的能力。
开发简单,上手快。

TCL输入输出

1
2
3
4
5
6
7
8
9
10
#!/usr/bin/tclsh
#默认输出到标准输出流
puts hello
puts stdout hello
#输出到标准错误流
puts stderr error
#输入必须指定输入流
gets stdin varible
puts $varible

TCL文件读写操作

1
2
3
4
5
6
7
8
9
#打开data文件,打开模式有r,w,a,r+,w+,a+
set fp [open data w]
puts $fp "hello world"
close $fp
set fp [open data r]
gets $fp test
puts $test
close $fp

TCL数据类型

TCL变量不需要声明,可以直接使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# TCL对象,对于整数,浮点数,bool,字符串,都是一个对象
#可直接给其赋值
set varible 1
set s "hello world"
# 列表,列表初始化时可以使用双引号或者大括号进行初始化
set list {hello world hah hah}
set list "hello world hah hah"
#访问列表元素,使用lindex
puts [lindex $list 0] #hello
#关联数组,类似map,key可以是数字,也可以是字符串
set map(a) 10
puts $map(a)

TCL条件控制语句

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
# if语句
if {expression1} {
dosomting
} elseif {expression2} {
dosometing
} else {
dosomething
}
#for 语句
for {set a 10} {$a < 20} {incr a} {
dosometing
}
#switch语句
switch switchingString {
matchString1 {
body1
}
matchString2 {
body2
}
matchStringn {
bodyn
}
}

运算符的优先级

分类 操作符 关联
Unary + - Right to left
Multiplicative * / % Left to right
Additive + - Left to right
Shift << >> Left to right
Relational < <= > >= Left to right
Equality == != Left to right
Bitwise AND & Left to right
Bitwise XOR ^ Left to right
Bitwise OR Left to right
Logical AND && Left to right
Logical OR || Left to right
Ternary ?: Right to left

Comment and share

Unix网络编程相关错误码和信号处理

特别注意当出现一下错误码时,处理之后一定要将errno复位为0

在网络编程的过程中会出现一些错误码,下面总结:

  • EAGAIN(11) : Resource temporarily unavailable

    • 错误原因:当将套接字设置为异步时,由于函数调用之后是立即返回的,所以会出现两种情况导致这个错误:(1):当调用read函数,此时没有数据可读,此时read函数会立即返回错误码EAGAIN表示此时无数据可读(2):当调用write函数,此时缓冲区满,write函数将会立即返回错误码EAGAIN。
    • 解决方法:EAGAIN错误表示此时无数据可读或者缓冲区已满,所以此时只需要重试即可。
  • ECONNRESET(104):Connection reset by peer

    • 错误原因:当对端socket已关闭,此时调用read或write函数将返回ECONNRESET错误,在之后如果继续调用read或write,就会得到该错误。常见的原因是发送端接收端实现约定好的数据长度不一致,若接收端被通知需要接收99个字节,而服务端发送了100个字节给接收端,这样一来,接收端接收99个字节就执行了close操作,如果发送端继续发送,接收端将向发送端返回一个RESET信号
  • EALREADY (114):Operation already in progress

    • 错误原因:套接字为非阻塞套接字,并且原来的链接请求还未完成
  • EINPROGRESS(115):Operation in progress

    • 错误原因:套接字为非阻塞套接字,连接正在建立

网络编程相关信号

  • SIGPIPE(13):管道破裂。管道另一端没有进程接收数据,导致管道破裂而崩溃。对一个对端已经关闭的socket进行两次write,第二次调用将会产生该信号,此信号的默认行为是结束进程
    • 解决方法:将该信号的处理函数设置为SIG_IGN,即忽略此信号

Comment and share

Unix网络函数与TCP状态转变之间的关系

connect函数导致状态转变

client状态变化:

connect函数导致当前client套接字从CLOSED(初始状态)转移到SYN_SENT状态,若成功则再转移到ESTIBLISHED状态,若失败,则回到CLOSED状态

server TCP状态变化:

当client发送SYN分节,server接收成功并返回SYN分节之后,server套接字将从LISTEN状态转移到SYN_RCVD状态,server发送SYN分节之后,client返回ACK到server,Server套接字状态从SYN_RCVD状态转变为ESTIBLISHED状态。

注意:若connect失败,必须调用close函数将当前socket关闭,不可再次调用connect函数。若需重试,则关闭后重新创建socket进行connect操作

listen函数导致状态转变

listen函数把一个未连接的套接字转换成一个被动套接字,调用listen导致套接字从CLOSED状态转变为LISTENED状态

Comment and share

netstat命令使用详解

netstat命令用于显示各种网络信息,如当前机器的网络连接状态,路由表,接口状态等等。

基本信息

执行netstat命令,其结果主要包括两个部分。

  • Active Internet connections (servers and established),称为有源TCP链接,包括TCP和UDP等的详细状态
  • Active UNIX domain sockets (servers and established)。称为有源Unix域套接口。

示例:

1
2
3
4
5
6
7
8
9
10
$netstat -a
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State
tcp 0 2 xx.xx.xx.xx:telnet ss.ss.ss.ss:port ESTABLISHED
...
Active UNIX domain sockets (servers and established)
Proto RefCnt Flags Type State I-Node Path
unix 2 [ ACC ] STREAM LISTENING 6474 /var/run/test.socket
...

有源TCP链接字段详解:

  • Proto : 当前链接的协议。如TCP,UDP
  • Recv-Q : 网络接收队列
  • Send-Q : 网络发送队列
  • Local Address : 本机的ip:端口(注意此处127.0.0.1默认显示主机名,0.0.0.0默认显示*,端口可能显示别名。若强制显示数字,加-n参数)
  • Foreign Address:对端IP:端口。与local address规则相同
  • State : 当前套接字的网络状态。

有源Unix域套接口字段详解

  • proto : 当前链接的协议,一般为Unix
  • RefCnt : 连接段本端口上的进程号
  • Type:套接字的类型,stream或 DGRAM
  • state : 当前套接字的状态
  • I-Node : 当前socket对应的inode号
  • Path : 连接到套接口的其它进程使用的路径名。

netstat常用参数

  • -a : 显示所有选项
  • -t : 显示所有与TCP相关的选项
  • -u : 显示所有与UDP相关的选项
  • -x : 显示所有与Unix域相关的套接字选项
  • -n : 拒绝显示别名,能显示数字的全部转换为数字显示
  • -p : 显示建立相关连接的程序名。
  • -l : 显示所有状态为Listen的连接
  • -e : 显示扩展信息,如当前链接所对应的用户
  • -c : 间隔一段时间执行一次netstat命令。
  • -s : 显示统计信息。对每种类型进行汇总

netstat常用组合

  • netstat -anp : 显示所有的网络连接,并拒绝显示别名,同时打印该连接对应的程序名
  • netstat -lt : 显示所有正在监听状态的tcp连接
  • netstat -lu : 显示所有正在监听状态的udp连接

Comment and share

Unix信号之sigaction函数

sigaction函数是除signal函数之外的另一个可以设置信号处理的函数。sigaction用法比signal函数复杂,但是可以对信号处理进行更加精准更灵活的控制。sigaction是POSIX的信号接口,而signal函数是标准C的信号接口,所以sigaction函数的可移植性更强。

sigaction函数说明

函数功能

检查或修改与指定信号相关联的处理动作,可以同时检查和修改。

函数原型

1
2
3
4
5
6
7
8
struct sigaction{
void (*sa_handler)(int); //信号处理函数地址
sigset_t sa_mask; //信号掩码集,当调用信号处理函数时,程序将阻塞sa_mask中的信号
int sa_flag; //位掩码,指定用于控制信号处理过程中的各种选项。
void (*sa_sigaction)(int,siginfo_t*,void*); //暂不用
};
int sigaction(int signo,const struct sigaction*restrict act,struct sigaction*restrict oact);

函数参数

  • signo : 指定操作的信号

  • act : 新修改的sigaction

  • oldact : 保存该函数原有的sigaction。

上述struct sigaction中sa_flag取值说明(常用的有以下两个):

  • SA_NODEFER:当信号处理函数正在进行时,不堵塞对于信号处理函数自身信号功能。
  • SA_RESETHAND:当用户注册的信号处理函数被执行过一次后,该信号的处理函数被设为系统默认的处理函数

函数返回值

  • 0 : 返回0表示设置成功
  • -1 : 返回-1表示设置失败

函数示例

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
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
void sighandler(int sig) {
printf("this is in the sig handler\n");
for (int i = 0; i < 5; i++) {
printf("num:%d\n", i);
}
}
int main() {
struct sigaction act;
struct sigaction oldact;
act.sa_handler = sighandler;
act.sa_flags = SA_NODEFER ;//| SA_RESETHAND;
sigaction(SIGINT , &act,&oldact);
printf ("this is the main function\n");
pid_t pid = getpid();
printf("%d", pid);
kill(pid, SIGINT);
}

Comment and share

判断主机字节序大端规则或小端规则方法

大端规则与小端规则

在计算机存储中存储字节的顺序有两种分别为大端规则和小端规则。

  • 小端规则(littel endian):低序字节存储到内存较低的位置,即起始位置。
  • 大端规则(big endian):低序字节存储到内存较高的位置,即高序字节存储到起始位置。

有一个32位数字为:0x01020304

小端规则的机器上,其存储如下:

低地址 -> -> 高地址
0x04 0x03 0x02 0x01

大端规则机器上,其存储如下:

低地址 -> -> 高地址
0x01 0x02 0x03 0x04

判断当前机器字节序的方法

判断当前机器为大端规则还是小端规则,其本质是对于一个变量,判断其各字节的存储顺序

方法一:使用union判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
using namespace std;
int main() {
union {
short a;
char c[2];
} u;
u.a = 0x0102;
if (u.c[0] == 2 && u.c[1] == 1) {
std::cout << "little" << std::endl;
} else if (u.c[0] == 1 && u.c[1] == 2) {
std::cout << "big" << std::endl;
} else {
std::cout << "unkown" << std::endl;
}
return 0;
}

方法二:直接将字节取出,判断顺序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
using namespace std;
int main() {
short s = 0x0102;
char* a = (char*)(&s);
std::cout << (*a) << std::endl;
char b = 0x02, c = 0x01;
if (((*a) | b) == b) {
std::cout << "little" << std::endl;
} else if (((*a) | c) == b) {
std::cout << "big" << std::endl;
} else {
std::cout << "unknow" << std::endl;
}
return 0;
}

Comment and share

pack与aligned的区别

GCC支持用__attribute__为变量、类型、函数以及标签指定特殊属性。其中可以使用attribute的aligned属性控制变量或类型的内存对其规则,之前一篇文章已经提到pack可以改变结构体中各成员之间的内存对其规则。https://langzi989.github.io/2017/10/02/C语言内存对其相关/

其中#pragma pack()和__attribute__((aligned))区别很大。

aligned内存对其详解

使用场景:

  • 变量
  • 类型

功能说明:

  • 当aligned作用于变量时,其作用是告诉编译器为变量分配内存的时候,要分配在指定对其的内存上.作用于变量之上不会改变变量的大小。
    • 例如:int a __attribute__((aligned(16)));该变量a的内存起始地址为16的倍数。
  • 当aligned作用于类型时,其作用是告诉编译器该类型声明的所有变量都要分配在指定对齐的内存上。当该属性作用于结构体声明时可能会改变结构体的大小。
1
2
3
4
5
6
7
8
struct Test{
char a[3];
}__attribute__((aligned(8)));
int main() {
//8
std::cout << sizeof(Test);
}

如上所示,当align作用于结构体定义时会改变结构体的大小。结构体最终大小为aligned指定大小的整数倍。

aligned与pack的区别

从上面可以看出,aligned和pack的主要区别如下:

  • pack作用于结构体或类的定义,而aligned既可以作用于结构体或类的定义,也可以作用于变量的声明。
  • pack的作用是改变结构体或类中成员变量的布局规则,而aligned只是建议编译器对指定变量或指定类型的变量分配内存时的规则。
  • pack可以压缩变量所占内存的空间
  • align可以指定变量在内存的对其规则,而pack不可以。
  • 若某一个结构体的默认pack为n,pack指定的对齐规则m大于n,则该pack忽略。若aligned指定的对齐规则s大于n,则此时结构体的大小一定为s的整数倍。
  • aligned和pack指定规则时都必须为2的n次幂。

参考链接:http://blog.shengbin.me/posts/gcc-attribute-aligned-and-packed

Comment and share

fcntl实现对文件加锁功能

之前有一篇文章详细介绍了fcntl的用法,这一节将说明使用fcntl实现对文件加锁的功能,

fcntl函数原型

fcntl函数如下,具体用法可参考上面的文章。

1
2
#include <fcntl.h>
int fcntl(int fd, int cmd, .../*int args or lock args*/);

使用fcntl对文件加锁

当fcntl中的cmd为F_GETLK,F_SETLK,F_SELFKW时为对文件进行锁操作,此时arg参数为flock。注意:使用fcntl对文件加锁,加锁效果类似于自旋锁,只有写写互斥和读写互斥,读读并不互斥。

cmd取值及其操作

  • F_GETLK : 获取当前锁得状态
  • F_SETLK : 给当前文件上锁(非阻塞)。
  • F_SETLKW : 给当前文件上锁(阻塞,若当前文件正在被锁住,该函数一直阻塞)。

flock结构体定义如下:

1
2
3
4
5
6
7
8
struct flock {
short int l_type;
short int l_whence;
off_t l_start;
off_t l_len;
pid_t l_pid;
};

下面对flock中的参数一一解释:

  • l_type:此参数表示所得类型。其可能的取值包括一下三个:
    • F_RDLCK : 读锁
    • F_WRLCK : 写锁
    • F_UNLCK : 无锁状态
  • l_start : 此参数锁区域的开始位置的偏移量
  • l_whence:此参数决定锁开始的位置。其可选参数为:
    • SEEK_SET:当前位置为文件的开头
    • SEEK_CUR:当前位置为文件指针的位置
    • SEEK_END:当前位置为文件末尾
  • l_len : 锁定文件的长度

若要锁定整个文件,通常的方法为将l_start设为0,l_whence设为SEEK_SET,l_len设为0.

实例

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
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
void lock_set(int fd, int type) {
struct flock lock;
lock.l_whence = SEEK_SET;
lock.l_start = 0;
lock.l_len = 0;
while (1) {
lock.l_type = type;
if ((fcntl(fd, F_SETLK, &lock)) == 0) {
if (lock.l_type == F_RDLCK)
printf("read lock set by %d\n", getpid());
else if(lock.l_type == F_WRLCK)
printf("write lock set by %d\n", getpid());
else if (lock.l_type == F_UNLCK)
printf("release lock by %d\n", getpid());
return;
}
//检查文件是否可以上锁
fcntl(fd, F_GETLK, &lock);
//判断不能上锁的原因
if (lock.l_type != F_UNLCK) {
if (lock.l_type == F_RDLCK)
printf("read lock has been already set by %d\n", getpid());
else if (lock.l_type == F_WRLCK)
printf("write lock has been already set by %d\n", getpid());
getchar();
}
}
}
int main() {
int fd;
fd = open("data", O_RDWR | O_CREAT, 0666);
if (fd < 0) {
perror("open failed");
return -1;
}
lock_set(fd, F_WRLCK);
getchar();
lock_set(fd, F_UNLCK);
getchar();
close(fd);
return 0;
}

Comment and share

魏传柳(2824759538@qq.com)

author.bio


Tencent


ShenZhen,China