2014年6月5日星期四

Mac OS X 中 sed 换行符

在Mac OS X 中使用sed遇到一个无法替换换行的问题,在Linux下,下面的命令效果是很明显的,完全没有问题

$ echo "123 345 678 789" | sed 's/ /\n/g'
123
345
678
789

但是在Mac OS X中,上面的命令是无法正常的,结果如下
$ echo "123 345 678 789" | sed 's/ /\n/g'
123n345n678n789

究其原因就是Max OS X上的sed是BSD的版本,Linux上的是Gnu的版本,导致的不一致。

怎样解决这个问题呢

第一就是使用tr,这样也能得到期望的结果
$ echo "123 345 678 789" | tr ' ' '\n'

第二就是使用BSD版本的写法。
$ echo "123 345 678 789" | sed -e 's/ /\'$'\n/g'
123
345
678
789


2013年12月3日星期二

Learning C++ Primer Plus chapter 8 - 9

这几周主要在学习8-9章,涉及到得新的知识点越来越多,导致学习的进度并不是很快,后面的学习速度需要提高一下

第八章的主要内容是讲cpp中得函数的使用,包含内联函数,引用变量,函数重载,函数模板等主题,大部分内容是以前有过了解,但是从未深入的,渐渐的感觉到了cpp的强大,对C语言的进化

第九章的主要内容是关于内存模型和名称空间的,这类涉及到得细节相对多一点,知识点本身病不是很多

下面主要列一下在这两章中学习到得一些知识点:


一、引用
在说引用之前,先说一下左值和右值,先看下wikipedia上得解释
  • * L-valueAn expression (computer science) that designates an object▫
  • * see alsohttp://www.iso-9899.info/n1570.html#6.3.2.1
  • * r-value, in computer science, a value that does not have an address in a computer language
其实从上面来看,左值和右值的本质区别应该是是否可以被改变,简单的区别办法就是在等号左边的即是左值,它本质是一个对象,在内存中是一个实际的地址,可以往这个地址里存储符合的值,而右值可以是一个表达式,也可以是一个值,右值中的数据只能被读取,不能写入,也就是不能被改变

在c++11中,在原有的引用的基础上,新增了右值引用,原来的引用办法,现在被称之为左值引用,下面看下简单的例子

左值引用:
  double a = 1.2;
 
 double & b = a;
  cout << b << endl;
右值引用:
 
 double && c = 1 + 3.4;
  cout << c << endl;
在正常的情况下,直接使用右值做引用是不可以的,但是现在在C++11中,是可以的,目前具体的右值引用的使用场景还没有见到,在到后面使用时,再做详细的说明。



二、函数重载(多态)
函数重载的要点在于函数的参数列表,也被称之为函数特征标,函数的返回值类型,可以相同也可以不同,但是特征标,必须不用
还有一点是,编译器在检查函数特征标的时候,将会吧类型引用和类型本身,视为相同的特征标
double cube(double x)
double cube(double & x)
所以如果同时定义了这两个函数,那么就是不合法的,编译器将不知道使用哪个函数来做处理



三、函数模板(泛型)

函数模板,就是通过泛型来定义函数,当传递具体的类型给模板,可以让编译器生成具体的类型的函数,模板因为允许以泛型来编写程序,因此也被称之为通用编程,通过模板,我们可以将以前大多数的工作量通过模板来简化。

如果以前需要重载int double float三种类型的函数,那么需要手工写三次,如果使用模板,那么只需要写一次即可,编译器会自动为你完成剩余的工作。

定义方式:
template <typename T>void show_value(typename &t);

在c++98之前,没有关键字typename,使用class来创建模板
template <class T>
void show_value(class &t);

上面的二者是等价的



C++具体化方法
1.对于给定的函数名,可以有非模板函数,模板函数和显式具体化的模板函数以及他们的重载版本
2.显式具体化的原型和定义是以template <> 打头,并通过名称来指出类型
3.非模板函数优先于具体化,优先于常规模板

三种原型:
//非模板函数:
void swap(job &,job &);

//显式具体化:用户自定义 TODO
template <> void swap<job>(job &, job &);
template <> void swap<int>(int &, int &);
template <> void swap(int &, int &);

