36用d编程指针

指针对系统编程很重要,d的指针其实不难,d的有些特征可以替代指针.

import std.stdio;

void main() {
    int[] numbers = [ 1, 11, 111 ];

    foreach (number; numbers) {
        number = 0;     //复制语义
    }

    writeln("循环后: ", numbers);
}

加上引用

    foreach (ref number; numbers) {
        number = 0;
    }
//同样,副本
import std.stdio;
void addHalf(double value) {
    value += 0.5;        // ← Does not affect 'value' in main
}

void main() {
    double value = 1.5;

    addHalf(value);

    writeln("调用函数后值: ", value);
}
//同样,引用
void addHalf(ref double value) {
    value += 0.5;
}

引用类型:类,切片,关联数组

import std.stdio;

class Pen {
    double ink;

    this() {
        ink = 15;
    }

    void use(double amount) {
        ink -= amount;
    }
}

void main() {
    auto pen = new Pen;
    auto otherPen = pen; //都能访问相同对象
    writefln("Before: %s %s", pen.ink, otherPen.ink);

    pen.use(1);          // 使用相同对象
    otherPen.use(2);     // 使用相同对象
    writefln("After : %s %s", pen.ink, otherPen.ink);
}

类对象引用,都是一个实体的几把钥匙.但钥匙很强大
引用和指针概念通过微控器的特殊寄存器实现.指向内存位置.
理解指针很重要.
除了void指针,任意指针都关联一种类型,并只能指向这种类型对象.如指针,只能指向.
指向类型 * 指针变量名;,如int * myPointer;,前后空格没意义.
指针默认值为无效(空针).
指针其实就是个地址.

    int myVariable = 180;
    int * myPointer = &myVariable;

也可以有指针的地址,即指向指针的指针.多一层间接性.
访问指针也用,星 指针,声明也用.注意区别.

struct Coordinate {
    int x;
    int y;

    string toString() const {
        return format("(%s,%s)", x, y);
    }
}
//这样访问:
 auto center = Coordinate(0, 0);
    Coordinate * ptr = &center;//指针定义
    writeln(*ptr); 
//这样:
   (*ptr).x += 10;//麻烦不?

(星ptr).x
在d中这样ptr.x += 10;
为了方便,在d中把.操作移至指针.(例外在尾)因而,
指针自身没有x成员,因而应用至指针所指对象的x成员

class ClassType {
    int member;
}

// ...

    ClassType variable = new ClassType;
    variable.member = 42;//访问成员

类变量与指针差不多.
例外是取类型大小:

    char c;
    char * p;//指针`类成员/指针`
    writeln(p.sizeof); //`char*`,而不是`char`

//这里是的大小.
危险的指针运算:

    ++ptr;
    --ptr;
    ptr += 2;
    ptr -= 2;
    writeln(ptr + 3);
    writeln(ptr - 3);

表示指针现指向另一个变量.1为1个单位.而不是具体的地址加减.即++ptr,现在指向下个变量,
小心:
不要瞎移,禁止指针指向不存在变量.指向不是程序有效字节位置的指针是未定义行为.例外是可以指向一个数组的虚元素位(数组末位+1)
++myPointer;,你不知道下个是什么.
数组和切片是挨着的,可以预测,因而可以.但要小心.
不要超出末尾.

import std.stdio;
import std.string;
import std.conv;

enum Color { red, yellow, blue }

struct Crayon {
    Color color;
    double length;

    string toString() const {
        return format("%scm %s crayon", length, color);
    }
}

void main() {
    writefln("Crayon objects are %s bytes each.", Crayon.sizeof);

    Crayon[] crayons = [ Crayon(Color.red, 11),
                         Crayon(Color.yellow, 12),
                         Crayon(Color.blue, 13) ];

    Crayon * ptr = &crayons[0];// 定义
    for (int i = 0; i != crayons.length; ++i) {
        writeln("Pointer value: ", ptr);//使用针
        writeln("Crayon: ", *ptr);//访问元素
        ++ptr;//移动
    }//可以用更高级方式替代.
}

编译器与d运行时无法保证正确使用指针.由程序员负责
.是空针还是有效值.
最好尽量使用d的高级特征.
左闭右开:

    int[] values = [ 0, 1, 2, 3 ];
    writeln(values[1 .. 3]);//实质是[)左闭右开
//---
import std.stdio;

void tenTimes(int * begin, int * end) {
    while (begin != end) {//可以访问,但永远不可达
        *begin *= 10;
        ++begin;
    }
}

