C/C++的一些杂项

来源:互联网 发布:炉石盒子 mac 编辑:程序博客网 时间:2024/06/06 10:38

fwrite 和 fread的参数问题

        有时,我们在阅读别人的代码时会看到这样的写法:

[cpp] view plain copy 在CODE上查看代码片派生到我的代码片
  1. fwrite(buff, 1, 8912, fout);  
  2. fread(buff, 1, 8912, fin);  
我就觉得奇怪,为什么不写成:

[cpp] view plain copy 在CODE上查看代码片派生到我的代码片
  1. fwrite(buff, 8912, 1, fout);  
  2. fread(buff, 8912, 1, fin);  

        感觉第二种写法会有效率一点。因为其一次就写完,而第一种需要写多次,每次只写一个字节。后来看多代码了,发现很多人都这样写的。我就觉得里面肯定有文章。用谷歌一搜,就发现已经有老外问到这个问题了。http://stackoverflow.com/questions/19410230/fread-fwrite-size-and-count 和 http://stackoverflow.com/questions/10564562/fwrite-effect-of-size-and-count-on-performance。

       从回答中可以看到,第一种写完更合理一点。首先,实现fwrite和fread函数的人不是傻蛋,其不会实现为:每次只写一个字节,写8912次 。最重要的是,在第一种写法中可以知道写了/读了多少字节。特别是在读的时候,很有必要。如果是第二种写法,在读的时候,只能返回0或者1,根本就不知道究竟读了多少字节。



C++11的编译选项

        C++11越来越流行了,有必要学一下C++11。这里和这里可以看到各个编译器对C++11的支持。安装了支持C++11的编译器gcc,还要加上编译选项-std=c++11。编译命令如下:

[cpp] view plain copy 在CODE上查看代码片派生到我的代码片
  1. g++ -std=c++11 test.cpp -o test  

        如果想在Windows下使用支持C++11的Mingw,可以到这里下载。

        对于Qt Creator,这个编译选项是在.pro文件添加的,添加下面内容即可:

[cpp] view plain copy 在CODE上查看代码片派生到我的代码片
  1. QMAKE_CXXFLAGS += -std=c++11  



strspn和strcspn函数

strspn:

        对于strspn,虽然有些文章进行了一些解说,但总是难于理解。自己写一个例子验证自己的理解,但结果并非自己预测的那样。所以我这里也来解说一下strspn,不过我是直接上strspn的实现代码。我就用glibc2.19中strspn的实现代码来解说。