//常规模板:
template <typename T>
void swap(T &, T &);

实例化:我们在上面定义的函数模板,仅仅是对于生成函数的一个描述信息,而并非真正的函数本身,当程序在执行的过程中,编译器根据传入的参数,生成具体的真实的函数的过程,称之为实例化。最初的c++只能通过隐式实例化来使用函数模板,现在c++可以支持显式实例化,可以直接让编译器生成自己所需要的函数定义。

显式实例化:通过用户传入的参数,编译器根据模板来生成符合要求的函数实例
语法:template void swap<int>(int, int);

隐式实例化:用户自己指定参数来让编译器通过模板生成符合用户需要的函数实例


隐式实例化,显式实例化,显式具体化,统称为具体化,相同之处在于,表示的都是使用具体类型的函数定义,而不是通用描述

四、存储持续性
在前面说过,c++的有几种存储方案来存储不同的数据,分别是
自动存储持续性:栈
静态存储持续性:静态存储区
动态存储持续性:堆

在c++11中,新增县城存储持续性,主要应用于多线程编程中,这让程序能够将计算放在可并行处理的不用线程中,使用关键字thread_local声明,其生命周期与所属的线程一样长


五、寄存器变量register
在c语言以及早期的c++中,register关键字声明的变量,表示寄存器变量,即建议编译器将其存储在cpu的寄存器中,因硬件和编译器变得越来越复杂,所以在c++11中,关键字register的这种提示作用失效了,现在的register只是显式的支出变量是自动的


六、定位new运算符
在一般情况下,new运算符,从堆中申请置顶大小的内存块,但在实际应用中,new还有一种被称之为定位new运算符的变体,它可以在指定的位置申请内存块,比如静态存储区,栈,堆等,我们可以使用这种特性来设置内存管理规程,处理需要通过特定地址才能访问的硬件,以及在特定的位置创建对象

使用方法:
#include <iostream>
#include <new> // for placement new
const int BUF = 512;const int N = 5;char buffer[BUF];      // chunk of memory
int main()
{
   
 using namespace std;

   
 double *  pd1 = new (buffer) double[N];  // use buffer array
    cout << &buffer << endl;
    cout << pd1 << endl;
   
 return 0;
}
执行输出结果:
0x10f1e2070

0x10f1e2070

可以看到,pd1的地址,与buffer的起始地址是一样的,所以pd1实在buffer上面申请的一块空间。


2013年11月13日星期三

Learning C++ Primer Plus chapter 5 - 7

本周的学习主要是5-7章
主要内容
第五章:循环与关系表达式
第六章:分支语句与逻辑运算符
第七章:函数-c++编程模块

这几章,主要还是一些编程的基础知识,五六章主要简单的看了,与C语言级其他编程语言的差别很小,重点是在看第七章,其中关于函数的部分有一些比较新的东西,还有一些以前的理解不够深刻的点,着重看了一下。




1.基于范围的for循环( P152)
在C++11中,新增了一种循环,这种方式类似于perl中得数组foreach循环,python中也有类似的实现
这种方式简化了对于,数组,容器的循环任务,对其中的每个元素执行相同的操作,直到最后一个元素,退出循环。
例子:

  1 #include<iostream>
  2 using namespace std;
  3 int main()
  4 {
  5   int arr[] = {1,2,3,4,5};
  6   for (int x : arr)
  7     cout << x*2 << endl;
  8   return 0;
  9 }
如果要修改上面数组中得值,只需要将x换成&x作为引用即可
  1 #include<iostream>
  2 using namespace std;
  3 int main()
  4 {
  5   int arr[] = {1,2,3,4,5};
  6   for (int &x : arr)
  7     x = x * x;
  8   for (int x : arr)
  9     cout << x << endl;
 10   return 0;
 11 }

注:本特性始于C++11,需要在编译时候加上编译参数--std=c++11


2.数组与指针的区别

1.两者在含义上的区别。 
数组对应着一块内存区域,而指针是指向一块内存区域。其地址和容量在生命期里不会改变,只有数组的内容可以改变;而指针却不同,它指向的内存区域的大小可以随时改变,而且当指针指向常量字符串时,它的内容是不可以被修改的,否则在运行时会报错。 

