C_basis

来源:互联网 发布:win10 修改intel mac 编辑:程序博客网 时间:2024/05/29 15:13
/*----------------------------------------------------------------------------*/
/*                                  C 基础                                    */
/*----------------------------------------------------------------------------*/


/*          目录          */
0.  基本数据类型
1.  数组
2.  字符串
3.  指针
4.  结构体和联合
5.  表达式
6.  函数
7.  动态内存
8.  预处理
9.  输入输出
10. 编码规范


/*          正文           */

/*------------------------------------*/
0.  基本数据类型

0.1
    不管是 32 位机或是 64 位机,int 都是 32 位。而 long 在 32 位机上是 32 位,在 64 位机上是 64 位。
    下面是在 32 位机上各个基本数据类型的大小(字节)。
char        1
short       2
int         4
long        4
void*       4
float       4
double      8
long long   8
lonb double 12

0.2
    如下表达式将不会得到想要的结果。
int a = 100;
inb b = 3;
double c = a / b;

    虽然得到的 c 值是以 double 的形式表示,不过结果却被截断了,只是 33.000000。正确的应该在 a 前面加强制转换。
double c = ( double )a / b;
   
0.3
    在 C 里面字符常量,像 'a' 是一个 int 型,所以 sizeof( 'a' ) == sizeof( int )。

0.4
    在使用 sizeof() 的时候, char *a 只是作为一个指针的大小,而 char a[] 则会根据初化的字符串的个数得到整个数组的大小。
    作为数组指针 char (*p)[ 10 ],当 sizeof( *p ) 的时候,将能够返回 p 所指向的数组的大小 10。

0.5
    以 0 为开头的数字常量是以 8 进制表示的。

0.6
    在现在的 VS 和 GCC 里面,两个数相除时,余数的绝对值小于除数。

0.7
    可以通过查看 <limits.h> 中的 CHAR_MIN 来知道当前编译环境当中 char 类型是否是 unsigned 的。

0.8
    在 <stdint.h> 当中有一系列 intN_t 形式的基本类型。可以通过 N 来确定类型的大小。

0.9
    几种后缀代表的类型:
U       unsigned
L       long
UL      unsigned long
LL      long long
LLU     unsigned long long
F       float

    几种前缀代表的类型:
\       octal               // \10 == 8
\x      hexadecimal         // \x10 == 16
\u      universal  

1.0
    有符号数和无符号数之间不要比较大小。


/*------------------------------------*/
1.  数组

1.1
    数组指针:
int array[ 10 ];
int (*p)[ 10 ];         // 一个指向有 10 个 int 数组的指针
p = &array;             // 让 *c 指向数组 a
*p[ 0 ] = 1;            // 这样子就能改变数组 a 里面的值

int array[ 10 ][ 2 ];
int (*p)[ 2 ];
p = &array[ 0 ];
*p[ 0 ] = 1;

1.2
    在向一个函数传递一个多维数组的时候,该函数的参数声明中,仅仅是多维数组的第一维个数可以被省略,而后面的个数是必须的。
void f( int a[][ 10 ] )
{
    ...
}

int main()
{
    int a[ 10 ][ 10 ];
    f( a );
   
    ...
}

// void f() 也可以这样定义:
void f( int (*a)[ 10 ] )
{
    ...
}


/*------------------------------------*/
2.  字符串

2.1
    这样的字符数组是可以被修改的。
char[] a = "aaa";
a[ 0 ] = 'b';

    这样的字符指针不能被修改。运行时会出 core。
char* a = "aaa";
a[ 0 ] = 'b';

2.2
    字符数组的声明和定义必须在一起,而字符串指针则不用。
char[] a = "aaa";
char* b;
b = "bbb";

2.3
    避免使用下面形式的定义:
char a[ 3 ] = "abc";

    因为这样的定义中 a 并不是一个真正意义上的 C 字符串,因此它不能用在跟字符串相关的函数中,如 printf(),strcpy() 等。

2.4
    一些有趣的字符串特性。
char string[] = "aaaaa""bbbbb";     // string == "aaaaabbbbb";
char string[] = "\x30""a";          // string == "0a";

char string[] = "aaaaa\
bbbbb";                             // string == "aaaaabbbbb";

char string[] = "aaaaa\
    bbbbb";                         // string == "aaaaa     bbbbb";

char string[] = "aaaaa"
                "bbbbb";            // string == "aaaaabbbbb";


/*------------------------------------*/
3.  指针

3.1
    p 所指向的字符不能更改:
const char* p;
char const *p;

    指针 p 本身不能更改,而指定的字符可以更改:
char* const p;

    简单地说,如果 const 没有跟指针直接放在一起的话就是指向的内容不能更改,如果跟指针直接放在一起则指针本身不能更改。


/*------------------------------------*/
4.  结构体和联合

