Linux C编程(含C陷阱与缺陷笔记)

1、 warning: incompatible implicit declaration of built-in function 'memset'
       缺少头文件:加入
#include<string.h>

2、结构体的动态内存分配:
 结构体:
struct [小标签名称]{成员声明列表};
注意的几点:
 1)不能在成员声明列表里初始化赋值。
 2)如果声明一个结构体变量,如struct struct_name aa; 那么aa已经分配好内存了。可以在后面直接对aa的成员赋值(如果该成员是指针,还是需要对这个指针分配内存后才可赋值的)。 但是,如果是声明一个结构体指针,如sturct struct_name *aa; 那么,在对其成员赋值前,需要 先 用calloc或者malloc分配动态内存;如果成员变量是指针,则需要进一步对该成员变量分配内存。
问题: 
        否则为该结构体的成员赋值时会发生段错误:
       例如 我定义了结构体 struct tm *p_tm2; //tm 结构体为linux的“time.h”内置的时间数据结构
                如果直接为该结构体赋值“p_tm2->tm_sec = 10; ……”则编译运行都会通过,但是会出现段错误。 
解决:
在定义了   struct tm *p_tm2;后 为该结构体分配动态空间:p_tm2 = (struct tm *)malloc(sizeof(struct tm)); 并且要加入头文件“stdlib.h”否则会有1、中缺少头文件的问题。

 3、指针:
 1)
      a.  直接为一个指针赋值是错误的 (能通过编译,但是会出现段错误):
            
   int *p;
    *p = 5; 
指针变量必须初始化后才能使用。

    b.            
char *p;
p = "abc";
p[0] = 'A';   //这句编译通过但是段错误,c不允许字符串常量进行修改。
              //对字符串常量修改的行为是未定义的。

2)
      a.   
int a[] = {1, 2, 3};   //a为一个数组
int *p;                    //p为int型的指针
         则 把数组下标为0的元素的地址赋值给p的语句是:  p = a;  不要使用p = &a; ,因为&a是一个指向数组的指针,而p是一个指向整型变    量的指针,它们的类型不匹配,编译会出现警告”warning: assignment from incompatible pointer type“。
         除了a被用做运算符sizeof的参数外,其他情况下数组名a都表示指向数组a中下标为0的元素的指针。sizeof(a)等于12(三个int型数据的大小)。
 
     b. 
<pre name="code" class="cpp">//这里声明了数组的数组(c语言仅有一维数组,不存在二维数组的说法)
int a[2][3] = { {1, 2, 3}, {4, 5, 6}};
//以下是声明了一个指向这类数组的指针,并使用该指针获取数组a的元素。
int (*p)[3];                            
p = a;                             
printf("%d", **p); 
<pre name="code" class="cpp"><pre name="code" class="cpp">/*  不能用一个普通指针int *q;指向数组a,即q = a;是错误的,
因为q是一个指向整型变量的指针而a表示一个指向数组的指针。 */
 
 
 
 
 
  
 3)       用单引号引起的一个字符实际表示一个整数;       用双引号引起的一个字符串表示的却是一个指向无名数组起始字符的指针。 该数组被双引号之间的字符以及一个额外的二进制值为零的字符‘\0’初始化。4)函数指针:      a.   int *g();    由于()的优先级高于*,因此*g()也就是*(g()) :g是一个函数,该函数的返回值是一个指针(返回值类型为指向整型数的指针)。             
 *p() 等价于 *(p())  等价于 *((*p)())    
       b.   int (*g)();  g是一个函数指针(指向函数的指针),g指针所指向的返回值为整型。g是一个指向返回值为整型的函数的指针。那么 (int (*)()) 表示的是”指向返回值整型的函数的指针“的类型转换。      c.   若ptr_func是一个函数指针,那么调用ptr_func所指向的函数function的语句是:(*ptr_func)();如: 
 
void function() {                 //定义了一个函数function
     printf("In function()\n");
} 
void (*ptr_func)() = function;  //定义了一个函数指针ptr_func,该指针指向函数function
(*ptr_func)();                // 对ptr_func所指向的函数进行调用;调用该函数可以简写为ptr_func();但是这仅仅是简写。

     d.   对  (*(void(*)())0)()  的解析: 
          (void(*)()) 0 :将0转化为”指向返回值为void的函数的指针“;
          因此参考c可以知道 (*(void(*)())0)()  是对上边所提到的指针指向的函数的调用
       
