zoukankan      html  css  js  c++  java
  • 第5章 指针、数组和结构

    5.1 指针
    指针,是一个无符号整数(unsigned int),它是一个以当前系统寻址范围为取值范围的整数。
     char c = 'a';
     char *cp = &c;//cp保存着c的地址

    5.2 数组
    数组就是相同数据类型的元素按一定顺序排列的集合。
    int iarr [5];

    5.2.1 字符串文字量
    用双引号括起来的字符序列。例如 "hello"。
    一个字符串文字量总是由一个空字符''作为结束符。
    sizeof("hello") == 6
    字符串文字量的类型是“适当个数的const字符数组”。
    字符串文字量是静态分配的,所以函数返回他们是安全的。


    重要概念辨析:
    (1)函数指针:实质是一个指针,该指针指向函数的入口地址。
    void (*func)(int ,int);
    (2)指针函数:本质是一个函数,函数返回类型是某一类型的指针。
    void * func(int, int);
    (3)数组指针:指向数组的指针,实质是一个指针,其指向的类型是数组。
    int (*parr)[5];
    (4)指针数组:元素为指针的数组,本质是一个数组,其中的元素为指针。
    int * parr[5];



    5.4 const
    const表达“不变化的值”这样一个概念。
    const是一个C语言的关键字,它限定一个变量不允许被改变。使用const在一定程度上可以提高程序的安全性和可靠性。
    1什么是const?
    常类型是指使用类型修饰符const说明的类型,常类型的变量或对象的值是不能被更新的。(当然,我们可以偷梁换柱进行更新)
    2为什么引入const?

    const 推出的初始目的,正是为了取代预编译指令,消除它的缺点,同时继承它的优点。
    3主要作用
    (1)可以定义const常量,具有不可变性。 
    例如:const int Max=100; int Array[Max]; 
    (2)便于进行类型检查,使编译器对处理内容有更多了解,消除了一些隐患。
    例如: void f(const int i) { .........} 编译器就会知道i是一个常量,不允许修改; 
    (3)可以避免意义模糊的数字出现,同样可以很方便地进行参数的调整和修改。 同宏定义一样,可以做到不变则已,一变都变!
    如(1)中,如果想修改Max的内容,只需要:const int Max=you want;即可! 
    (4)可以保护被修饰的东西,防止意外的修改,增强程序的健壮性。 还是上面的例子,如果在函数体内修改了i,编译器就会报错; 
    例如: void f(const int i) { i=10;//error! } 
    (5) 可以节省空间,避免不必要的内存分配。 例如: 
    #define PI 3.14159 //常量宏 
    const double Pi=3.14159; //此时并未将Pi放入RAM中 ...... 
    double i=Pi; //此时为Pi分配内存,以后不再分配! 
    double I=PI; //编译期间进行宏替换,分配内存 
    double j=Pi; //没有内存分配 
    double J=PI; //再进行宏替换,又一次分配内存! 
    const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是象#define一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而#define定义的常量在内存中有若干个拷贝。 
    (6) 提高了效率。 
    编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高。


    重要概念辨析:
    (1)常量指针:只能读取内存中内容,不能够修改内存中内容的指针。
    int const * ip;
    (2)指针常量:指针所指向的地址不能改变,即指针本身是一个常量,但是指针所指向的内容可以改变。
    int * const p=&a;
    ps:指针常量必须在声明的同时对其初始化,不允许先声明一个指针常量随后再对其赋值,这和声明一般的常量是一样的。
    简单理解--左定值,右定向
    const在*号左边,表示指针指向的内容不能修改(常量指针)
    const在*号右边,表示指针指向的地址不能修改(指针常量)


    5.5 引用
    一个引用就是某个对象的另一个名字。
    引用的主要作用是为了描述函数的参数和返回值。
    为了确保一个引用总能够使某个对象的名字,我们必须对引用初始化。
    int i = 100;
    int &r = i;


    5.6 指向 void 的指针
    一个指向任何对象类型的指针都可以赋值给 void*的变量,一个void*变量可以赋值给另一个void*变量,两个void*变量可以比较相等与否,一个void*变量可以显式的转换为另一个类型。
    其他的操作都是不安全的(编译器不知道实际被指向的是哪种对象),因此,要使用void*变量,必须显式的将它转换为某个特定类型的指针。
    void* 的最主要的用途是向函数传递一个指针。
    int a = 100;
    int * pi = &a;
    void * pv = pi; //ok,从int*到void*的隐式转换
    *pv; //err, void* 不能间接引用
    pv++; //err, void* 不能自增(不知道被指向对象的大小)


    5.7 结构
    结构就是一个可以包含任意类型元素的集合。
    struct Person
    {
       char name[20];
       char sex[10];
       int age;


    };//!!!冒号不能掉;


    结构类型对象的大小是其成员的大小之和

    重要概念辨析:

    内存对齐
    一、内存对齐的原因
    大部分的参考资料都是如是说的:
    1、平台原因(移植原因):不是所有的硬件平台都能访问任意地址上的任意数据的;
    某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
    2、性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。
    原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。
    也有的朋友说,内存对齐出于对读取的效率和数据的安全的考虑,我觉得也有一定的道理。
    二、对齐规则
        每个特定平台上的编译器都有自己的默认“对齐系数”(也叫对齐模数)。
    比如32位windows平台下,VC默认是按照8bytes对齐的(VC->Project->settings->c/c++->Code Generation中的truct member alignment 值默认是8),
    程序员可以通过预编译命令#pragma pack(n),n=1,2,4,8,16来改变这一系数,其中的n就是你要指定的“对齐系数”。
        在嵌入式环境下,对齐往往与数据类型有关,特别是C编译器对缺省的结构成员自然对届条件为“N字节对齐”,N即该成员数据类型的长度。
    如int型成员的自然对界条件为4字节对齐,而double类型的结构成员的自然对界条件为8字节对齐。
    若该成员的起始偏移不位于该成员的“默认自然对界条件”上,则在前一个节面后面添加适当个数的空字节。
    C编译器缺省的结构整体的自然对界条件为:该结构所有成员中要求的最大自然对界条件。
    若结构体各成员长度之和不为“结构整体自然对界条件的整数倍,则在最后一个成员后填充空字节。
        那么可以得到如下的小结:
    类型 对齐方式(变量存放的起始地址相对于结构的起始地址的偏移量)
    Char    偏移量必须为sizeof(char)即1的倍数
    Short   偏移量必须为sizeof(short)即2的倍数
    int     偏移量必须为sizeof(int)即4的倍数
    float   偏移量必须为sizeof(float)即4的倍数
    double  偏移量必须为sizeof(double)即8的倍数
       各成员变量在存放的时候根据在结构中出现的顺序依次申请空间,同时按照上面的对齐方式调整位置,空缺的字节编译器会自动填充。
    同时为了确保结构的大小为结构的字节边界数(即该结构中占用最大空间的类型所占用的字节数)的倍数,
    所以在为最后一个成员变量申请空间后,还会根据需要自动填充空缺的字节,也就是说:结构体的总大小为结构体最宽基本类型成员大小的整数倍,
    如有需要编译器会在最末一个成员之后加上填充字节。对于char数组,字节宽度仍然认为为1。
       对于下述的一个结构体,其对齐方式为:
    struct Node1{
        double m1;
        char m2;
        int m3;
    };
      对于第一个变量m1,sizeof(double)=8个字节;接下来为第二个成员m2分配空间,
    这时下一个可以分配的地址对于结构的起始地址的偏移量为8,是sizeof(char)的倍数,
    所以把m2存放在偏移量为8的地方满足对齐方式,该成员变量占用 sizeof(char)=1个字节;
    接下来为第三个成员m3分配空间,这时下一个可以分配的地址对于结构的起始地址的偏移量为9,
    不是sizeof (int)=4的倍数,为了满足对齐方式对偏移量的约束问题,自动填充3个字节(这三个字节没有放什么东西),
    这时下一个可以分配的地址对于结构的起始地址的偏移量为12,刚好是sizeof(int), 
    由于8+4+4 = 16恰好是结构体中最大空间类型double(8)的倍数,所以sizeof(Node1) =16.
     
    typedef struct{
        char a;
        int b;
        char c;
    }Node2;
        成员a占一个字节,所以a放在了第1位的位置;由于第二个变量b占4个字节,为保证起始位置是4(sizeof(b))的倍数,
    所以需要在a后面填充3个字节,也就是b放在了从第5位到第8位的位置,然后就是c放在了9的位置,此时4+4+1=9。
    接下来考虑字节边界数,
    9并不是最大空间类型int(4)的倍数,应该取大于9且是4的的最小整数12,所以sizeof(Node2) = 12.
    typedef struct{
        char a;
        char b;
        int c;
    }Node3;
       明显地:sizeof(Node3) = 8
       对于结构体A中包含结构体B的情况,将结构体A中的结构体成员B中的最宽的数据类型作为该结构体成员B的数据宽度,
    同时结构体成员B必须满足上述对齐的规定。
       要注意在VC中有一个对齐系数的概念,若设置了对齐系数,那么上述描述的对齐方式,则不适合。
       例如:
    1字节对齐(#pragma pack(1))
    输出结果:sizeof(struct test_t) = 8 [两个编译器输出一致]
    分析过程:
    成员数据对齐
    #pragma pack(1)
    struct test_t {
        int a; 
        char b; 
        short c;
        char d; 
    };
    #pragma pack()
    成员总大小=8;
     
    2字节对齐(#pragma pack(2))
    输出结果:sizeof(struct test_t) = 10 [两个编译器输出一致]
    分析过程:
    成员数据对齐
    #pragma pack(2)
    struct test_t {
        int a; 
        char b; 
        short c;
        char d; 
    };
    #pragma pack()
    成员总大小=9;
     
    4字节对齐(#pragma pack(4))
    输出结果:sizeof(struct test_t) = 12 [两个编译器输出一致]
    分析过程:
    1) 成员数据对齐
    #pragma pack(4)
    struct test_t { //按几对齐, 偏移量为后边第一个取模为零的。
    int a; 
    char b; 
    short c;
    char d; 
    };
    #pragma pack()
    成员总大小=9;

  • 相关阅读:
    Oracle 12c中文乱码,修改字符集的方法
    C++设计模式——简单工厂模式与策略模式比较
    C++设计模式——工厂模式Factory Method
    JavaWeb——Servlet基础
    C++设计模式——装饰模式Bridge-Pattern
    线性代数思维导图(2)——矩阵
    线性代数思维导图(1)——行列式
    C++设计模式——适配器模式Bridge-Pattern
    C++设计模式——桥接模式Bridge-Pattern
    不想写博客?那试试日记吧!
  • 原文地址:https://www.cnblogs.com/riasky/p/3509116.html
Copyright © 2011-2022 走看看