4.1
    下面这种定义在 C 下面是错的,而在 C++ 下是允许的,它能把结构标签自动生成类型。
struct x
{
    ...
};

x x1;

    在 C 下面要用这种定义:
struct x x1;

    下面这种定义是正确的:
typedef struct
{
    ...
}x;

x x1;

    而这样又是不正确的:
struct x x1;

4.2
    结构体会自动对齐数据,因此当 sizeof() 的时候得到的数值可能会比定义的数据要大,在定义结构体的时候把大的数据放在前面,小的数据放在后面,可以减少空间。

4.3
    使用 gcc 的话可以通过 __attribute___ 扩展来禁止字节对齐。
struct A
{
    char    c;
    int     n;
}__attribute__(( packed ));     // sizeof( struct A ) == 5;

    其实最简单的取消字节对齐的方式就是强制 1 字节对齐。这种方法不管是对 gcc 还是 VS 都同样适用。
#pragma pack( 1 )

4.4
    默认情况下, VS 和 gcc 的 pack(n) 的 n 值是 4。
struct A
{
    char c;
    int a;
    char b;
};

// VS   sizeof( struct A ) == 12;
// gcc  sizeof( struct A ) == 12;

4.5
    两个 struct 即使内部数据定义是一模一样的,编译器还是会把它们当作两个不同的数据结构,直接相互转换的话会发出警告。

4.6
    在一个 struct 里面不能自包含自己,不过可以自包含自己的指针,就像链表、树等数据结构。

4.7
    关于 struct 里面的位段功能。位段功能只能用于整数,包括有符号和无符号整数。并且位段的大小要小于一个整数所占的位数,这样才有意义。
    比如在 32 位机下,下面两个 struct 的大小是不一样的。
struct A
{
    int a;
    int b;
    int c;
};          // sizeof( A ) == 12

struct B
{
    int a   :16;
    int b   :16;
    int c;
};          // sizeof( B ) == 8

    使用位段功能编写的程序是不可移植的。

4.8
    对联合进行初始化赋值是对第一个变量进行的,所以设定的值必须符合第一个变量的类型,不然编译器会给出警告。
union A
{
    int a;
    char c;
} AA = { 1.1 };     // warnning

4.9
    结构体之间可以直接赋值,而数组是不行的。
struct A a, b;
a = b;                  // OK

int a[ 10 ], b[ 10 ];
a = b;                  // NO


/*------------------------------------*/
5.  表达式

5.1
    == 的优先级高于 =。
x = y == z
x = (y == z)

5.2
    对于 typedef 的类型不能跟其它类型混用,如下面的声明是错误的:
typedef int banana;
unsigned banana i;

5.3
    强制转换后的变量是一个右值,不能再进行赋值操作,如下面的代码是错误的:
((int *)p)++;       // NO

5.4
    逻辑移位是直接以 0 补充,而算术移位会使用原来标志位上的值。
    右移操作符使用的是逻辑移位还是算术移位是由编译器决定的。因此如果一个程序有右移位的表达式,那么该程序是不可移植的。

5.5
    在 C 语言里面 if() 语句的比较默认处理是:
if ( expresstion != 0 )

5.6
    由双引号括起来的符号只会被当作普通字符,像注释符在 "" 里面并不会被当作真正的注释符。

5.7
    在 switch 的 case 里面的常量不能是指定的 const 常量,那样编译器会报错。不过可以使用 enum。


/*------------------------------------*/
6.  函数

6.1
    在使用函数指针的时候可以不用解引用,两种方法是一样的。
int (*fp)();
fp();

int (*fp)();
(*fp)();

6.2
    在声明函数指针的时候可以不用参数。
int f( int i )
{
    ......
}

int (*fp)();
int (*fp)( int i );


/*------------------------------------*/
7.  动态内存

7.1
    在 malloc() 一个结构体的时候,如果里面包含有指针的话也要对每一个指针 free() 一次。也就是说对于每一个 malloc(),要有一个相应的 free()。

7.2
    在执行 free() 之后,对于 free() 的对象最好赋值为 NULL。这样可以避免引用到空指针,虽然 free() NULL 并不会怎么样。

7.3
    不要在一个指针还没释放内存的时候再对它进行 malloc() 的赋值,这样会使得它之前所指向的内存没办法再通过指针访问或释放。


/*------------------------------------*/
8.  预处理

8.1
    粘贴标识符,可以把两个字符合在一起。
#define PASTE( a, b ) a##b
int PASTE( a, b ) = 10;
printf( "%d\n", ab );

8.2
    几个预定义好的宏。
__DATE__            // 编译时间,格式为“Mmm dd yyyy”
__FILE__            // 源文件名。在 VS 下是绝对路径名,而在 gcc 下就只是相对路径名
__FUNC__            // __FUNC__ 所在的函数名
__LINE__            // __LINE__ 所在的行号
__STDC__            // 如果是标准C就是常量 1
__STDC_HOSTED__     // 如果操作系统存在则为 1
__TIME__            // 编译链接的时间,格式为“hh:mm:ss”