//此外, (*(void(*)())0)() 也可以用typedef简化写为:
typedef  void  (*ptr) ();   //将ptr声明为指向返回值为void类型的函数的指针
(*(ptr)0) ();               
/* 其中(ptr)0 是将常数0转化为ptr类型(即将0转化为指向返回值为void类型的函数的指针),然后再调用该指针指向的函数 */


      e.  用signal库函数处理信号  void  (*signal(int sig, void (*func)(int))) (int); 对该函数的解析:
          该函数有两个参数,返回一个函数指针,而该指针所指向的函数无返回值(void)。第一个参数signo是一个整数,第二个参数是函数指针,它所指向的函数需要一个整型参数,该函数没有返回值。signal函数的返回值是一个函数地址,该函数有一个整型参数(即最后的int)。也就是要给信号处理函数传送一个整型参数,而它却没有返回值。当调用signal设置信号处理程序时,第二个参数是指向该函数(即信号处理程序)的指针。signal的返回值则是指向之前的信号处理程序的指针。
分步解析:
1)signal函数返回值是一个函数地址,该函数有一个整型参数且无返回值:void (*) (int)。
2)signal函数有两个参数,分别为int和一个函数指针(该函数有一个int参数,无返回值):signal( int singno, void (*func)(int))
3)所以,完整原型为void  (*signal(int sig, void (*func)(int))) (int);

用typedef可以写成如下较为清晰的结构:
typedef void(* Sigfunc)(int);
Sigfunc *signal(int, Sigfunc);


5) 空指针问题:
      当常数0被转化为指针使用时,该指针绝对不能被解除引用;即绝对不能企图使用该指针所指向的内存中存储的内容。
     如下代码是错误的:
           
 int *p = NULL;
 int *q = (int *)0;
 printf ("%d",*p);  //这里是错误的,因为不能对空指针p解引用。
 printf(“%d”,*q);   //这里也是错误的,因为q是将0转化得来的指针,也是空指针。
     同理,下面的也是错误的:
   if  (strcmp(p, (char *) 0) == 0)    //这里也是错误的,因为 strcmp函数的实现中会包含查看它的指针参数所指向内存中的内容的操作

6)  指针常量与常量指针
       常量指针: 是一个指针,指向的是常量值,它指向的量的值不能改变、必须是常量,但是。(记忆方法:int *p:是整型指针,指向整型数的指针;因此常量指针是指向常量的指针) const char *p
       指针常量:是一个指针,指针本身是常量,即指针指向的地址不能改变,但是所指的地址里的内容可以改变。 指针常量必须在定义时同时赋值。 char *const p;
 

4、malloc与free函数 
http://blog.csdn.net/zhengdan66/article/details/5553635
注意点:

A、申请了内存空间后,必须检查是否分配成功。

B、当不需要再使用申请的内存时,记得释放;释放后应该把指向这块内存的指针指向NULL,防止程序后面不小心使用了它。

C、这两个函数应该是配对。如果申请后不释放就是内存泄露;如果无故释放那就是什么也没有做。释放只能一次,如果释放两次及两次以上会

出现错误(释放空指针例外,释放空指针其实也等于啥也没做,所以释放空指针释放多少次都没有问题)。

D、虽然malloc()函数的类型是(void *),任何类型的指针都可以转换成(void *),但是最好还是在前面进行强制类型转换,因为这样可以躲过一

些编译器的检查。


5、C连接mysql数据库问题
a.   linux控制台进入mysql的语句: mysql [-h 用户名或IP] -u 数据库用户名 -p;其中-h参数用于连接远程数据库
b.  为了某个机器可以远程连接mysql,mysql的设置如下,
         循序C类子网的192.168.1.0-192.168.1.255的登陆: grant all on *.* to 用户名@‘192.168.1.0/255.255.255.0' identified '密码';
c. 在运行c程序的mysql程序前要
                 (1)安装libmysqlclient-dev:sudo apt-get install libmysqlclient-dev  否则编译程序会出错。
                 (2)编译时要加上参数 -lmysqlclient  否则编译有“