2.计算内存容量的区别。 
用运算符sizeof可以计算出数组的容量(字节数),而用sizeof却无法计算指针所指内存的容量,用sizeof(p)得到的结果永远是4或者2(即指针变量所占内存单元的字节数,一般情况下指针变量占2个或4个字节的内存单元)。在进行参数传递时,数组会自动退化为同类型的指针。 

在C++中,当(且仅当)用于函数头或函数原型中,int *arr和int arr[]才是相同的。

下面两个恒等式,无论arr是指针还是数组名,均成立:
arr[i]==*(arr+i);
&arr[i]==arr+i; 


3.const指针,指向const的指针

这个是一个比较容易混淆的东西,在下面举例说明,不做过于详细的叙述了。
const指针:
int gorp = 16;
int chips = 12;
const int * p_snack = &gorp; 
//p_snack的类型为const int * 也就是指向的地址内容是const,所以其指向地址得内容是不可修改的

*p_snack = 20;          //不可以
p_snack = &chips;     //可以


指向const的指针 
int gorp = 16;
int chips = 12;
int * const p_snack = &gorp; 
//p_snack的类型为int * const 也就是指向的地址是const,所以其指向地址是不可修改的

*p_snack = 20;          //可以
p_snack = &chips;     //不可以 



4. 函数指针
函数指针是一个非常有用,并且有趣的内容,通过函数指针,我们可以创建公共的接口,然后用户通过传入自定义的函数,来实现其自定义的功能,非常适用于那些有共性,但是在不同情况下实现机制不同的程序。通过这一层的逻辑抽象,也可以将简化程序设计。

使用函数指针的方式:
定义一个正常的函数,然后用其原型抽象出函数指针,编写其他的被调程序

比如函数原型如下:
int sum(int );
声明指向函数的指针只需要将函数名置换为指针名就可以了,如上面的置换为
int (* p_sum)(int);
其中p_sum就是这类函数的指针


下面是一个函数指针的例子:
  1 #include <iostream>
  2 using namespace std;
  3 
  4 int print_string(const char * str){
  5   cout << str << endl;
  6   return 0;
  7 }
  8 int print_string_2(const char * str){
  9   cout << str << '\t' << str <<endl;
 10   return 0;
 11 }
 12 
 13 int func(int (*f)(const char *), const char * st){
 14   f(st);
 15   return 0;
 16 }
 17 
 18 int main(int argc, const char * argv[])
 19 {
 20     func(print_string,"first one!!!");
 21     func(print_string_2,"second !!!");
 22     return 0;
 23 }  




2013年11月5日星期二

Learning C++ Primer Plus chapter 1- 4

    本周开始看《c++ primer plus》,其中前半部分1-7章,主要是一些c++和c的基础,计划是花两周时间简单看下,那些已知的知识点掠过,主要看一下那些没有见过的内容,以及c++中的特殊部分,习题等主要在脑中过一下。对于有一定的难度的,实地写程序来处理。

本周主要看了1-4章,大部分都掠过了,对于那些头一次见的东西,想到的东西,记录如下,其中很多内容是c++11中新加的特性,所以在编译时要加参数-std=c++11


1.列表初始化(list-initialization)(P43,P63)
这种写法来源于对数组以及数据结构的初始化,如:int a[3] = {0,1,2};
从c++98开始,可以用于单值得赋值,如:int b = {4};
c++11开始,支持可以省略等号。当大括号中没有值得时候,变量将要被初始化为0.
例如:int a{3} 等价于 int a = 3 等价于 int a = {3};
这种写法只能用于c++11标准,所以在编译时候必须加上-std=c++11

优点:有助于防范类型转换错误
具体就是:列表初始化不允许缩窄(narrowing),即变量可能无法表示赋给它的值。
举例说:
测试代码:如果用int a = 1.1; 
那最终编译过程中只会报出一个警告信息,但是最终还是可以生成可执行文件。

main.cpp:22:11: warning: implicit conversion from 'double' to 'int' changes value from 1.1 to 1 [-Wliteral-conversion]  int a = 1.1;       ~   ^~~ 1 warning generated.