8.3
    __FILE__ 和 __LINE__ 的值可以由 #line 来控制。
#line 10 "a.c"
printf( "%d    %s\n", __LINE__, __FILE__ );     // 10    "a.c"

8.4
    使用宏定义表达式时,不仅对每一个参数都要加括号,对整个表达式本身也要加括号
#define MULTI( x )    ( (x) * (x) )

8.5
    # 操作符在宏定义里面的作用是把参数作为一个字符串看待。
#define PRINT( VALUE )      printf( "The value of " #VALUE " is %d\n", VALUE )
int value = 10;
PRINT( value );     // printf( "The value of " value " is %d\n", 10 )

8.6
    编译器会用一个空格替换注释 /*...*/。因此下面的表达式是相等的:
int a;
int/*a*/a;

8.7
    在 C99 里面可以在 #define 的时候使用不定的变量个数
#define print_log( ... ) fprintf( stderr, __VA_ARGS__ )
print_log( "%s %s\n", "a", "b" );       // fprintf( stedrr, "%s\n", "a", "b" )

8.8
    #error 指令,一般用在条件编译的时候用,如果不满足条件编译则编译器会报告 #error 以及后面错误的消息。在 VS 下面不能用。
#ifndef __STDC__
#error "This compiler does not conform to the ANSI C standard."
#endif


/*------------------------------------*/
9.  输入输出

9.1
    用 printf() 输出 %, 只要连续两个 %% 就可以了。

9.2
    用 printf() 输出单精度或双精度时只要用 %f 就可以,而用 scanf() 的时候读入双精度的话要用 %lf。

9.3
    在 printf() 里面 # 可以对格式进行微调, 可以用 * 来指定要输出的域宽。
printf( "%*d\n", 10, 5 );       // 会先输出 9 个空格后再输出 5。


/*------------------------------------*/
10.  编码规范

10.1
    在代码的最后留一行空行。
    虽然好像现在在 VS 或 GCC 上不多一个空行都没问题,不过有的编译器是以换行符为一个语句的终结,所以如果在最后一句表达式没有多一个换行符的话可能会出错。

10.2
    在进行函数调用之前,对于要传递进去的参数,如果是临时变量的话最好赋初始值,特别是对于指针型函数,因为有些库函数会直接对传递进来的指针参数进行访问。

10.3
    对于一个表达式,如果包含比较复杂的运算符时,最好用括号进行区分。

10.4
    下面一些代码具有不确定性,在写程序的过程应该避免。
a[ i ] = i++;
i++ * i++;
i = i++;

10.5
    虽然说在定义结构体的时候,把变量按大到小进行定义可以避免由于自动对齐而造成的空间浪费。不过最好还是以代码的清晰为主要考虑点,除了在必要优化的时候。

10.6
    在声明一个函数的时候,如果该函数没有参数的话就最好在参数列表里面加 void。这样子能更好地区别函数。例如:

10.7
    在调用一个函数的时候,如果程序没有找到该函数的声明,那么它会认为该函数在别的地方进行定义的,不过它会默认地认为该函数是返回 int 型的。
    这样就会有一个问题,如果一函数并不是返回 int 型的,那么在使用该函数之前没有进行正确声明的话那么调用该函数就很有可能会出错。
// a.c
int main()
{
    float a;
    a = f();
    printf( "%f\n", a );
   
    return 0;
}

// b.c
float f()
{
    return 1.2345;
}

    在这种情况下, float a 的值是不正确的。因此最好在使用到一个函数之前都对该函数进行声明。
// a.c
extern float f();

int main()
{
    float a;
    a = f();
    printf( "%f\n", a );
   
    return 0;
}

10.8
    在定义函数名的时候最好不要跟系统的一些常用函数名重合,这样可能会导致很难发现的错误。

10.9
    在传递参数的时候,如果传递的是指针的话,如果不需要改变指针所指向的内存的值,那么给参数加上 const。
这样做大多数情况并不是真的怕在函数里面不小心改变了原来并不想改变的指针指向的内存。更多的情况是表明了这样一个事实:
可以放心地向这个参数传递指针,该指针只不过是用来传递一个变量,或是为了节省内存传递一个大的数据结构,而不是为了改变该指针所指向的内存的值。
    在上述前提下,如果一个函数的参数是指针类型并且不是为 const 的,
那么可以认为向该函数传递指针的目的就是为了改变指针的内容,或者是说为了获取函数的其它返回值。

10.10
    在声明一个函数的时候,虽然可以省略参数的名称,不过最好还是不要省略。这样子能够更好地了解该函数要传递的参数的内容。

10.11
    在编译的时候最好让编译器最大限度地提出警告。
-Wall -Wextra

原创粉丝点击