C/C++编程心得

http://antkillerfarm.github.io/

参考资料

http://www.cplusplus.com/

这个网站可以查到C++的标准库的用法。

头文件被多个源文件引用

在头文件中定义字符串时,如果该头文件会被多个源文件引用的话,字符串必须被定义为const,否则会有重定义错。当然最好在头文件中只放声明,不要放定义。

使用诸如

#ifndef _COMMON_STRING_
#define _COMMON_STRING_
...
...
...
#endif

解决不了这个问题。因为这段代码解决的是同一个源文件重复包含某个头文件的问题。而这里的问题是不同的源文件包含同一头文件时,产生的重复定义的问题。const关键字保证了同一标识符只会定义一次。

指针数组与数组指针

int (*p)[10] 先是 *p起作用 ,声明一个指针(p肯定是指针了),然后是[10](数组说明),则p是指向10个整形元素组成的数组的指针。——数组指针。

若是int *p[10] 则首先是[ ]起用,它是一个数组了。然后是*,所以这个数组元素是指针型的。——指针数组。

引申一下

    int a[10];
    printf("%x\n",&a);
    printf("%x\n",a);

可以看到&a和a的值是相等的,但含义不同。a相当于int *p,而&a相当于int (*p)[10]

类似的

int (*p[])(int)函数指针数组。

int (*p())[]返回数组指针的函数。

int *p()[]字面上可以解释为返回指针数组的函数,不过函数是不能返回数组的。

int *(*a())()这是一个函数,它没有参数,它的返回值是一个函数指针,这个指针指向的函数,也没有参数,且返回值是int型的指针。

嵌入式程序员应知道的几个基本问题

http://linux.chinaitlab.com/c/713810.html

深入理解C++中的mutable关键字

http://dev.yesky.com/393/3007393.shtml

UNREFERENCED_PARAMETER的作用

http://blog.csdn.net/apunix/archive/2008/01/14/2043945.aspx

sizeof进行结构体大小的判断

http://blog.csdn.net/van150/archive/2005/12/05/544454.aspx

VC的默认规则基本如上所示,gcc的默认规则就是4字节对齐。

可以比较一下

struct s
{
  char a;
  double b;
  int c;
}

struct s
{
  char a;
  int b;
  double c;
}

在各个平台上的sizeof值。

附带说一下如果看到以下的代码片段,也就不要觉得惊奇了。

typedef enum _XXX
{
    XXX_0        = 0,
    XXX_1        = 1,
    XXX_FORCE_DWORD    = 0x7FFFFFFF/* 编译器对齐 */
}XXX;

字节对齐的设定

1)编译器选项指定

VC:/Zp

gcc:-fpack-struct

2)代码指定

VC和gcc都可以用#pragma pack(4)

gcc还可以用__attribute__((packed))

Java中创建对象的时候代码执行顺序(这个问题C++应该也存在)

http://jacob777.blog.sohu.com/106426297.html

宏展开函数的小技巧

有的时候,会使用宏展开的方式定义“伪”函数。

这些“伪”函数有以下特征:

  1. 不是真正的函数,而是接受参数的宏。

  2. 形式上与普通函数相同。

为了实现这个功能,教科书上给出的做法是这样的:

#define st(x)      do { x } while (0)
#define HAL_DMA_SET_SOURCE( pDesc, src ) \
  st( \
    pDesc->srcAddrH = (uint8)((uint16)(src) >> 8); \
    pDesc->srcAddrL = (uint8)( (uint16)(src) & 0xFF ); \
  )

但是do { x } while (0)在有些编译器上会报warning。最近看Ti的代码的时候,看到了一种更好的办法:

#define st(x)      do { x } while (__LINE__ == -1)

C++ 11的新特性

我是在2003年以后学习C++的,后来直到2009年以前,C++都是我的主要工作语言。但由于本人先学的C语言,所以编程的思想一直是函数式的。完成一项任务,无论用C、C++、Java、C#,还是Matlab、Python,风格都是差不多的。这也是后来我对GTK情有独钟的一个重要的原因。