undefined reference to `mysql_init' ”等错误。

6、作为语句结束标志的分号:
 
int main() {
   int a;
   printf("test\n");
   return                         //注意此处少了分号,但是程序仍能运行,只是将a=5的结果作为main函数的返回值。
   a = 5;
} 


还有代码片段:
     
 struct s {
      ...... 
       }                      //这里忘了写分号
      main() {
          return;         //这里实际上是将struct s作为main函数的返回值类型,即main()返回struc s类型的数据,而不是返回默认的int型。
     } 


6、C/C++中的static关键字的异同
          static主要有三个作用:

            局部静态变量

            外部静态变量/函数

              静态数据成员/成员函数 
  http://zjayang198861.blog.163.com/blog/static/50383462201171611849983/
  http://blog.csdn.net/skyereeee/article/details/8000512

7、C语言和C++函数声明的差别
        在C中,如果一个函数没有float、short、char类型的参数, 在函数声明中完全 可以省略参数类型的说明(当然,在定义该函数时,参数类型是不能省略的)。例如:函数的定义为:double test(int a, double b) {......}   那么对该函数的声明可以写成:double test();。当然,这样做依赖于调用者能够提供数目正确且类型恰当的实参。这里的“恰当”不是“等同”:float类型参数会自动转化为double类型,char或short会自动转化为int类型。
       但是 在C++中,函数声明中形参类型 不能省略,因为涉及函数重载问题。

       另外, C语言中没有默认实参,但是C++中有:
       C++中若函数声明提供了默认实参(注意一个形参有默认实参则其后的所以形参都必须有默认实参),则调用包含默认实参的函数时,可以为默认实参提供参数也可以不提供,当不提供时则系统会使用默认的实参。
       例如:定义了函数  string test(string a, int b = 2,double c = 3.1) {......}   那么在调用该函数时候可以这么写:
       test("sss", 33, 4.1)或 test("sss")或test("sss", 22)均正确。
       如果函数定义的形参中提供了默认实参,那么只是在包含该函数的源文件中调用该函数时,默认实参才是有效的。

8、《C陷阱与缺陷》第84页,getchar()函数问题未解决。
     char ch;     //这里使用char类型是错误的,应该使用int型,为什么?
     while ((ch = getchar()) != EOF)
.   ...... 

   以下是解释: http://blog.csdn.net/hercaffe/article/details/7207616
                        http://www.cnblogs.com/chenyadong/archive/2012/03/06/2382628.html

        《C和指针》中的解释:EOF需要的位数比字符型值所能提供的位数要多。然而把getchar的返回值(int型)首先存储于ch中将导致它被截断。然后截断的值被提升为整型并与EOF进行比较。当这段存在错误的代码在使用有符号字符集的机器上运行时,如果读取了一个值为\3777的字节时,循环将会终止,因为这个值截断再提升之后与EOF相等。当这段代码在使用无符号字符集的机器上运行时,这将是一个死循环。

9、宏定义注意事项:
a. 宏定义只是对程序的文本进行原样替换,因此:
            #define  fun(a)  a > 0 ? a :-a
    在上述宏定义中,不会得到我们想要的结果:
            例如,我们期望通过fun(2-3) 得到结果1,但是我们得到的结果却是-5
    这就是因为“宏定义仅仅原样替换”,调用fun(a-b)的结果实际上是 a - b > 0 ? a-b :-a - b;而不是
  a - b > 0 ? a-b :-(a -   b)
    同样的,fun(a) + 1的结果是 a > 0 ? a :-a + 1
    解决方案:
           可以通过为宏定义的每个参数都加括号来解决,即:
                     #define  fun(a)  ( ( (a) > 0 ) ? (a) :-(a) )

b. 在注意到a中的事项后,还要注意在宏定义中要避免参数具有副作用 
    例如:    
#include <stdio.h>
#include <stdlib.h>
#define max(a, b) ((a) > (b) ? (a) : (b) )    //定义了一个求两者中大值的宏
int main() {                                                //求数组test中的最大值
    int test[] = {2, 3, 1};              
    int biggest = test[0];
    int i = 1;
    while (i < 3) {
         biggest = max(biggest, test[i++]);
    }
   printf("%d\n", biggest);
}    
      在上述程序中,最后的结果为1,不是我们期望的3。原因是++运算带来的”副作用“
      首先,biggest与test[1]比较,而test[1] = 3;因此宏定义中关系运算结果为false,不过在关系运算中由于i++已经将i的值变为了2;
      然后,因为结果为false,因此将test[i++]赋值给biggest,即biggest == test[i++] == test[2] == 1,  此时由于++ 的副作用,i的值为3就退出了循环;所以结果为1不是期望的3。 
        解决方法:

不让宏中的参数有副作用(或不将max函数定义为宏)

biggest =test[0];
for (i = 1; i< 3; i++) {
biggest = max(biggest, test[i]);


c. 宏并不是类型定义
          可以使用宏来使多个不同变量的类型在同一个地方说明:
          
#define  FOOTYPE struct foo
FOOTYPE a;
FOOTYPE b, c;
          但是最好使用类型定义:
                     typedef struct foo FOOTYPE

          这两种方式看似一样,但是考虑以下:
                     #define T1 struct foo*
                      typedef struct foo *T2
           当我们试图用它们声明多个变量时,问题就出现了:
                     T1 a, b;
                     T2 a, b;
            其中第一个声明被拓展为 struct  foo  *a, b;  a为一个指向结构的指针,b却是一个结构;   第二个声明不同:它定义的a和b都是指向结构的指针,因为T2的行为完全与一个真实的类型相同。 

10、《c陷阱与缺陷》第116页的7.11节有问题:
           printf("%s\n", "0123456789"[2]);  或   printf("%s\n ", "0123456789"[2] );   类似这样的无法输出,有错误。  
           但是putchar("0123456789"[1]); 类似这样的可以正常输出,不太明白为什么printf无法输出。 

11、printf:
 
a.  printf(s) 和printf("%s", s) 是不同的:第一个将把字符串s中的任何%字符视为一个格式项的标志,因而其后的字符会被视为格式码。如果除%%外的任何格式码在字符串s中出现,而其后没有对应的参数都会带来麻烦。第二个例子中,将会打印出任何以空字符结尾的字符串。

 b.     char a[] = "1234567890";
        printf("%*.*s\n", 15, 3, a);
上述代码用于输出字符串a的前3个字符(或者更少,当strlen(a) < 5)的时候,前面将填充若干空白字符以达到总共打印15个字符的要求。

        printf("%*%\n", n) 用于在宽度为n个字符的域内以右端对齐的方式打印出一个%符号,即先打印n - 1个空白字符,然后再跟 一个%符号。 

12、sizeof 和 strlen
a. sizeof 是运算符, strlen是函数
          sizeof 后如果是类型必须加括号 ,但如果sizeof后是变量名的话可以不加括号,因为它是操作符。如可以: int a;  sizeof a。
b. 两者的返回值都是size_t类型,是一个无符号整数类型
c. sizeof 可以用数据类型、指针类型、函数、对象作为参数;

    strlen只能用char *类型、数组名做参数,且必须是以“\0”结尾的。例如:

char a[] = {'a', 'b'};
上面一行代码:sizeof(a)的结果毫无疑问,为2,但是strlen的结果则是不确定的数,因为strlen计算长度以'\0'结束。

在比如:

char b[] = {'a', 'b', '\0'}; //结果:sizeof(a) == 2; strlen(a) == 1

d. 对于C风格字符串,两者输出不同

sizeof计算时包含最后的'\0',而strlen长度不包含:

char a[] = "123"; //sizeof(a) == 4, strlen(a) == 3

 sizeof不同参数作为返回值的含义如下:

         数组:编译时分配给数组的空间大小;char test[100] = "abcd",那么sizeof(test) 返回100, 而strlen(test)返回字符串“abcd"的长度   。同时,sizeof(*test) 返回值为1,这是指针test所指向的字符的长度。

         但是,当数组名作为函数形参时,在该函数中sizeof(数组名)的结果为4,因为数组名做形参时数组名相当于指针,详见文章《C++多态》的例子“数组名作形参的列子”

         指针:存储该指针所使用的空间大小;如 int *p; sizeof(p)返回的是存储指针p所使用空间的大小:4。

         类型:该类型所占用的空间大小;

         char a;  //sizeof(a)为1
         sizeof(a + 1)的结果为4,因为 a + 1类型已经变为了int类型

       对象:该对象所占用的空间大小;
       函数:该函数的返回值占用的空间的大小;函数不能返回void。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值