zoukankan      html  css  js  c++  java
  • C专家编程 总结

    1 类型转换

    当执行算术运算时,操作数的类型如果不同,就会发生转换,数据类型一般朝着浮点精度高、长度更长的方向转换,整数型如果转换为signed不会丢失信息,就转换为signed,否则转换为unsigned。

    K&R C所采用无房户后保留原著,就是当一个无符号类型与int或更小的整型混合使用时,结果类型是无符号类型。

    2 C语言中const并不真正表示常量。

    3 switch语句的缺点

    1)switch语句最大的缺点是它不会在每个case标签后面的语句执行完毕后自动终止。

    2)由于break语句事实上跳出的是最近的那层循环语句或switch语句,所以break可能使switch语句提前跳出结束。

    4 C语言中的符号重载

    符号         意义
    static 

    在函数内部,表示该变量的值在各个调用间一直保持延续性

    在函数这一级,表示该函数只对本文件可见

    extern

    用于函数定义,表示全局可见(属于冗余的)

    用于变量,表示它在其他地方定义

    void

    作为函数的返回类型,表示不返回任何值

    在指针声明中,表示通用指针的类型

    位于参数列表中,表示没有参数

    *

    乘法运算符

    用于指针,间接引用

    在声明中,表示指针

    &

    位的AND操作符

    取地址运算符

    = 赋值符
    == 比较运算符

    <=

    <<=

    小于运算符

    左移复合赋值运算符

    <

    小于运算符

    #include指令的左定界符

    ()

    在函数定义中,包围形式参数表

    调用一个函数

    改变表达式的运算次序

    将值转换为其他类型(强制类型转换)

    定义带参数的宏

    包围sizeof操作符的操作数(如果它是类型名)

    5 C语言中的优先级

    优先级问题 表达式 人们可能误以为的结果 实际结果

    .的优先级高于*。->操作符

    用于消除这个问题

    *p.f p所指对象的字段f (*p).f

    对p取f偏移,作为左值,然后进行

    解除引用操作。*(p.f)

    []高于* int *ap[] ap是个指向int数组的指针 int(*ap)[] ap是个元素为int左值的数组int *(ap[])
    函数()高于* int *fp() fp是个函数指针,所指函数返回int,int(*fp)() fp是个函数,返回int*,int*(fp())
    ==和!=高于位运算符 (val&mask!=0) (val&mask)!=0 val&(mask!=0)
    ==和!=高于赋值符 c=getchar()!=EOF (c=getchar())!=EOF c=(getchar()!=EOF)
    算术运算符高于移位运算符 msb<<4+lsb (msb<<4)+lsb msb<<(4+lsb)
    逗号运算符在所有运算符中优先级最低 i=1,2 i=(1,2) (i=1),2

    结合性只用于表达式中出现两个以上相同优先级的操作符的情况,用于消除歧义。事实上,你会注意所有优先级相同的操作符,它们的结合性也相同。

    6 返回局部对象的指针

    char * localized_time(char* filename)
    {
        struct tm *tm_ptr;
        struct stat stat_block;
        char buffer[120];
        
        stat(filename,&stat_block);
        tm_ptr=localtime(&stat_block.st_mtime);
        strftime(buffer,sizeof(buffer,"%a %b %e %T %Y",tm_ptr);
        return buffer;
    }

    问题就出在最后一行,也就是返回buffer的那行。buffer是一个自动分配内存的数组,是该函数的局部变量。当控制流离开声明自动变量(即局部变量)的范围时,自动变量就会失效。这就意味着即使返回一个指向局部变量的指针,当函数结束时,由于该变量已被销毁,谁也不知道这个指针所指向的地址的内容是什么。

    在C语言中,自动变量在堆栈中分配内存。当包含自动变量的函数或代码块退出时,它们所占用的内存便被回收,它们的内容肯定会被下一个调用的函数覆盖。这一切取决于堆栈中先前的自动变量位于何处,活动函数声明了什么变量,写入了什么内容等。原先自动变量地址的内容可能被立即覆盖,也可能稍后才被覆盖。

    解决这种问题有几种方案:

    1 返回一个指向字符串常量的指针。例如:

    char* func() { return "Only works for simple strings";}

    2 使用全局声明的数组。例如:

    char *fun() {

    ...

    my_global_array[i] = 

    ...

    return my_global_array;

    }

    这个适用于自己创建字符串的情况,也很简单易用。它的缺点在于任何人都有可能在任何时候修改这个全局数组,而且该函数的下一次调用也会覆盖该数组的内容。

    3 使用静态数组。例如:

    char * func()

    {

    static char buffer[20];

    ...

    return buffer;

    }

    这就可以防止任何人修改这个数组。只有拥有指向该数组的指针的函数才能修改这个静态数组。但是,该函数的下一次调用将覆盖这个数组的内容,所以调用者必须在此之前使用或备份数组的内容。和全局数组一样,大型缓冲区如果闲置不用是非常浪费内存空间的。

    4 显式分配一些内存,保存返回的值。例如:

    char* func(){

    char *s=malloc(120);

    ...

    return s;

    }

    这个方法具有静态数组的优点,而且在每次调用时都创建一个新的缓冲区,所以该函数以后的调用不会覆盖以前的返回值。它适用于多线程的代码。它的缺点在于程序员必须承担内存管理的责任。根据程序的复杂程度,这项任务可能很容易,也可能很复杂。如果内存尚在使用就释放或者出现“内存泄露”(不再使用的内存未回收),就会产生令人难以置信的Bug。

    5最好使用的解决方案就是要求调用者分配内存来保存函数的返回值。为了提高安全调用者应该同时指定缓冲区的大小。

    void func(char* result,int size){

    ...

    strncpy(result,"That't be in the data segment,Bob",size);

    }

    buffer=malloc(size);

    func(buffer,size);

    ...

    free(buffer);

    如果程序员可以在同一代码块中同时进行“malloc”和“free”操作,内存管理是最为轻松的。这个解决方案就可以实现这一点。

    7 声明是如何形成的

    不合法的声明:

    • 函数的返回值不能是一个函数,所以像foo()()这样是非法的。
    • 函数的返回值不能是一个数组,所以像foo()[]这样是非法的。
    • 数组里面不能有函数,所以像foo[]()这样是非法的。

    合法的声明:

    • 函数的返回值允许是一个函数指针,如: int(*fun())();
    • 函数的返回值允许是一个指向数组的指针,如: int(*foo())[];
    • 数组里面允许有函数指针,如int(*foo[])()
    • 数组里面允许有其他数组,所以你经常看到int foo[][]

    8 typedef的使用

    typedef与define的区别:

    首先,可以用其他类型说明符对宏类型名进行扩展,但对typedef所定义的类型名不能这样做。如下所示:

    #define peach int 

    unsigned peach i; //没问题

    typedef int banana;

    unsigned banana i;  //错误! 非法

    其次,在连续几个变量的声明中,用typedef定义的类型能够保证声明中所有的变量均为同一种类型,而用#define定义的类型则无法保证。如下所示:

    #define int_ptr int *;

    int_ptr chalk,cheese;

    经过宏扩展,第二行变为:

    int * chalk,cheese;

    这使得chalk和cheese成为不同的类型:chalk是一个指向int的指针,而cheese则是一个int。相反,下面的代码中:

    typedef char* char_ptr;

    char_ptr Bentley,Rolls_Royce;

    Bentley和Rolls_Royce的类型依然相同。虽然前面的类型名变了,但它们的类型相同,都是指向char的指针。

    //下面两个声明具有相似的形式
    
    typedef struct fruit{ int weight, price_per_lb; } fruit; //语句1
    struct veg{ int weight,price_per_lb; } veg;  //语句2

    但它们代表的意思却完全不一样,语句1声明了结构表情“fruit”和由“typedef声明的结构类型”fruit“,其实际效果如下:

    struct fruit mandarin;   //使用结构标签fruit

    fruit mandarin;   //使用结构类型fruit

    语句2声明了结构标签veg和变量veg,只有结构标签能够在以后的声明中使用,如

    struct veg potato;

    如果试图使用veg  cabbage这样的声明,将是一个错误。这有点类似下面的写法:

    int i;

    i j;

    9 声明和定义的区别

    记住,C语言的对象必须有且只有一个定义,但它可以有多个extern声明。

    定义是一种特殊的声明,它创建了一个对象;声明简单地说明了在其他地方创建的对象的名字,它允许你使用这个名字。

    定义   只能出现在一个地方         确定对象的类型并分配内存,用于创建的对象。例如,int my_array[100];

    声明   可以多次出现          描述对象的类型,用于指代其他地方定义的对象(例如在其他文件里)例:extern int my_array[];

    区分定义和声明

    只要记住下面的内容即可分清定义和声明:

    声明相当于普通的声明:它所声明的并非自身,而是描述其他地方的创建的对象。

    定义相当于特殊的声明:它为对象分配内存。

    extern对象声明告诉编译器对象的类型和名字,对象的内存分配则在别处进行。由于并未在声明中为数组分配内存,所以并不需要提供关于数组长度的信息。对于多维数组,需要提供除最左边一维之外其他维的长度——这就给编译器足够的信息产生相应的代码。

    10 数组和指针的访问

    C语言引入了”可修改的左值“这个术语,它表示左值允许出现在赋值语句的左边,这个奇怪的术语是为与数组名区分,数组名也用于确定对象在内存中的位置,也是左值,但它不能作为赋值的对象。因此,数组名是个左值但不是可修改的左值。

    左值:出现在赋值符左边的符号有时被称为左值。

    右值:出现在赋值符右边的符号有时则被称为右值。

    编译器为每个变量分配一个地址(左值)。这个地址在编译时可知,而且该变量在运行时一直保存于这个地址。相反,存储于变量中的值(它的右值)只有在运行时才可知。如果需要用到变量中存储的值,编译器就发出指令从指定的地址读入变量值并将它存于寄存器中。

    这里的关键之处在于每个符号的地址在编译时可知。所以,如果编译器需要一个地址(可能还需要加上偏移量)来执行某种操作,它就可以直接进行操作,并不需要增加指令首先取得具体的地址。相反,对于指针,必须首先在运行时取得它的当前值,然后才能对它进行解除引用的操作(作为以后进行查找的步骤之一)。

    相反,如果声明extern char *p,它将告诉编译器p是一个指针,它指向的对象是一个字符。为了取得这个字符,必须得到地址p的内容,把它作为字符的地址并从这个地址中取得这个字符。指针的访问要灵活很多,但需要增加一个额外的提取,如图所示:

    11 数组和指针的其他区别

    比较数组和指针的另外一个方法就是对比两者的特点。

    数组和指针的区别

    指针          数组

    保存数据的地址

    保存数据               

    间接访问数据,首先取得指针的内容,把它作为地址,然后从这个地址提取数据。

    如果指针有一个下标[I],就把指针的内容加上I作为地址,从中提取数据

     直接访问,a[I]只是简单地从a+I为地址取得数据

    通常用于动态数据结构 通常用于存储固定数目且数据类型相同的元素
    相关的函数为malloc() free()  隐式分配和删除
    通常指向匿名数据 自身即为数据名

    定义指针时,编译器并不为指针所指向的对象分配空间,它只是分配指针本身的空间,除非在定义时赋给指针一个字符串常量进行初始化。例如,下面的定义创建了一个字符串常量(为其分配了内存):

    char *p="breadfruit";

    注意只有对字符串常量才是如此。不能指望为浮点数之类的常量分配空间,如:

    float *pip=3.141;  //错误

    初始化指针时所创建的字符串常量被定义为只读的。如果试图通过指针修改这个字符串的值,程序就会出现未定义的行为。

     

    数组也可以用字符串常量进行初始化:

    char a[]="gooseberry";

    与指针相反,由字符串常量初始化的数组是可以修改的。

     

     12 UNIX中的堆栈段和MS-DOS中的堆栈段

    在UNIX中,当进程需要更多空间时,堆栈会自动生长。程序员可以想象堆栈是无限大的。这是UNIX胜过其他操作系统如MS-DOS的许多优势之一。在UNIX的实现中一般使用某种形式的虚拟内存。当试图访问当前系统分配给堆栈的空间之外时,它将产生一个硬件中断,称为页错误。处理页错误的方法有好几种,取决于对页的引用是否有效。

    在正常情况下,内核通过向违规的进程发送合适的信号(可能是段错误)来处理对地址的引用。在堆栈顶部的下端有一个称为red zone的小型区域,如果对这个区域进行引用,不会产生失败。相反,操作系统通过一个好的内存块来增加堆栈段的大小。

    在DOS中,在建立可执行文件时,堆栈的大小必须同时确定,而且它不能在运行时增加,如果你猜测错误,需要的堆栈空间大于所分配的空间,那么你和程序都会迷失。如果设置了检查选项,就会收到STACK OVERFLOW(堆栈溢出)消息。

     

    13 返回局部指针的两种不同情况。

     先看下面的程序:

    #include<iostream>
    using namespace std;
    
    char* test2()
    {
        char p[] = "hello world";
        return p;
    }
    char* test3(){
        char *p = "hello world";
        return p;
    }
    int main()
    {
        test2();
        test3();
    }

    其中,test2中p是一个数组,分配在栈空间,在栈空间存放字符串。当是数组p,则函数会将字符串常量的字符逐个复制到p数组里面,返回p则是返回数组p,但是调用函数结束后p被销毁,里面的元素不存在了。

    但是在test3中p是一个指针,则p指向存放字符串常量的地址,返回p则是返回字符串常量地址值,调用函数结束字符串常量不会消失(是常量)。所以返回常量的地址不会出错。

    虽然都是返回局部变量的指针,但是test2结果会有问题,但是test3不会。

  • 相关阅读:
    python操作json来存储简单的数据,pickle来操作复杂的数据
    python元组,列表,字典练习
    python文件实现增删改查操作
    socket模拟服务器,客户端下载东西(ftp)
    optiontransferselect标签
    doubleselect标签联动选择框
    datetimepicker标签日期选择器
    combobox标签复合框
    checkboxlist标签多选框组
    使用动态数据的autocomplete标签
  • 原文地址:https://www.cnblogs.com/wuchanming/p/4302373.html
Copyright © 2011-2022 走看看