void main() {
    int[] values = [ 0, 1, 2, 3 ];
    int * begin = &values[1];
    tenTimes(begin, begin + 2);//最后一个
    writeln(values);
}
//也可这样
    for ( ; begin != end; ++begin) {
        *begin *= 10;
    }
//这样
    foreach (ptr; begin .. end) {//两个指针的区间
        *ptr *= 10;
    }
//这样
   tenTimes(begin,begin + values.length);

因而,是允许指针越过尾+1,这是个不可达的虚指针.

    double[] floats = [ 0.0, 1.1, 2.2, 3.3, 4.4 ];

    double * ptr = &floats[2];

    *ptr = -100;      // 直接访问
    ptr[1] = -200;    // 索引操作
    writeln(floats);

这里转义了.pointer[index]转义成星(pointer + index).

    ptr[1] = -200;      // 
    *(ptr + 1) = -200;  //等价操作

切片更安全,编译器不保证指针指向正确.

    double[] slice = floats[2 .. 4];
    slice[0] = -100;
    slice[1] = -200;

类到指针Struct * ptr = makeObjects(10);.//假设是个c函数.返回第一个对象指针
指针到切片slice = pointer[0 .. count];.
切片可这样Struct[] slice = ptr[0 .. 10];构造
writeln(slice[1]);
可指向任意指针的void星,但其功能受限.

  int number = 42;
    double otherNumber = 1.25;
    void * canPointAtAnything;

    canPointAtAnything = &number;
    canPointAtAnything = &otherNumber;

不能直接访问其元素:星canPointAtAnything = 43;
要这样:

    int number = 42;//实际变量
    void * canPointAtAnything = &number;//存储
int * intPointer = cast(int*)canPointAtAnything;
//先转成相应类型,再用(星)访问
*intPointer = 43;//访问

这个void星的指针算术,就是按字节来算的了.
++canPointAtAnything;加1.
同c交互时有用.
指针可转为逻辑表达式,空针,非空为真…

void print(Crayon crayon,size_t * numberOfBytes){
     immutable info = format("Crayon:%s",crayon);
    writeln(INFO);

    if(numberOfBytes) {
        * numberOfBytes = info.length;
    }
}

new构造的变量叫动态变量.

  Class classVariable = new Class;//是类变量
//--
    Struct * structPointer = new Struct;//结构
    int * intPointer = new int;//基本类型
//是指针
    int[] slice = new int[100];
//数组是切片
    auto classVariable = new Class;
    auto structPointer = new Struct;
    auto intPointer = new int;
    auto slice = new int[100];
//不明显.
import std.stdio;

struct Struct {
}

class Class {
}

void main() {
    writeln(typeof(new int   ).stringof);
    writeln(typeof(new int[5]).stringof);
    writeln(typeof(new Struct).stringof);
    writeln(typeof(new Class ).stringof);
}

new用于,构造值类型的动态变量时,只要在程序中有指向这个实体的指针,生命期就会延长.这也是引用类型的方式.

    int[] numbers = [ 7, 12 ];

    int * addressOfFirstElement = numbers.ptr;
    writeln("First element: ", *addressOfFirstElement);

数组和切片的.针指针属性是第一个元素的地址.
c库交互时,很有用.一些c函数取内存中连续元素的第一个地址.
串也是数组,也用.ptr属性.第一个元素不是第一个字符,而是第一个代码单元,(按utf8存储的)
关联数组的in操作符.

    if ("purple" in colorCodes) {
        // 有键为"purple"的元素
    } else {
        // 无叫"purple"的元素
    }

如果存在键,则返回元素的地址.否则为空针.

import std.stdio;

void main() {
    string[int] numbers =
        [ 0 : "zero", 1 : "one", 2 : "two", 3 : "three" ];
    int number = 2;
    auto element = number in numbers;//初化.
    //这里是`*串`值类型.
    if (element) {//不为空,指针访问
        writefln("我知道: %s.", *element);
    } else {//空针,不能访问
        writefln("我不知道%s.", number);
    }
}

d中指针已经很少见了.readf可不用显式指针.

  GdkGeometry geometry;
    // ... 置'geometry'成员 ...

    window.setGeometryHints(/* ... */, &geometry, /* ... */);

到c/c++库的绑定可能需要指针.
引用值类型的变量时,需要指针.

import std.stdio;
import std.random;