总的来说,我对C++用的多,但理解的却不深。只对单继承、成员函数的封装、访问之类的概念有一定的认识和使用。多重继承、模板会看不会写。更不必提C++ 03和C++ 11的新特性了。最近因为研究Cocos2d-x,而接触到这些新特性,颇有些感觉到自己已经是老古董了.

不过好在Java语言用的还可以,大部分的C++新特性,学起来倒也难不倒我。

1.auto关键字

C++11中引入的auto主要有两种用途:自动类型推断和返回值占位。auto在C++98中,标识临时变量的语义,由于使用极少且多余,在C++11中已被删除。前后两个标准的auto,完全是两个概念,这点尤其需要注意。

http://blog.csdn.net/huang_xw/article/details/8760403

2.引入nullprt

http://blog.csdn.net/huang_xw/article/details/8764346

3.仿函数、lambda表达式和闭包

http://www.cnblogs.com/npbool/p/3434757.html

4.C++、Java和C#的lambda表达式的格式

C++

auto func = [=](int x, int y)->int {return x * y;};

Java

Runnable r1 = (int x, int y) -> { return x * y; }

C#

Func towParams = (x, y) => x * y

添加废弃提示

有些类库出于兼容性的考虑,仍然保留了对旧函数的支持。但是继续使用这些函数,显然不是作者的初衷。因此,有必要在编译时,给出废弃的提示。

近日浏览cocos2d-x v3的代码,发现可以这样做:

宏定义:

#if defined(__GNUC__) && ((__GNUC__ >= 4) || ((__GNUC__ == 3) && (__GNUC_MINOR__ >= 1)))
#define CC_DEPRECATED_ATTRIBUTE __attribute__((deprecated))
#elif _MSC_VER >= 1400 //vs 2005 or higher
#define CC_DEPRECATED_ATTRIBUTE __declspec(deprecated)
#else
#define CC_DEPRECATED_ATTRIBUTE
#endif

宏使用:

CC_DEPRECATED_ATTRIBUTE static TextureCache * getInstance();

关于引用的一个常见错误

class Base
{
  public:
    void something(Base& b){}
};

int main()
{
  Base b;
  b.something(Base());
  return 0;           
}

上面的代码在编译时,会出现如下错误信息:

abc.cpp:12:20: error: no matching function for call toBase::something(Base)abc.cpp:12:20: note: candidate is:
abc.cpp:6:7: note: void Base::something(Base&)
abc.cpp:6:7: note: no known conversion for argument 1 fromBasetoBase&’

这是由于Base()生成的是临时变量,将之赋值给一个non-const的引用是不行的。

解决方法是

void something(const Base& b){}

可以参看下文:

http://stackoverflow.com/questions/20247525/about-c-conversion-no-known-conversion-for-argument-1-from-some-class-to

常用libc实现

libc是C语言标准库的简称,它有多种实现。除了最常用的gcc自带的glibc之外,还有musl、uClibc、dietlibc等。

http://www.etalabs.net/compare_libcs.html

这个网址是以上4种libc实现的比较结果。从结果来看,musl比较有投资价值。实际上,最近(2015.5)的OpenWrt项目就已经将libc由uClibc改为musl。我也是因为这个原因,才知道musl的。

当然这个表并不完整,其他的libc可以参见:

https://en.wikipedia.org/wiki/C_standard_library

数组定义时的赋值方法

struct mtd_partition {
    const char *name;       /* identifier string */
    uint64_t size;          /* partition size */
    uint64_t offset;        /* offset within the master MTD space */
};

static struct mtd_partition parts[] = {
      {name: "boot", offset: 0,           size:0x500000,},
      {name: "setting", offset: 0x500000, size:0x300000,},
      {name: "linux", offset: 0x800000,   size:0x500000,}, 
      {name: "config", offset: 0xd00000,   size:0x100000,}, 
      {name: "rootfs", offset: 0xe00000,  size:0x3200000,},
      {name: "app", offset: 0x4e00000, size:0x800000,},

};

