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 = ¢er;//指针定义
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);
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现