使用c语言写一个函数实现两个数交换,很简单写下以下代码:
void swap(int *a, int *b) { *a ^= *b; *b ^= *a; *a ^= *b; }
只有三行代码,且没有引入中间变量,使用位运算,效率高!
但首先必须保证a, b 不是空指针,否则就会出现段错误。
于是代码进一步改成:
void swap(int *a, int *b) { if (a == NULL || b == NULL) return; *a ^= *b; *b ^= *a; *a ^= *b; }
似乎这样就完美了?
咋一看,真没有什么问题了,不信你可以测试,无论正数,负数,还是0,基本都不会出错了。。
那么请看以下代码:
static int count = 0; void permutation(int *a, int from, int to) { if (from == to) { cout << ++count << ":"; for (int i = 0; i <= to; ++i) cout << a[i] << " "; cout << endl; return; } for (int i = from; i <= to; ++i) { swap(&a[from], &a[i]); permutation(a, from + 1, to); swap(&a[from], &a[i]); } }
以上代码是求一个数组全排列的递归方法,算法应该没有错?
可是输出大量0!!为什么呢???
答案在下面:
原因在于当swap(int *a, int *b)传的是同一个地址时,就会等于0
即若 a == b
*a ^= *b,此时 *a == 0, *b == 0
最后必然是 *a == 0, *b == 0,
for (int i = from; i <= to; ++i) { swap(&a[from], &a[i]); permutation(a, from + 1, to); swap(&a[from], &a[i]); }
上面代码没有考虑当from == i的情况,即传递的是同一个地址。
所以正确的swap函数,应该是:
void swap(int* a, int* b) { if (a == NULL || b == NULL || a == b) return; *a ^= *b; *b ^= *a; *a ^= *b; }
linux网络工具非常强大,下面列举下几个常用的网络命令。本文只是列举一些常用的用法,并不对每个命令作详细讲解,请自行google之。
1.ping 这个大家都不陌生了,我们平时使用这个工具查看两个主机是否想通。默认是一直发送直到用户终止(Ctrl + c),这在nova 实例下网页上使用vnc控制时,由于不支持Ctrl,发送ping后不能终止,只能ssh到虚拟机上kill掉,自此我一般都会加上-c参数,指定发送的包数量。
2. ifconfig 这个命令用于查看网络接口信息,修改参数、启动网络接口等,比如查看eth0 信息,ifconfig eth0 ,可以查看eth0的封装类型,mac地址、ip地址、流量等信息。
3.dhclient 如果你是通过dhch获取ip的,有时可能开机后没有获取到ip,通过这个命令可以从dhclient server上分配一个ip地址,比如dhclient -4 -v eth0,-4代表ipv4。这个命令必须以root身份执行。
4.curl是非常强大的http命令行工具,能够发送各种http请求,发送参数。
5.wget 一般用于下载文件,当然curl同样可以实现。通过wget可以爬取获得整个网站的镜像。
6. axel 多线程下载,通过指定-n numbers 设置线程数,加速下载,一般会比wget快。
7.route 用于查看设置本地路由表。
8. ip 这个命令非常强大,几乎可以取代ifconfig、route。
9.traceroute ping命令一般只能判断两个主机是否想通,但无法检测中间过程的状态。而traceroute会对每一跳发送一个包,获取RTT,据此当网速慢时,或者网络出问题时,可以通过该命令判断是哪个节点坏掉了。
10 host 想知道google的ip地址?用人说直接ping一下不就知道了,这也是一种方法,但并不完备。有些域名不是指向一个ip,使用host命令就可以获取获取的ip列表。
11 nslookup 同样用于把域名转化为ip地址。
12.netstat 查看开启了哪些服务,这些服务使用了哪些端口,某个端口被谁占用了,哪些端口处于监听状态,当前建立了哪些tcp连接。netstate可以说是非常强大的工具,比如我想知道mysql服务器开的哪个端口:sudo netstat -n -l -t -p | grep "mysql" ,注意有些服务是属于root的,需要root身份读取,所以需要用root身份执行netstat,否则可能获取的信息不全。
13. lsof 有人说lsof不是查看某个文件被谁打开了吗,这也属于网络工具?ofcource! 其实这个工具几乎可以取代ps 和netstat。比如我想知道3306这个端口被谁占用了,使用sudo lsof -i:3306 查看。
注意,使用netstat和lsof等命令时,有时很慢,这时由于试图通过dns获取主机域名,如果加上-n参数,直接返回ip,不作域名转化,会快很多。
14 mtr 这个工具基本就是ping和traceroute的集成。
15. nc(netcat),网络中的瑞士军刀。想知道1-9999端口哪个开启了,sudo nc -z -v localhost 1-9999 |& grep -v -i "Connection refused" (注意管道后面的&符号,一般的管道指把标准输出流导入管道,加入&后会把标准错误流导入管道中)。这个命令非常强大,可以实现各种功能,比如扫描端口、聊天工具、开启临时监听服务等。
16 ss 另一个查看socket的工具,类似netstat。
17 rfkill 管理无线设备工具。
18.iptable 设置查看防火墙、nat表等。
19. ifstat 实时查看网络流量信息。
20. tcpdump 抓包工具。
21 ncmap 端口扫描工具、网络主机发现、网络安全审计工具。用于列举网络主机清单、监控主机、服务运行状况等。查看当前主机哪些端口开启了sudo nmap localhost
22 ftp ncftp lftp ssh scp telnet 。
后期会补充更多!
tmux复制粘贴时使用的是tmux内置的buffer,可是有时需要用到系统复制粘贴,比如有时需要复制错误信息到chrome搜索,似乎就比较麻烦了,因为tmux buffer和系统clipboard是独立的,不能像我们平时习惯的那样,使用鼠标右键复制粘贴功能。而且也不支持鼠标选取右击选择复制粘贴。
有人使用y键绑定了快捷方式实现从tmux buffer中拷贝数据到系统clipborad,如下:
bind y run-shell "tmux show-buffer | xclip -sel clip -i" \; display-message "Copied tmux buffer to system clipboard"
这样就能使用prefix + y键实现从tmux buffer 中提取top数据(最后的buffer)拷贝到系统中。
另外一种方式是按住shift键,然后用鼠标选择,就能恢复系统原来的复制粘贴选项了。
bash数组不是什么新的东西,网上的资料也很多,我再写,似乎有点多余。但网上都讲的不太清楚,尤其是* @ 和引号之间的区别。
下面先从数组的基本操作开始:
初始化数组, a=(1 2 3 4),这个用多了c语言的尤其容易出错,往往=左右加上空格,( ) 写成{ },中间元素加上多余的逗号等,这都会造成错误,而错误不是语法错误,而是结果不是你所期待的,这才是最危险的!比如写出a=(1,2,3,4),语法没有错误,但结果是一个只有一个元素的数组,元素为"1,2,3,4",a={1 2 3 4 5}则会出现语法错误,解释器会把{1当作一条命令, 数组初始化也可以指定索引,并且可以逻辑不连续存储,即可以这样a=([2]=2 [5]=5),此时a[2]=2, a[5]=5,但a[0],a[1],...等均为空,并且a数组的长度为2而不是6,原因下面讲!
访问某一个元素的值是${array[n]}(前面有美元符号,并且array[n]有花括号括起来,这里不知道为什么不显示)其中array为数组名,n为索引。比如echo ${a[2]}表示输出a数组第3个元素的值。
得到一个数组后就要说数组的长度了,由于bash数组不是静态分配的,具体占多少内存不知道,你完全可以不进行任何声明和初始化,直接输出a[100],没有任何语法错误,只不过它的值为空而已,可以理解成值为“”(注意不是真的是“”,只是好理解而已),而长度仅仅是计算不为空元素的个数,比如a=([2]=2 [5]=5),展开为 "" "" 2 "" "" 5 "" "" .. ,其中""表示空,则这个数组长度为2,而不是5也不是6. 输出数组长度的关键符号是#,比如获取a数组的长度,语法是${#a}吗,错误。。。这个又需要了解两个符号* 和@。
先讲讲#的原理,#可以看作是一个函数,这个函数输出它后面参数并以IFS定义为分隔符的个数,比如 # 1 2 3 4 5, 返回5(IFS默认为空白符),而# "1 2" "3 4 5" 6返回3,引号内为一个整体,请注意,这样只是为了好理解,并不是说bash真的有这样的语法!
*的作用是把一个数组的所有元素当做一个整体返回,比如 * 1 2 3 4 5 ,返回 "1 2 3 4 5",注意""只表示这是一个整体,并不是真的会有引号,但确实是当做一个字符串处理的。
@的作用是把数组的所有元素逐一返回, 比如 @ 1 2 3 4 5 ,返回 "1" "2" "3" "4" "5",同样注意""只是记号!
好了,假设a={1 2 3 4 5}
,则${#a}输出什么呢?答案是1,为什么呢?注意a后面没有索引符号[ ],则默认为输出a的值,而注意,bash中a和a[0]的值完全等价,即
b=([0]=2),此时$b为2,而c=5,echo ${c[0])也为5,这个学过c应该容易理解.
因此${#a}相当于${#a[0]},展开为# 1 返回1,相当于求a[0]元素的长度。而要求整个数组长度,应该使用* 和 @,
即${#a[*]}, 展开为 # 1 2 3 4 5 ,结果为5,当然使用@返回结果也是一样的。
下面讲讲系统内置数组,即位置参数,shell中相当于传递的命令行参数,而函数,则相当于传递的参数。这个数组是匿名的,即没有名字,访问第3个元素,则${3},[ ]也要省略, 或者直接$3,求长度为${#},简写$#,其他$@ 和 $* 同理。当然对于这个位置参数,系统提供了更丰富的操作,比如shift等。
如果一个元素使用引号引起来,则相当于一个元素,即a={"1 2 3 4 5"},它的长度为1.
接下来看切片,学过python就不会陌生了,这个和python类似,${a[@]:1}表示返回索引1(包括1)开始以后的所有元素,${a[@]:1:2}表示从索引1开始,包括1后面的2个元素,a={1 2 3 4 5}, ${a[@]:1:2}返回2 3。
元素替换,这个和vim的操作类似,比如要把a的所有元素中的2替换成3,则${a[@]/2/3}.
删除元素,使用unset,比如删除第2个元素则unset a[1],注意删除元素但并不会移动元素,只是简单的把该索引的元素置空,原来的元素位置不变。
好了,数组的基本语法基本讲完了,下面看看以下代码:
#!/bin/bash a=(1 2 3 4 5) b=("1 2" "3 4" 5) echo "le a is ${#a[*]}" # 5 echo "len a is ${#a[@]}" # 5 echo "len b is ${#b[*]}" # 3 echo "len b is ${#b[@]}" # 3 c=(${b[*]}) d=(${b[*]}) echo "len c is ${#c[*]}" #5 echo "len d is ${#d[*]}" #5 e=("${b[*]}") f=("${b[*]}") echo "len e is ${#e[*]}" #1 echo "len f is ${#f[*]}" #1 g=(${b[@]}) h=(${b[@]}) echo "len g is ${#g[*]}" #5 echo "len h is ${#h[*]}" #5 i=("${b[@]}") j=("${b[@]}") echo "len i is ${#i[*]}" #3 echo "len j is ${#j[*]}" #3
c语言中宏还是挺重要的东西,c++中由于有了内联函数和模板,而宏编译器不作任何宏参数类型检查,又极易容易出错,宏慢慢会被取代。
我们可能用的最多的就是利用宏定义常量,但我们经常被建议不要这样做,而应该使用const定义常量,因为const常量有类型,编译器会做更多的检查而减少错误。
我们也经常定义类似函数的宏,最常见的如
#define MIN(a, b) ((a) < (b) ? (a) : (b)) #define SQUARE(a) ((a) * (a))
你可能以为SQUARE和函数一样,其实不然,比如int i = 5; SQUARE(++i); 我们期望的结果是36, 可结果是49,原因在于宏是会展开的,利用gcc -E 可以查看展开后的结果, ((++i) * (++i)) , 因此是7 × 7 == 49.
用的比较少的是#符号,它的作用是把参数变成字符串,比如
#define STR(s) #s
简单的理解就是#把后面的参数直接加上引号,因此如果是printf("%s\n", STR(5 * 5) ) 不是"25" 而是"5 * 5"。
还有一个##, 它的作用是连接两个参数,即拼凑起来,注意不是字符串连接,而是符号拼凑,比如
#define CAT(s1, s2) s1##s2
CAT(123, 456) 展开就是123456,而CAT("123", "456")得不到"123456", 编译不过去的!
宏还有与函数不同的是,不支持递归!即
#define TEST(a) ((TEST(a) + a))
TEST(5) 展开为((TEST(5) + 5))。
系统一些预定义宏也挺有用的,比如
printf("%s %s %d\n", __FILE__, __func__, __LINE__);
使用gcc -E 会展开为 printf("%s %s %d\n", "tmp.c", __func__, 5);tmp.c是源代码文件名,5是当前行号,__func__没有展开,而运行的时候会被替换成当前函数名,即输出tmp.c main 5
还有一个系统宏是 __VA_ARGS__,这个是变参列表,会自动替换成参数列表,比如
#define ERROR(format, ...) fprintf(stderr, format, __VA_ARGS__)
ERROR("errno = %d: %s\n", 404, "Not Found!");展开为fprintf(stderr, "%d:%s\n", 404, "Not Found!");
这些系统宏主要用于调试。
lambda表达式是c++11标准新加特性,学过python的一定不会陌生了,或者类似javascript的闭包。cppreference中的定义是:Constructs a closure: an unnamed function object capable of capturing variables in scope. 简单地说就是定义一个临时局部匿名函数。语法为:
[ capture ] ( params ) mutable exception attribute -> ret { body }
其中capture为定义外部变量是否可见(捕获),(这里的外部变量是指与定义这个lambda处于同一个作用域的变量)。
若为空,则表示不捕获所有外部变量,即所有外部变量均不可访问,= 表示所有外部变量均以值的形式捕获,在body中访问外部变量时,访问的是外部变量的一个副本,类似函数的值传递,因此在body中对外部变量的修改均不影响外部变量原来的值。& 表示以引用的形式捕获,后面加上需要捕获的变量名,没有变量名,则表示以引用形式捕获所有变量,类似函数的引用传递,body操作的是外部变量的引用,因此body中修改外部变量的值会影响原来的值。例如:[ ]表示不捕获任何外部变量, [a, b]以值的形式捕获a,b, [=]以值的形式捕获所有外部变量,[&a, b]a以引用的形式捕获,而b以值的形式捕获,[&, a],除了a以值的形似捕获,其他均以引用的形式捕获,[this]以值的形式捕获this指针!
params就是函数的形参,和普通函数类似,不过若没有形参,这个部分可以省略。
mutalbe表示运行body修改通过拷贝捕获的参数,exception声明可能抛出的异常,attribute 修饰符,参考:http://en.cppreference.com/w/cpp/language/attributes ->ret ret表示返回类型,如果能够根据返回语句自动推导,则可以省略,body即函数体。
注意:除了capture和body是必需的,其他均可以省略,即
int main(int argc, char **argv) { []{}(); return 0; }
定义了一个空lambda表达式,并执行(实际上它什么都没有做)。
int main(int argc, char **argv) { int i = 0; []{cout << i << endl;}(); /* 'i' is not captured */ return 0; }
声明这段代码不能编译通过,因为[ ] 没有捕获任何外部变量,因此i是不可见的,lambda不能访问i。
int main(int argc, char **argv) { int i = 0; cout << i << endl; [=]()mutable{cout << ++i << endl;}(); cout << i << endl; return 0; }
上面的代码输出为0, 1, 0,注意mutable是必需的,因为body中修改了捕获的i,由于i是以值传递的,因此并没有修改i原来的值,而是i的一个副本。
int main(int argc, char **argv) { int i = 0; cout << i << endl; [&](){cout << ++i << endl;}(); cout << i << endl; return 0; }
上面代码输出0, 1, 1,因为i是以引用传递的,而body中修改了i的值。
lambda表达式有什么用呢? lambda的作用就是创建一个临时匿名函数,想想有STL算法中很多需要传递谓词函数,比如count_if。假如我有一个字符串容器,我需要统计长度大于3的个数,则可以这样:
int main(int argc, char **argv) { vector<string> s = {"1", "12", "123", "1234", "12345", "123456", "1234567"}; cout << count_if(begin(s), end(s), [](string s){return s.size() > 3;}); return 0; }
这样就不必要再声明一个函数了,代码比较简洁。
还有一个问题就是,我可能需要统计字符串长度大于4,或者5,或者6,显然不能通过传递一个形参来实现,因为谓词函数限定只能传递一个形参,这里我们传递的是string,而不能再传递一个表示长度的数。 如果定义全局函数实现,则我们有两种方式:
一是声明一个全局变量,通过和这个全局变量作比较。二是分别声明大于4或者5的函数。显然两个办法都不太好。全局变量污染一直是很危险的,而声明多个函数显然不科学,如果有更多的需求,则需声明gt2, gt3, gt4 ...。 这个用lambda表达式似乎更好,如:
int main(int argc, char **argv) { vector<string> s = {"1", "12", "123", "1234", "12345", "123456", "1234567"}; string::size_type n = 3; cout << count_if(begin(s), end(s), [n](string s){return s.size() > n;}); return 0; }
注意这里的n并不是全局变量,所以不存在全局变量污染的问题。当然还有更好的办法,那就是利用函数对象,如:
using namespace std; class Gt { public: typedef string::size_type size_type; Gt(size_type i):n(i){}; Gt():n(0){}; void setN(const size_type& i) { n = i; } size_type getN() const{return n;} bool operator () (const string &s) const {return s.size() > n;} operator int() const { return n;} private: string::size_type n = 0; }; inline Gt operator + (const Gt &s1, const Gt &s2) { return Gt(s1.getN() + s2.getN()); } int main(int argc, char **argv) { vector<string> s = {"1", "12", "123", "1234", "12345", "123456", "1234567"}; cout << count_if(begin(s), end(s), Gt(1)) << endl; /* > 1 */ cout << count_if(begin(s), end(s), Gt(2)) << endl; /* > 2 */ cout << count_if(begin(s), end(s), Gt(2) + Gt(3)) << endl; /* > 5 */ return 0; }
上面分别统计长度大于1,大于2,大于5的字符串数量(重载+运算符,纯属娱乐!)。
lambda表达式另外一个功能是声明局部函数。我们知道c++中是不允许在函数中嵌套定义函数的,如果要声明一个局部函数,即只能在函数内部调用,则可以用lambda实现,如声明一个min函数,返回a,b中较小值。则
#include <iostream> using namespace std; int main(int argc, char **argv) { auto min = [](int a, int b) -> int{ return a < b ? a : b; }; cout << min(1, 2) << endl; cout << min(8, 2) << endl; return 0; }
首先讲讲c语言中const关键字的作用,很多书把const修饰的关键字叫做常量,但我认为还是叫做只读变量恰当点,毕竟它和真正的右值常量还是有区别的,除了只读(即只能定义时初始化,其他任何时候不能再修改)这个限制外,和普通变量没有什么区别。
基本使用方法,就是在变量声明的加上const修饰即可。如const int N = 0;即声明了一个只读变量N。这个N是不允许修改的。
关键在于涉及指针时需要弄清楚const修饰的是指针本身还是修饰指针指向的对象,这点很重要!
可以这样简单记忆,如果const在 * 前面则const修饰的是指针指向的对象,即指向的对象是只读的。如果const 在 *后面则修饰的是指针本身,即该指针变量不可指向其他对象,而指向的对象并不受const约束。如
int x = 5, y = 6; const int *p = &x; int * const q = &x; // q = &y; // *p = 6; p = &y; *q = 7;
p指针不能修改指向对象的值(注意:只能说不能通过p来修改所指向对象的值,不能说指向的内容不能修改,完全可以通过其他方式修改其值,比如q指针),而q指针可以修改指向对象的值,但本身是一个只读变量,即不能再指向其他对象。另外,注意一点的是,const变量必须在声明时初始化,这和java中的final修饰符不同,final可以声明不进行初始化,可以在任何时候进行初始化。
c++的引用类型也基本类似,看 & 位置决定const修饰的内容。另外注意以下代码:
typedef int* Pointer; int x = 5; const Pointer p = &x;
很容易把const Pointer p = &x, 展开为 const int *p = &x,引起错误! 注意typedef并不是简单的展开,它和宏有本质的区别。上面定义的p应该相对于int * const p = &x; 即p本身是可读变量,而不是其指向的对象不可修改。
const修饰符有什么作用,也许第一反应就是声明一个常量(为什么要声明常量?与宏#define的区别?)。其实更确切地说,const修饰符的功能是为了保护数据安全。
这在函数传参中更容易理解,假如我有一个char *s 字符串, 我要一个查找某个字符的位置的函数find,返回第一次出现该字符的索引,但我不希望由于传入参数后意外的被修改了原来的字符串,关键是这个函数还不一定是我自己写的,有可能是其他人写的。我们可以声明一个函数为 int find(const char *s)来达到这样的目的。 这样我就不用担心我的字符串被恶意修改了。因此:只要是不需要修改原来的内容,则尽量声明为const参数,减少出错率。比如在写qsort的cmp函数时,应该写成int cmp(const void *a, const void *b),因为不希望在比较的时候就把原来的值篡改了。不过一般情况下,const只对指针或者引用参数有效,int foo(const int n)纯属胡闹,因此该函数是传值,每次调用函数时,都会重新创建该函数所有的形参,此时所传递的实参将会初始化对应的形参,普通的非引用类型的参数通过复制对应的实参实现初始化,函数并没有访问调用所传递的实参本身,因此不会修改实参的值,而只是操作实参的一个副本。因此foo函数形参n没有必要const。当然也有时这样使用,比如int op(const int *a, const int n),a表示一个动态数组,n表示数组大小,我不希望在函数体内意外的修改了n的值,因此声明n为const形参是有用的。另外需要注意:如果是在c++中,应该尽量将不需要修改的参数定义为const引用,而不应该使用传值的方法!这样能够避免复制实参,减少开销。虽然op(string s1) 和 op(const string &s1)都不会修改s1,但前者需要复制s1,而后者不需要,如果一个类型的复制代价很大时,这效率更明显!
另外需要注意的是const 指针和非const指针、const引用和非const引用是可以重载的,即int foo(const int *x) 和 int foo(int *x)可以重载,int foo(const int &x) 和 foo(int &x)可以重载,所以说重载与否只看参数类型和个数是不确切的,还需要考虑是否const指针或者引用。int foo(const int &x)可以传入右值, 而foo(int &x)不可以传入右值,因为右值是不可修改的。(注意c++11标准中右值引用 && 可以修改右值,因此int foo(int &&x)可以传入右值). 在决定调用哪个版本时,由传入的参数是否const决定!
在c++类中可以声明const成员函数,即 int foo() const {} ,这样声明说明这个函数不能修改对象成员,即this指针是const的,但注意,const成员函数仍然可以修改mutable修饰的数据成员!
总结一下:const的最主要功能是保护数据, 另外使用const引用还能提高函数运行效率,减少复制实参开销!
学过c的都知道sizeof运算符。不过还是需要注意以下几点。先从c的sizeof说起:
1. sizeof 是运算符,而不是函数。虽然我们习惯sizeof(...),但( )并不是必需的,它只是表示优先级。我们把sizeof后面的目标叫对象或者操作数。本文约定就叫sizeof对象。
2. 当sizeof 的对象是表达式时,求的大小是表达式返回值的类型大小,但并不计算表达式的值,比如
char c = 1; int i = 2; cout << sizeof(c + i) << endl; cout << sizeof(c = c + i) << endl;
前者c + i会隐式类型转化为int类型(类型提升),因此返回4(32位系统), 而后者虽然运算时也是转化为int,但赋值给c时又会转化为char,因此返回的是1。同样如果对象是函数,则返回函数返回值类型大小,如:
long long foo() { printf("'%s' has been called.\n", __func__); return 0; } int main(int argc, char **argv) { cout << sizeof(foo()) << endl; return 0; }
执行后输出8, 不会输出 'foo' has been called.说明函数没有真正执行,而只是判断了下返回类型。
3.注意sizeof 对象是指针和数组的区别。
当sizeof的对象是数组时,返回数组总大小,而当对象是指针时,返回指针本身的大小,而不是指示内存空间的大小。因为指针本身就是一个无符号整型数,因此int *p ,sizeof(p)返回的大小是sizeof(void *), 32 位系统返回4,即32位。但注意当数组名作为实参传入函数时,会自动转化为指针类型,如下:
void foo(int a[]) { cout << sizeof(a) << endl; /* 4 */ } int main(int argc, char **argv) { int a[] = {1, 2, 3, 4}; int *p = a; cout << sizeof(a) << endl; /* 16 */ cout << sizeof(p) << endl; /* 4 */ foo(a); return 0; }
4. sizeof 无法获取动态分配的内存大小,即使用malloc动态的分配内存,无法使用sizeof获取其大小。
5. 注意c_style字符串末尾有一个\0结束符,也需要占一个char空间,因此sizeof("1") 返回2。而strlen返回的是字符数,不包括\0结束符。
6.关于结构体类型。
理论上一个结构体所占空间是所有成员的大小总和,但由于考虑到对齐问题,会有填充字节。
struct node { int a; char c; };
大小为8字节而不是5字节,填充了3字节。
注意:c语言中空struct大小为0, 而c++中空struct 大小为1, 具体看后面关于空类的讨论。另外,c99中结构体后面的动态数组,即不指定大小的数组,sizeof 时不包括动态数组的大小,即
struct node { int a; char c; int d[]; };
返回依然是8。
下面关于c++类的讨论。除了struct ,以上讨论关于c的sizeof同样适合于c++。首先说说c++ 中的struct类型,注意和c中的struct是不一样的,c中的struct只是一种把各种基本数据类型包装的组合类型,而c++的struct本质上是类,即类有的东西,struct基本都有,即struct也有构造函数、析构函数、成员函数等等,不过它的默认成员是public的,而class定义的类成员默认是private的。另外,struct继承默认也是public,而class定义的类默认是private。另外注意:class可以定义模板参数,但struct不可以!因此,struct本质就是类。
下面主要讨论类的大小:
1. 空类的大小。空类型实例中不包含任何信息,应该大小为0. 但是当我们声明该类型的实例的时候,它必须在内存中占有一定的空间,否则无法使用这些实例。至于占用多少内存,由编译器决定。g++中每个空类型的实例占1字节空间。注意空struct即空类,这就是为什么c++的空struct占一个字节的原因。
2. 构造函数、析构函数、成员函数调用时只需知道函数地址即可,而这些函数的地址之与类型相关,而与具体的实例无关,因此不会在实例中额外添加任何信息。
3. 静态数据成员放在全局数据成员中,它不占类实例大小,多个类实例只有一个实体。可以看作是一种特殊的全局变量。
综上1,2,3:
class A { public: static int a; static char c; A(){}; ~A(){}; void foo(){}; };
类A的大小为1字节,等于空类大小,因此静态数据成员a,c和成员函数都不占类的大小。
4. 类的非静态数据成员和c语言中的struct类似,也需要对齐,可能需要字节填充。
class A { public: int a; char c; A(){}; ~A(){}; void foo(){}; };
类A的大小为8字节,a占4B,c占1B,填充3B。
5. 如果一个类中有虚函数,则该类型会生成一个虚函数表,并在该类型的每一个实例中添加一个指向虚函数表的指针,因此类大小必须加上一个指针所占的空间。如果是普通继承,子类和基类共享这个指针。
class A { public: int a; char c; A(){}; ~A(){}; void foo(){}; void virtual bar(){}; };
类A的大小为12B。数据成员8B,加上指向虚拟函数表的指针。注意,是在32位系统上。如果是64位机器,一个指针占8B。
6.虚继承时,派生类会生成一个指向虚基类表的指针,占一个指针大小空间。如果还有虚函数,不增加额外指针大小空间,原因不太清楚,如果谁知道,请一定要告诉我!如下:
class A { int a; }; class B: public virtual A { int b; virtual void foo(){}; };
类B的大小为12B,数据成员b占4B,从A中继承a也占4B,另外一个由于virtual存在,额外加一个指针大小4B,共12B。所以:只要有virtual,无论是在成员函数,还是在继承上,都额外加一个指针大小空间。
基本就这些了,如果有纰漏,请指出,谢谢!
1,支持不定长数组,即使用变量定义数组大小,之前定义数组大小必须是常量类型。比如int a[5], 但 int a[n]就不行。
2,新的数组初始化方式,即可以指定元素赋予初值,试想如果有一个int数组a,大小为100, 即int a[100], 需要把a[50]赋值为5, a[70]赋值为7,其他为0, 则可以这样 int a[100] = {[50] = 5, [70] = 7}; 这对于初始化稀疏数组非常方便。
3,新的结构体初始化方式,具体看下面的例子。
4. 增加内联函数的支持,不过一般要写到头文件中去,或者声明为static类型。
5. 增加__func__宏,用于打印当前函数名,这对于调试非常有用。
6.结构体可以声明动态数组成员,即不指定数组长度,由使用时视分配内存大小决定数组长度。注意动态数组成员必须在声明最后,且前面必须有其他成员,至多只有一个动态数组,并且使用sizeof或许结构体大小时,将不包括动态数组大小。使用时必须由malloc分配内存,并且应该比原结构体分配内存要大。具体看下面的例子。
例子:
#include <stdio.h> #include <stdlib.h> static inline void foo() /* inline函数必须声明为static或者放到头文件中 */ { /* 新增__func__宏,打印当前函数名 */ printf("function '%s' finished.\n", __func__); } typedef struct { int a; char c; int d[]; /* 增加动态数组支持,必须放到最后,且前面必须有一个成员 */ }data, *Data; int main(int argc, char **argv) { int a[] = {[10] = 2, [5] = 5}; /* 数组初始化 */ int len = sizeof(a) / sizeof(int); printf("a: "); for (int i = 0; i < len; i++) /* 变量声明不必在语句开头 */ printf("%d ", a[i]); printf("\n"); int n = 4; int b[n]; /* 数组长度可以是变量 */ printf("sizeof(b) = %u\n", sizeof(b)); data data1 = {1,'c'}; /* 初始化结构体方法一 */ printf("data1.c = %c\n", data1.c); data data2 = {.c='c'}; /* 初始化结构体方法二 */ printf("data2.a = %d, data2.c = %c\n", data2.a, data2.c); Data data3 = malloc(sizeof(data) + sizeof(int) * 10); printf("sizeof(data3) = %u\n", sizeof(*data3)); /* 内存不包括动态数组d的大小,返回8, * int a占4B , char c占1B, 为了对齐,所以返回8 */ data3 -> d[0] = 1, data3 -> d[1] = 1; printf("data3->d[1] = %d\n", data3->d[1]); free(data3); foo(1); return 0; }
另外还有其他特性,比如新增数据类型long long , long double, _Bool,复数等。摘自维基百科,如下
1、foreach语法
java python 等都有foreach语法用于遍历容器(数组也可以看成是一种容器), java 中只要对象是数组或者实现了Iterable接口就能使用foreach语法。在c++11标准中也增加了这个特性。如下:
vector<int> v = {1, 2,3,4}; set<int> s = {1,2,3,1,2,3}; int a[] = {1,2,3}; for (auto i : v) cout << i; cout << endl; for (auto i : a) cout << i; cout << endl; for (auto i : s) cout << i; cout << endl;
2、auto类型推导
在c语言中auto关键字表示声明为自动变量,这个auto关键字可有可无,因为默认就是自动变量。在c++11标准后去掉自动变量显式声明方法。而auto关键字依然使用,但意思不再表示自动变量,而表示类型推导,或者说类型占位符。它会由初始化代码推断真实变量类型,这主要的用处是减少程序员的负担,
vector<int> v = {1, 2, 3, 4}; for (std::vector<int>::const_iterator i = v.begin(); i != v.end(); i++) cout << *i << endl;
而c++11标准可以方便的写成:
vector<int> v = {1, 2, 3, 4}; for (auto i = v.begin(); i != v.end(); i++) cout << *i << endl;
3、空指针类型。
在c中用NULL表示空指针,它表示不指示任何对象。而NULL实际上就是0, 有时为了表明是指针类型,可以这样:
#define NULL ((void *)0)
在c++11标准中增加新的关键字nullptr表示空指针,这个可以避免被隐式转化为整型0.
4、容器初始化。
之前vector<int> v需要初始化1,2,3,4,5,需要逐一push_back,c++11标准可以直接这样:
vector<int> v = {1, 2, 3, 4, 5}; for (auto i : v) cout << i << endl;
当然其他容器也可以这样。
5、lambda表达式。
学过python的一定不陌生了,具体语法参见:http://en.cppreference.com/w/cpp/language/lambda 。如下一个例子:
auto p = [&count](set<int> v) { for (auto i : v) { cout << i << ' '; count++; } cout << endl << "count:" << count << endl;; }; vector<int> v = {1, 2, 3, 4, 5}; set<int> s = {1,1,1,2,2,2}; int count = 0; p(s); count = 0; p({1, 1, 2,3,3, 4, 5, 6, 6}); /* 会自动转化为set<int>类型 */ count = 0; p({1, 1,1,1, 1, 1, 1});
6、全局begin()、end()方法。
非成员方法begin() 和 end()方法加入到STL中,提高了代码一致性,并利于GP化。而且可以使用数组。如下:
int a[] = {1, 2, 3, 4, 5}; set<int> s = {1,1,2,2,3,3}; for (auto i = begin(a); i != end(a); i++) cout << *i << ' '; cout << endl; for (auto i = begin(s); i != end(s); i++) cout << *i << ' '; cout << endl;
7、右值引用。先看以下代码:
int bar(int &x) { x++; printf("%d\n", x); return x; } int main(int argc, char **argv) { bar(5); return 0; }
有什么语法错误呢?对的,上面的bar(5)传入的是一个常数,即试图右值引用,而右值是不允许修改的,所以bar必须声明为int bar(const int &x)才能传入常数字面值。左值是一个有名子的对象,而右值是一个没有名字的临时对象。c++11标准加入右值引用的语法,主要为了实现move语义,实现内存优化管理,减少不必要的深度拷贝开销。
深度拷贝会发生在当对象是以传值的方式传递。举例而言,std::vector<T> 是内部保存了 C-style 数组的一个包装,如果一个std::vector<T>的临时对象被建构或是从 函数返回,要将其存储只能通过生成新的std::vector<T>并且把该临时对象所有的数据复制进去。该临时对象和其拥有的内存会被摧毁。在 C++11,一个std::vector的 "move 构造函数" 对某个vector的右值引用可 以单纯地从右值复制其内部 C-style 数组的指针到新的 vector,然后留下空的右值。这个操作不需要数组的复制,而且空的临时对象的析构也不会摧毁内存。传回vector临时对象的函数不需要显式地传回std::vector<T>&&。如果vector没有 move 构造函数,那么复制构造函数将被调用,以const std::vector<T> &的正常形式。 如果它确实有 move 构造函数, 那么就会调用 move 构造函数,这能够免除大幅的内存配置。
右值引用主要使用 && 标记:
int foo(int &&x) { x ++; printf("%d\n", x); return x; } int main(int argc, char **argv) { foo(5); return 0; }
另外还有更多的c++11标准,参见维基百科:C++11