static struct resource pxa27x_resource_ohci[] = {
    [0] = {
        .start  = 0x4C000000,
        .end    = 0x4C00ff6f,
        .flags  = IORESOURCE_MEM,
    },
    [1] = {
        .start  = IRQ_USBH1,
        .end    = IRQ_USBH1,
        .flags  = IORESOURCE_IRQ,
    },
};

这些方法虽然不知道是C的那个标准引入的,但是见到有代码这样用。

switch…case

switch (reg) {
       case TAS5086_CLOCK_CONTROL ... TAS5086_BKNDERR:
       return 1;
       case TAS5086_INPUT_MUX:
       case TAS5086_PWM_OUTPUT_MUX:
       return 4;
}

可以用上面的办法,少写一些case语句。

动态链接

这两天实践了一下怎样在linux下创建动态链接。感觉网上的资料虽然翔实,但仍然有疏漏之处。

1)g++和gcc的区别

本来只想给链接的,以显示这不是我的原创。但是现在的链接失效的也太快了。。。囧

只好拿华丽的分隔符来表示引用的内容。


误区一:gcc只能编译c代码,g++只能编译c++代码

两者都可以,但是请注意:

1.后缀为.c的,gcc把它当作是C程序,而g++当作是c++程序;后缀为.cpp的,两者都会认为是c++程序,注意,虽然c++是c的超集,但是两者对语法的要求是有区别的。C++的语法规则更加严谨一些。

2.编译阶段,g++会调用gcc,对于c++代码,两者是等价的,但是因为gcc命令不能自动和C++程序使用的库联接,所以通常用g++来完成链接,为了统一起见,干脆编译/链接统统用g++了,这就给人一种错觉,好像cpp程序只能用g++似的。

误区二:gcc不会定义__cplusplus宏,而g++会

实际上,这个宏只是标志着编译器将会把代码按C还是C++语法来解释,如上所述,如果后缀为.c,并且采用gcc编译器,则该宏就是未定义的,否则,就是已定义。

误区三:编译只能用gcc,链接只能用g++

严格来说,这句话不算错误,但是它混淆了概念,应该这样说:编译可以用gcc/g++,而链接可以用g++或者gcc -lstdc++。因为gcc命令不能自动和C++程序使用的库联接,所以通常使用g++来完成联接。但在编译阶段,g++会自动调用gcc,二者等价。


因此,某些时候编不过去,可以试试换换cc的值。

2)gcc4.1.1下似乎对类型检查严了一些,dlsym返回的void*类型不能转换为相应的函数指针类型,需要强制转换。某些网上的例子在这里编不过去。

3)显式调用时,要注意动态库函数的声明,可能要加extern "C"才能正常执行。(显式调用是运行时加载,所以编译能过,执行却不对了。)可以用nm命令看看链接库的符号表,以确定问题所在。

4)链接的时候需要注意文件的顺序。

比如下面的例子:

https://github.com/antkillerfarm/antkillerfarm_crazy/tree/master/helloworld/linux_so

gcc -o main_link main_link.c -L. -lhello

这条命令中的main_link.c如果放到-lhello之后就会出问题。也考虑使用--start-group--end-group之类的链接选项解决链接顺序问题。

cout格式化输出

cout<//输出十六进制数
cout<//输出八进制数
cout<//输出十进制数

strtok

strtok函数多用于分割字符串,但它会改变被分割字符串的值。因此,如果该字符串以后还有用的话,需要首先复制该字符串,然后对复制的字符串执行strtok函数。

K&R风格

在C语言的函数定义上,我们通常用的函数定义方式为ANSI C的函数定义方式。但是在C语言之父创立C语言之时,函数的定义形式并非现在我们所见到的形式。

这种风格被称为K&R风格,多见于一些历史悠久的项目或者老的书籍中。出于兼容性考虑,现代的C编译器仍然支持K&R风格。

详见:

http://blog.chinaunix.net/uid-7426920-id-2627743.html

你可能感兴趣的:(C/C++编程心得)