如果使用int a {1.1};
则会报错误,导致无法编译通过

main.cpp:21:11: error: type 'double' cannot be narrowed to 'int' in initializer list [-Wc++11-narrowing]   int a {1.1};           ^~~ 
main.cpp:21:11: note: override this message by inserting an explicit cast   int a {1.1};           ^~~           static_cast<int>( ) 
main.cpp:21:11: warning: implicit conversion from 'double' to 'int' changes value from 1.1 to 1 [-Wliteral-conversion]   int a {1.1};          ~^~~ 1 warning and 1 error generated.


2.打印进度;
之前一直好奇一些程序的进度是怎么打印的,比如rsync文件过程中的百分比,这些数据不是按行打印的,而是在原地变化,现在终于搞懂这个是怎么搞的了,其实很简单,就是用了转义字符\r 回车符合,其实回车和换行还是有区别的,但是以前都是知其然而不知其所以然。具体可以参考程苓峰的这篇blog:《回车和换行

代码如下:
  1 #include <iostream>
  2 #include <unistd.h>
  3 #define MAXLINE 10
  4 int main(int argc, const char * argv[])
  5 {
  6   char line[MAXLINE+1] = {};
  7   for (int i=0 ;i < MAXLINE ;i++){
  8     line[i] = '-';
  9     std::cout << "\r" << line << i*10 << "%" ;
 10     std::cout.flush();
 11     sleep(1);
 12   }
 13   std::cout << std::endl;
 14   return 0;
 15 }


3.auto声明(P66)
auto声明可以让编译器根据其初始值的类型来推断变量的类型,auto起源于c语言,但是很少使用
例如:auto a = 100 a会被编译器初始化为int类型
auto b = 1.5  b会成为double
上面的情形误用会导致错误,比如上面的a本事要初始化为double的情形,会被自动初始化为int导致不符合预期

当这个特性用于复杂类型时候,会显现其威力。可以节省不少脑细胞。
std::vector <double> scores;
auto pv = scores.begin();
此时,pv会被正确的初始化为std::vector<double>::iterator 

再举一个例子:
  1 #include <iostream>
  2 #include <unistd.h>
  3 using namespace std;
  4 int main(int argc, const char * argv[])
  5 {
  6     char a = 'a';
  7     char b = 'b';
  8     char c = 'c';
  9     char * line[3] = {&a, &b, &c};
 10     char ** p_line = line;
 11     auto pp_line = line;
 12     std::cout << *p_line[1] << std::endl;
 13     std::cout << *pp_line[2] << std::endl;
 14     return 0;
 15 }  

在这里,p_line与pp_line是一样的,用auto会省略很多麻烦
注:因此特性在c++11中添加,所以使用此特性编译时需要加参数--std=c++11


4.显示原始字符串(P88)
如果要打印出带有双引号的字符串,一般情况下只能在其中加入转义字符,c++11提供了一种方便的办法,可以直接输出原始字符串。
例如:
std::cout << R"( hello "world" "\n" .)" << std::endl;
可以直接输出字符串:hello "world" "\n” .

如果要输出的字符串中含有括号,只需在双引号与逗号之间添加任意的基本字符即可:如
std::cout << R"i( hello ( my ) "world" "\n" .)i" << std::endl;

就可以输出:hello (my) "world” "\n"

其中i可以换成除了左右括号以及斜杠之外的任意可见字符
注:因此特性在c++11中添加,所以使用此特性编译时需要加参数--std=c++11

5.程序的变量存储区(P117)

以前对程序的变量存储有过一点接触,但只是听说,看了这个之后,对于程序中变量的存储区域了解的多了一点
a>自动存储(栈)
     在程序中定义的常规变量,在函数调用时候产生,函数结束时候消亡被释放,存储于栈中,正好满足栈的特点,先进后出
b>静态存储
     待详细了解
c>动态存储(堆)
     通过new申请的空间,都是在堆上,他们申请之后除非用户主动释放以及程序结束,其生命周期将一直持续。知道用户用delete释放,或者程序结束。这种机制给予程序员对数据更大的控制权。