void main() {
    size_t headsCount = 0;
    size_t tailsCount = 0;

    foreach (i; 0 .. 100) {
        size_t * theCounter = (uniform(0, 2) == 1)
                               ? &headsCount
                               : &tailsCount;
        ++(*theCounter);
    }//这里指针,其实是多余的,

    writefln("头: %s  尾: %s", headsCount, tailsCount);
}

作为数据结构成员时用指针,这比较常用.比如.
许多数据结构不是连续的.比如链表,树,指针更自然,有效.
直接访问底层时,指针提供字节级内存访问.当然,仍然是有效变量的位置.尝试随机访问内存位置是未定义行为.
单链表,优点是插入删除方便.缺点是很难,N复杂度.

struct Node {//结点
    int element;
    Node * next;//自身相同元素,递归类型.
    // ...
}

整个列表表示为:

struct List {
    Node * head;
    // ...
    void insertAtHead(int element) {
        head = new Node(element, head);
    }//为了简单,仅定义一个函数
}

更详细:

import std.stdio;
import std.conv;
import std.string;

struct Node {
    int element;
    Node * next;

    string toString() const {//表示
        string result = to!string(element);

        if (next) {
            result ~= " -> " ~ to!string(*next);
        }

        return result;
    }
}

struct List {
    Node * head;

    void insertAtHead(int element) {
        head = new Node(element, head);
    }

    string toString() const {
        return format("(%s)", head ? to!string(*head) : "");
    }//表示
}

void main() {
    List numbers;

    writeln("前: ", numbers);

    foreach (number; 0 .. 10) {
        numbers.insertAtHead(number);
    }

    writeln("后 : ", numbers);
}

比较好的访问内存的指针类型应是ubyte,这样,可以访问那个变量的所有字节.
int variable = 0x01_02_03_04;16进制,更清晰.
可这样定义指针int * address = &variable;
字节ubyte *bytePointer=cast(ubyte*)address;
字节访问:

   writeln(bytePointer[0]);
    writeln(bytePointer[1]);
    writeln(bytePointer[2]);
    writeln(bytePointer[3]);

如你的为小头,则4 3 2 1.
可以用来观察所有变量的字节.

import std.stdio;

void printBytes(T)(ref T variable) {
    const ubyte * begin = cast(ubyte*)&variable;
//都可以转成指针类型.

    writefln("type   : %s", T.stringof);//类型
    writefln("value  : %s", variable);//值
    writefln("address: %s", begin);//打印地址
    writef  ("bytes  : ");

    writefln("%(%02x %)", begin[0 .. T.sizeof]);
//取类型大小,并打印.生成切片,并打印.
//或这样:
    foreach (bytePointer; begin .. begin + T.sizeof) {//最后为不可达的虚指针.
        writef("%02x ", *bytePointer);//遍历字节
    }//不如上面简洁吧.
    writeln();
}

示例:

struct Struct {
    int first;
    int second;
}

class Class {
    int i;
    int j;
    int k;

    this(int i, int j, int k) {
        this.i = i;
        this.j = j;
        this.k = k;
    }
}

void main() {
    int integerVariable = 0x11223344;
    printBytes(integerVariable);

    double doubleVariable = double.nan;
    printBytes(doubleVariable);

    string slice = "a bright and charming faxade";
    printBytes(slice);

    int[3] array = [ 1, 2, 3 ];
    printBytes(array);

    auto structObject = Struct(0xaa, 0xbb);
    printBytes(structObject);

    auto classVariable = new Class(1, 2, 3);
    printBytes(classVariable);
}

可见整,静态数组,结构对象是挨个并排的.双精为特定位模式
而串不是这样.
串实际是这样的:

struct __string {//__表明是编译器用的内部类型
    size_t length;
    char * ptr;    //实际地址
}

类如下:

struct __Class_VariableType {//类,其实是个指针
    __Class_ActualObjecType * object;
}

再来

import std.stdio;
import std.ascii;

void printMemory(T)(T * location, size_t length) {
    const ubyte * begin = cast(ubyte*)location;

    foreach (address; begin .. begin + length) {
        char c = (isPrintable(*address) ? *address : '.');
//像中文 ,分成两个符,就打印出..

        writefln("%s:  %02x  %s", address, *address, c);
    }
}

现在不打印字节,在特定位置打印指定字节数.
isPrintable可区分打印与不可打印字符.
可以通过.针打印串的代码单元

import std.stdio;

void main() {
    string s = "a bright and charming facade";
    printMemory(s.ptr, s.length);
}
发布了440 篇原创文章 · 获赞 29 · 访问量 11万+

猜你喜欢

转载自blog.csdn.net/fqbqrr/article/details/104588692