[cpp] view plain copy 在CODE上查看代码片派生到我的代码片
  1. size_t  
  2. strspn (s, accept)  
  3. const char *s;  
  4. const char *accept;  
  5. {  
  6.     const char *p;  
  7.     const char *a;  
  8.     size_t count = 0;  
  9.   
  10.     for (p = s; *p != '\0'; ++p)  
  11.     {  
  12.         for (a = accept; *a != '\0'; ++a)  
  13.             if (*p == *a)  
  14.                 break;  
  15.         if (*a == '\0'//遍历完accept就返回  
  16.             return count;  
  17.         else  
  18.             ++count;  
  19.     }  
  20.   
  21.     return count; //遍历完s也返回  
  22. }  

        代码里面有两个循环,分别用来遍历两个字符串。


        我们看一下在什么时候该函数会返回。当*a == ‘\0’时,就会返回。也就是说当内循环遍历了整个accept字符都没有break时,就会发生*a == ‘\0’成立,也就返回了。没有break就说明:在整个accept字符串中都找不到一个字符等于*p。

        *p是什么,*p是另外一个字符串s的一个字符。此时联合内外循环应该可以理解为:用*p从s的第一个字符开始遍历,如果在accept中能找到等于*p的字符,那么就++count, 测试s的下一个字符。如果在accept中找不的话,就直接return count。如果s的字符都能找accept中找到,那么就返回s的长度。

        用一句简单的话来叙述,那就是:遍历字符串s,返回第一个只出现在s字符串的字符的下标值(在s中的下标)。

        对于s=”126.com”,accept=”1234567890”,返回值为字符”.”的下标,即3

        对于s=”126.com”,accept=”google.com”,返回值为字符”1”的下标,即0


        strspn函数有什么用途呢?假如accept是一个删除字符集合,并且想删除字符串s前面的一些属于删除字符集合的字符。此时strspn函数返回值就是一个下标,在该下标之前的字符统统删掉。

strcspn:

        函数strcspn和前面的strspn长得有点像,可以用strspn来帮助理解。下面给出它的实现代码。

[cpp] view plain copy 在CODE上查看代码片派生到我的代码片
  1. size_t  
  2. strcspn (s, reject)  
  3.      const char *s;  
  4.      const char *reject;  
  5. {  
  6.   size_t count = 0;  
  7.   
  8.   while (*s != '\0')  
  9.     if (strchr (reject, *s++) == NULL)  
  10.       ++count;  
  11.     else  
  12.       return count;  
  13.   
  14.   return count;  
  15. }  

        用一句简单的话来叙述,遍历字符串s,返回第一个既出现在s,也出现在reject字符的字符下标值(在s中的下标)。

        对于s=”hao123.com”,reject=”1234567890”,返回值为字符”1”的下标,即3




跨编译器include智能指针头文件:

        在tr1中,C++加入了神器share_ptr,但不同的编译器把智能指针share_ptr  放在不同的头文件中。比如VS系列编译器把它放到memory中,而gcc(包括mingw)则放到tr1/memory头文件。由于mingw编译器在使用的时候,会自动加入WIN32这个宏,所以用这个宏来跨编译器使用share_ptr是不行的了。解决方案是使用宏_MSC_VER。这个宏是用来标明VS系统编译器版本的,当然在mingw中就不会存在的。所以可以像下面那样跨编译器使用include智能指针头文件。

[cpp] view plain copy 在CODE上查看代码片派生到我的代码片
  1. #ifdef _MSC_VER  
  2. #include<memory>  
  3. #else  
  4. #include<tr1/memory>  



getopt和getsubopt命令行解析函数:

        有时候我们写的程序也想像Linux的一些命令一样,可以有很多参数,这时我们就需要在自己的程序里面解析这些参数。Linux提供了两个函数getopt和getsubopt可以帮助我们完成解析操作。


getopt:

        先来看一下命令行参数是怎么使用的。$ps -e$find -name main.c这两个命令都使用了命令行参数。减号-表示一个命令行参数的开始,紧挨着-的是选项。像上面的e和name就是一个选项。其中选项e是没有参数值的,而选项name是有参数值的,为main.c。有一些选项既可以有参数值,也是可以没有参数值的。上面的-name选项就必须要有参数值。

        现在来看一下getopt函数的声明。

[cpp] view plain copy 在CODE上查看代码片派生到我的代码片
  1. #include <unistd.h>  
  2. extern char *optarg;  
  3. int getopt(int argc, char * const argv[], const char *optstring);  

        参数argc和argv是main函数的两个参数,而参数optstring指明要在argv中查找哪些选项。假如optstring的值为"ab:c:de::",那么就说明会在argv中查找a、b、c、d、e这5个选项(不错,选项都是一个字符的)。其中b、c选项后面带有一个冒号:,说明b和c选项都要有一个参数。选项e后面有两个冒号,那么e选项既可以有参数也可以没有参数。选项a和d都没有冒号,说明这两个个选项都是没有参数的。


        函数getopt一般是和循环语句一起使用的,使用如下:
[cpp] view plain copy 在CODE上查看代码片派生到我的代码片
  1. int main(int argc, char** argv)  
  2. {  
  3.     int opt;  
  4.   
  5.     while( (opt = getopt(argc, argv, "ab:c:de::")) != -1 )   
  6.     {  
  7.         switch( opt )  
  8.         {  
  9.             case 'a' :  break;            
  10.             case 'b' :  printf("parm = %s", optarg); break;  
  11.             case 'c' :  break;  
  12.             case 'd' :  break;  
  13.   
  14.             case 'e' :    
  15.                     if(optarg)  
  16.                     {  
  17.                         printf("option e has parm\n");    
  18.                     }  
  19.                     break;  
  20.   
  21.             default : //出现了不是选项的字符  
  22.         }  
  23.     }  
  24. }  

        可以看到,getopt函数的返回值就是对应选项的ASCII值。当查找完所有的命令行参数后就返回-1。

        如果一个选项是有参数的,那么全局变量optarg就会指向这个参数字符串的开始位置。从上面的代码也可以看到,如果选项e有参数,那么optarg就不等于NULL,否则就等于NULL。


getsubopt:

        getsubopt函数一般是和getopt函数配合使用的,用来处理某一个选项的参数。当一个选项的参数比较复杂时,就可以使用getsubopt函数。比如命令./test -o ro,rw,name=main.c。-o表示一个选项。而后面的那些都是该选项的参数,挺复杂的。选项o有三个子选项ro、rw和name,而只有name有参数值main.c。这些子选项之间用逗号分开,不能用空格。

        看完了使用,现在看一下getsubopt的原型

[cpp] view plain copy 在CODE上查看代码片派生到我的代码片
  1. #include <stdlib.h>  
  2. int getsubopt(char **optionp, char * const *tokens, char **valuep);  

        参数tokens的修饰符有点怪怪的,不管了。先看一个例子吧。

[cpp] view plain copy 在CODE上查看代码片派生到我的代码片
  1. int main(int argc, char **argv)  
  2. {  
  3.     enum {  
  4.         RO_OPT = 0,  
  5.         RW_OPT,  
  6.         NAME_OPT  
  7.     };  
  8.     char *const token[] = {  
  9.         [RO_OPT]   = "ro",  
  10.         [RW_OPT]   = "rw",  
  11.         [NAME_OPT] = "name",  
  12.         NULL  
  13.     };  
  14.     char *subopts;  
  15. char *value;  
  16. int err = 0;  
  17.     int opt;  
  18.   
  19.     while ((opt = getopt(argc, argv, "aeo:")) != -1) {  
  20.         switch (opt) {  
  21.         case 'a'break;  
  22.         case 'e'break;  
  23.   
  24.         case 'o':  
  25.             subopts = optarg;//optarg指向参数字符串的开始位置  
  26.   
  27.             while (*subopts != '\0' && !err) {  
  28.                 //getsubopt会修改subopts的值  
  29.                 switch (getsubopt(&subopts, token, &value)) {  
  30.                 case RO_OPT:  
  31.                     if( value )  
  32.                         printf("ro parm = %s\n", value);  
  33.                     else  
  34.                         printf("ro\n");  
  35.                     break;  
  36.   
  37.                 case RW_OPT:  
  38.                     if( value )  
  39.                         printf("rw parm = %s\n", value);  
  40.                     else  
  41.                         printf("rw\n");  
  42.                     break;  
  43.                     break;  
  44.   
  45.                 case NAME_OPT:  
  46.                     if( value )  
  47.                         printf("name parm = %s\n", value);  
  48.                     else  
  49.                         printf("name\n");  
  50.                     break;  
  51.               
  52.                 default:  
  53.                     err = 1;  
  54.                     break;  
  55.                 }  
  56.             }  
  57.         }  
  58.     }  
  59.   
  60.     return 0;  
  61. }  

        从上面代码可以看到,要想使用getsubopt就需要使用定义一个二维字符数组。数组里面的字符串就是命令行参数里面的子选项值。如果查找到了一个子选项,那么getsubopt函数返回这个子选项在二维数组里面的下标值。而getsubopt的参数valuep则会指向对应子选项的参数值。

 

 

参考:http://man7.org/linux/man-pages/man3/getopt.3.html

           http://man7.org/linux/man-pages/man3/getsubopt.3.html





typedef类型的前置声明:

        如果某个结构体类型如下定义:

[cpp] view plain copy 在CODE上查看代码片派生到我的代码片
  1. typedef struct CvScalar  
  2. {  
  3.     double val[4];  
  4. }  
  5. CvScalar;  

        那么前置声明可以写成下面的形式:
[cpp] view plain copy 在CODE上查看代码片派生到我的代码片
  1. struct CvScalar;  
  2. typedef struct CvScalar CvScalar;  


        但是,如果某个结构体在定义的时候根本就没有名字,只是用typedef声明了一个别名,如下:

[cpp] view plain copy 在CODE上查看代码片派生到我的代码片
  1. typedef struct  
  2. {  
  3.     int width;  
  4.     int height;  
  5. }  
  6. CvSize;  

        此时,就无法进行前置声明了(我google了很久都没有找到能前置声明的方法。如果读者有解决方案,还请赐教)。


        虽然没有办法直接进行前置声明,但我还是进行了一个可行性尝试。不确保有可移植性。

        首先,这个结构体是定义在OpenCV的cv.h头文件的,并且cv.h使用宏__OPENCV_CORE_TYPES_H__防止重复包含。我就在自己的头文件里面加入

[cpp] view plain copy 在CODE上查看代码片派生到我的代码片
  1. #ifndef __OPENCV_OLD_CV_H__  
  2. typedef struct  
  3. {  
  4.     int width;  
  5.     int height;  
  6. }CvSize;  
  7. #endif  

        不错,就是在自己的头文件里面定义这个类型。为了避免重复定义,在对应的源文件还需要先包含OpenCV的cv.h头文件,然后才能包含自己的头文件。



数组的数组:

        独立定义几个数组,为了便于对这些独立数组统一在一个循环里面访问,需要将这些独立的数组作为另外一个数组的元素。可以使用下面的方式组装。

[cpp] view plain copy 在CODE上查看代码片派生到我的代码片
  1. int main()  
  2. {  
  3.     int a[2] = {1, 2};  
  4.     int b[2] = {3, 4};  
  5.     int c[2] = {5, 6};  
  6.   
  7.     int *d[3] = {a, b, c};  
  8.   
  9.     cout<<d[0][1]<<endl;  
  10.   
  11.   
  12.     int aa[2][2] = { {1, 2},  
  13.                      {3, 4}  
  14.                    };  
  15.   
  16.     int bb[2][2] = { {5, 6},  
  17.                      {7, 8}  
  18.                    };  
  19.   
  20.     int cc[2][2] = { {9, 10},  
  21.                      {11, 12}  
  22.                    };  
  23.   
  24.     int (*dd[3])[2] = {aa, bb, cc};  
  25.     cout<<dd[0][1][0]<<endl;  
  26.   
  27.     return 0;  

0 0
原创粉丝点击