47用d编程深入函数
返回类型属性:auto, ref, inout, 和 auto ref
auto
:不需要指定函数返回类型,即可变,占位符.有多个语句,返回各种返回类型
的公共类型
,如整
与双精
的公共类型为双精
,则返回双精
.
auto func(int i) {
if (i < 0) {
return i; // 整
}
return i * 1.5; // 双精
}
void main() {
auto result = func(42);
static assert(is (typeof(result) == double));
}
引用
,表示按引用
传参/返回值
,没有它,默认按值
传递.
int greater(int first, int second) {
return (first > second) ? first : second;
}
import std.stdio;
void main() {
int a = 1;
int b = 2;
int result = greater(a, b);
result += 10;//a,b都不变
writefln("a: %s, b: %s, result: %s", a, b, result);
}
//改一下;
ref int greater(ref int first, ref int second) {
return (first > second) ? first : second;
}
int a = 1;
int b = 2;
greater(a, b) += 10; //a,b都变了
writefln("a: %s, b: %s", a, b);
//注意
int result = greater(a, b);//返回的引用复制了一份给本地变量.
result += 10; //这里不会影响a,b. 因为result是值类型,复制了一份.
//指针
int * result = &greater(a, b);
*result += 10;
writefln("a: %s, b: %s, result: %s", a, b, *result)
//指针当然要变了.
不能返回本地变量的引用
.
ref string parenthesized(string phrase) {
string result = '(' ~ phrase ~ ')';
return result; //
} // 生命期在此结束
返回的引用应该是在函数之前就存在的变量的引用.
编译器会检查的
auto ref
表示模板.
auto ref string parenthesized(string phrase) {
string result = '(' ~ phrase ~ ')';
return result;//能编译.
}
意思就是生命期比函数大
的返回引用.小的返回副本.
尽量返回引用的意思.
在模板参数要看上下文才知道是引用
还是副本
时有用.
inout
是const,immutable,和mutable
的通配符.
string parenthesized(string phrase) {
return '(' ~ phrase ~ ')';
}
// ...
writeln(parenthesized("hello"));
inout(char)[] parenthesized(inout(char)[] phrase) {
return '(' ~ phrase ~ ')';
}
扩展一下,更通用了.类似模板,但比模板更限制.只限制可变属性.先推导出具体的可变属性
.就像占位符
一样.
char[] m;
writeln(typeof(parenthesized(m)).stringof);
const(char)[] c;
writeln(typeof(parenthesized(c)).stringof);
immutable(char)[] i;
writeln(typeof(parenthesized(i)).stringof);
//输出
char []
const(char)[]
string
行为属性.pure, nothrow, 和 @nogc
无副作用:正确性,维护性.
D的纯
:不访问可变全局和静态变量
的就是纯.由于输入输出流是可变全局.所以纯函数中不能输入/出
.
如果函数仅访问参数,本地变量,全局不变状态/变量
,则是纯.
另外,纯函数中也允许改变全局状态的以下操作:
1,用new
分配内存
2,终止程序
3,访问浮点处理标志
4,抛异常.
纯函数不能调用不纯函数.
pure
关键字指定该函数应该如上表现,由编译器保证.
import std.stdio;
import std.exception;
int mutableGlobal;
const int constGlobal;
immutable int immutableGlobal;
void impureFunction() {
}
int pureFunction(ref int i, int[] slice) pure {
enforce(slice.length >= 1);//可抛
i = 42;slice[0] = 43;//可改参数
// 可访问不变全局状态
i = constGlobal;i = immutableGlobal;
auto p = new int;// 可用`new`式
// 不能访问全局可变状态
i = mutableGlobal; // 编译错误
// 不能处理输入输出操作
writeln(i); // 编译错误
static int mutableStatic;
// 不能访问可变静态状态
i = mutableStatic; // 编译错误
// 不能调用非纯函数:
impureFunction(); // 编译错误
return 0;
}
void main() {
int i;
int[] slice = [ 1 ];
pureFunction(i, slice);
}
这样,对于给定的值,返回的值都是一样的.不变.方便优化.
模板的纯度取决于参数,需要推导,当然也可以指定,类似的,动
函数的纯度也是推导
的.
import std.stdio;
// This template is impure when N is zero
void templ(size_t N)() {
static if (N == 0) {
writeln("zero");//纯,则不能有输入出
}
}
void foo() pure {
templ!0(); // 编译错误
}
void main() {
foo();
}
//
void foo() pure {
templ!1();//编译
}
上面可以为纯.
import std.stdio;
debug size_t fooCounter;
void foo(int i) pure {
debug ++fooCounter;
if (i == 0) {
debug writeln("i is zero");
i = 42;
}
// ...
}
void main() {
foreach (i; 0..100) {
if ((i % 10) == 0) {
foo(i);
}
}
debug writefln("foo is called %s times", fooCounter);
}
输入出不为纯,太严格,放松一下,用debug
debug
时修改全局,打印信息
,不纯.但标记为debug
.当有-debug
开关时才加相应调试句子
.
interface Iface {
void foo() pure; // 子类必须纯
void bar(); // 可为纯
}
class Class : Iface {
void foo() pure { // 必须
// ...
}
void bar() pure { // 不必须
// ...
}
}
成员函数可标记为纯,子类纯
函数可覆盖父类不纯
函数,但反过来不行.
闭包和λ函数也可为纯,类似模板,编译器推导函数/闭包/auto函数
是否为纯
.
import std.stdio;
void foo(int delegate(double) pure dg) {
//要求参数为纯,
int i = dg(1.5);
}
void main() {
foo(a => 42); // 编译
foo((a) { // 编译错误
writeln("hello");//不纯
return 42;
});
}
最好,函数记录在特定错误条件下可能抛的异常的类型,一般调用者假定都可抛(现在好像要改了,以不抛作为默认类型)
不抛
保证函数不会抛出异常.
不建议抓错误
或其基类可抛
.这表示任何异常
(不能区分/细分异常.
不抛函数仍可发出
致命错误的子类,表示不可恢复.
不抛函数自身不能抛,其也不能调用
抛函数.
抛就是
可恢复.不然就是
错误`.
int add(int lhs, int rhs) nothrow {
writeln("adding"); // 编译错误
return lhs + rhs;
}
writeln不是且不能是
一个不抛
函数.
int add(int lhs, int rhs) nothrow {
int result;
try {
writeln("adding"); // 编译
result = lhs + rhs;
} catch (Exception error) { // 抓所有异常
// ...
}//都抓了异常了.
return result;
}
把所有异常都抓了,所以就不可能再抛
异常了.
nothrow
不包含Error
的子类异常,
int foo(int[] arr, size_t i) nothrow {
return 10 * arr[i];
}
尽管可能会抛区间错误
,但仍可标记为不抛
.因为已经进入错误
的范围了,这不可恢复
,抛/不抛
都无所谓了.
与纯一样,编译器自动推导闭包,模板,λ函数
是不抛
.
@nogc
d是垃集语言,许多结构与算法都用垃集.通过垃集
算法回收动态内存块.
常用D
操作利用垃集,比如切片的增加元素
:
int[] append(int[] slice) {
slice ~= 42;
return slice;
}
垃集
是昂贵操作,使程序变得相当慢,内存使用变大.
@nogc
表明程序不用垃集
.
void foo() @nogc {
// ...
}
编译器保证@nogc
不包含垃集
操作.
void foo() @nogc {
int[] slice;
// ...
append(slice); // 编译错误,
//不能调用非`@nogc`函数
}
代码安全属性:@安全
,@信任
,@系统
.
编译器会推导模板,闭包,λ函数,自动函数
的安全级.
@安全:
一类编程错误是无意
的在不相关的内存位置上写.
这是由于错误使用指针
和类型转换
造成的.
@safe
保证,不包含损坏
内存操作.在@safe
函数中,编译器不允许:
1,不能将指针
转为除void星
外其他指针类型.
2,非指针表达式不能转为指针.
3,不能修改指针.
4,不能使用有指针和引用
成员的联.
5,不能调用@系统
函数.
6,不能抓异常
的后代.
7,不能用内联汇编
8,可变
与不变
不能相互转换.
9,线程本地变量
与共享
不能相互转换.
10,不能取函数局部变量的地址.
11,不能访问_ _gshared
变量.
@信任:
有些函数是安全的,但不能标记为@安全
(太严格了),如必须调用不支持安全的C库
.或虽然有不允许的操作,但经过严格测试证明是正确的.
@trusted
是程序员
保证的内存安全
.编译器不再检查.允许@安全
代码调用@信任
.
@系统
,默认的安全属性,以后可能会改为@安全
.
ctfe
import std.stdio;
import std.string;
import std.range;
string menuLines(string[] choices) {
string result;
foreach (i, choice; choices) {
result ~= format(" %s. %s\n", i + 1, choice);
}
return result;
}
string menu(string title,string[] choices,size_t width) {
return format("%s\n%s\n%s",title.center(width),'='.repeat(width), // horizontal line
menuLines(choices));
}
void main() {
enum drinks =menu("Drinks",[ "Coffee", "Tea", "Hot chocolate" ], 20);
writeln(drinks);
}
ctfe,编译时执行函数,必须:
初化静/枚
变量,计算固定数组长度,计算模板值参数.
不是所有函数都能编译时执行.如访问全局变量
的函数就不行.因为运行时才有全局变量
.同样stdout
也是运行时函数.
_ _ctfe
需要结果时,编译时与运行时都可用,
_ _ctfe
用于区分仅用于编译时
或运行时
的函数.
import std.stdio;
size_t counter;
int foo() {
if (!__ctfe) {
//仅
++counter;
}
return 42;
}
void main() {
enum i = foo();
auto j = foo();
writefln("foo is called %s times.", counter);
}
【推荐】国内首个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岁的心里话
· 按钮权限的设计及实现