函数在能够处理不同数据时,变得非常灵活多用。你通过实参提供这些数据,实参是函数被调用时传递给函数的值。在函数内部,这些实参由形参接收,形参是函数签名中定义的特殊变量。可以把形参看作函数定义中的命名占位符,它们等待着在函数调用时由你提供的实际数据(实参)来填充。有效定义和使用函数形参,是编写灵活且易读的 Julia 代码必不可少的。Julia 提供了几种处理函数实参的方式,主要区分位置实参和关键字实参。形参与实参:基础知识在我们查看不同类型的实参之前,让我们先明确这些术语:形参:这些是函数定义中列出的名称。它们在函数内部充当局部变量,用于保存传入的值。实参:这些是函数被调用时提供给函数的实际值或表达式。考虑一个简单的函数来添加两个数字:function add_numbers(x, y) # x 和 y 是形参 return x + y end result = add_numbers(5, 3) # 5 和 3 是实参 println(result) # 输出: 8在这里,x 和 y 是 add_numbers 函数的形参。当我们调用 add_numbers(5, 3) 时,值 5 是传递给形参 x 的实参,而 3 是传递给 y 的实参。位置实参向函数传递实参最直接的方式是按位置传递。当你使用位置实参调用函数时,Julia 会根据它们的顺序将实参与形参匹配。第一个实参给第一个形参,第二个给第二个,依此类推。function describe_pet(animal_type, pet_name) println("I have a ", animal_type, " named ", pet_name, ".") end describe_pet("cat", "Whiskers") # "cat" 对应 animal_type, "Whiskers" 对应 pet_name describe_pet("Spike", "dog") # "Spike" 对应 animal_type, "dog" 对应 pet_name。哎呀!在第一次调用 describe_pet 时,"cat" 被赋值给 animal_type,"Whiskers" 被赋值给 pet_name。输出是: I have a cat named Whiskers.然而,在第二次调用中,顺序被无意中交换了。"Spike" 被赋值给 animal_type,"dog" 被赋值给 pet_name,导致输出为: I have a Spike named dog.这个例子强调了位置实参中顺序的重要性。对于有多个位置实参的函数,有时很难记住正确的顺序。这就是关键字实参非常有用的地方。关键字实参关键字实参通过它们的名称(形参名称)而不是位置来识别。这使得函数调用更加明确和易读,特别是在函数有许多形参时,或者当你希望跳过具有默认值的可选形参时(我们将在下一节介绍)。你在函数签名中定义关键字实参时,需要将其放在所有位置实参和一个分号 (;) 之后。function create_user_profile(username; email="not_provided", active=true) println("User: ", username) println("Email: ", email) println("Active: ", active) end # 使用关键字实参调用 create_user_profile("julia_fan"; email="julia@example.com", active=false) create_user_profile("coder123"; active=true) # email 使用其默认值 create_user_profile("data_sci"; email="data@example.org") # active 使用其默认值 # 关键字实参的顺序不重要 create_user_profile("another_user"; active=false, email="contact@example.net")在上面的调用中:username 是一个位置实参。email 和 active 是关键字实参。请注意函数定义 function create_user_profile(username; email="not_provided", active=true) 中的分号。调用时,你使用 形参名=值 的形式指定关键字实参。你在调用中列出关键字实参的顺序不重要(例如,active=false, email="contact@example.net" 的效果与 email="contact@example.net", active=false 相同)。如果一个关键字实参有默认值(例如这里的 email 和 active),你可以在函数调用中省略它,此时将使用默认值。在函数定义中使用分号 ; 是区分位置实参和关键字实参的关键。如果一个函数只有关键字实参,分号仍然被使用:function set_preferences(; theme="dark", notifications=true) println("Theme set to: ", theme) println("Notifications: ", notifications ? "enabled" : "disabled") end set_preferences(notifications=false) set_preferences(theme="light", notifications=true)下图说明了位置类型和关键字类型实参如何映射到形参。digraph ArgumentTypes { rankdir=TB; graph [bgcolor="transparent", fontname="Arial"]; // Added fontname for graph node [shape=plaintext, fontname="Arial"]; edge [color="#495057"]; // 位置实参 pos_def [label=< <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" BGCOLOR="#e9ecef" WIDTH="300"> <TR><TD COLSPAN="2" BGCOLOR="#adb5bd" ALIGN="CENTER"><B>位置实参</B></TD></TR> <TR><TD ALIGN="LEFT">定义:</TD><TD ALIGN="LEFT"><FONT FACE="Courier New" POINT-SIZE="10">function greet(name, message)</FONT></TD></TR> <TR><TD ALIGN="LEFT">调用:</TD><TD ALIGN="LEFT"><FONT FACE="Courier New" POINT-SIZE="10">greet("Alice", "Hi")</FONT></TD></TR> <TR><TD ALIGN="LEFT" VALIGN="TOP">映射:</TD><TD ALIGN="LEFT" VALIGN="TOP" > <FONT POINT-SIZE="10">"Alice" → name</FONT><BR ALIGN="LEFT"/> <FONT POINT-SIZE="10">"Hi" → message</FONT><BR ALIGN="LEFT"/> <I><FONT POINT-SIZE="9">(顺序很重要)</FONT></I> </TD></TR> </TABLE> >]; // 关键字实参 key_def [label=< <TABLE BORDER="0" CELLBORDER="1" CELLSPACING="0" BGCOLOR="#e9ecef" WIDTH="300"> <TR><TD COLSPAN="2" BGCOLOR="#adb5bd" ALIGN="CENTER"><B>关键字实参</B></TD></TR> <TR><TD ALIGN="LEFT">定义:</TD><TD ALIGN="LEFT"><FONT FACE="Courier New" POINT-SIZE="10">function style(text; color="black", bold=false)</FONT></TD></TR> <TR><TD ALIGN="LEFT">调用:</TD><TD ALIGN="LEFT"><FONT FACE="Courier New" POINT-SIZE="10">style("Hello"; bold=true, color="blue")</FONT></TD></TR> <TR><TD ALIGN="LEFT" VALIGN="TOP">映射:</TD><TD ALIGN="LEFT" VALIGN="TOP"> <FONT POINT-SIZE="10">"blue" → color</FONT><BR ALIGN="LEFT"/> <FONT POINT-SIZE="10">true → bold</FONT><BR ALIGN="LEFT"/> <I><FONT POINT-SIZE="9">(名称很重要,关键字顺序不重要)</FONT></I> </TD></TR> </TABLE> >]; }位置实参和关键字实参如何与形参匹配的比较。混合位置实参和关键字实参Julia 中的函数可以同时接受位置实参和关键字实参。定义此类函数时,所有位置形参必须在分号前列出,所有关键字形参在分号后列出。function send_message(recipient, message_text; sender="System", priority="Normal") println("To: ", recipient) println("From: ", sender) println("Priority: ", priority) println("Message: ", message_text) end send_message("user@example.com", "Your report is ready.") send_message("admin@example.com", "Server maintenance soon.", priority="High", sender="IT Dept")在这些调用中:recipient 和 message_text 是位置实参。它们的值由它们在调用中的顺序决定。sender 和 priority 是关键字实参。它们可以省略(使用默认值),也可以通过名称指定。当调用一个同时接受这两种类型的函数时,你必须首先按顺序提供所有必需的位置实参,然后是任何关键字实参(如果需要消除歧义,则以分号开头;如果上下文明确,则直接在位置实参之后列出)。# 正确的调用 send_message("test_user", "Test message text"; sender="QA Team") # 错误:关键字实参在位置实参之前 # send_message(sender="QA Team", "test_user", "Test message text") # 这会报错可变数量的实参(Varargs)有时,你希望函数接受可变数量的实参。Julia 使用“散列”运算符 (...) 来处理这种情况。位置可变实参如果你在函数定义中将 ... 附加到最后一个位置形参,该形参将把传递给函数的所有剩余位置实参收集到一个元组中。function list_items(list_name, items...) println(list_name, ":") if isempty(items) println(" (No items)") else for item in items println(" - ", item) end end end list_items("Shopping List", "apples", "bananas", "carrots") list_items("To-Do") list_items("Meeting Attendees", "Alice", "Bob")在第一次调用中,list_name 是 "Shopping List",items 变为元组 ("apples", "bananas", "carrots")。 在第二次调用中,list_name 是 "To-Do",items 是一个空元组 ()。 在第三次调用中,list_name 是 "Meeting Attendees",items 是 ("Alice", "Bob")。这个特性对于像 println (可以接受任意数量的实参来打印)或可能处理可变数量输入的数学函数(例如,一个对任意数量数字求和的函数)非常有用。关键字可变实参类似地,你可以使用分号后的 kwargs... 将所有未被分配的关键字实参收集到一个 NamedTuple 中。这对于那些可能将选项传递给其他函数或处理任意关键字形参的函数很有用。function process_options(main_option; extra_options...) println("Main Option: ", main_option) if !isempty(extra_options) println("Extra Options:") for (key, value) in pairs(extra_options) println(" ", key, " = ", value) end else println("No extra options provided.") end } process_options("EnableFeatureX"; log_level="debug", retries=3) process_options("RunAnalysis")在第一次调用中,main_option 是 "EnableFeatureX"。extra_options 变为一个 NamedTuple,等同于 (log_level="debug", retries=3)。 在第二次调用中,extra_options 是一个空的 NamedTuple。使用可变实参极大地增加了你的函数设计的灵活性。形参的类型标注正如你在第2章学到的,Julia 允许你用类型标注变量。这种做法也适用于函数形参。为形参指定类型可以使你的函数更容易理解,有助于捕获错误,并使 Julia 的编译器能够通过一种称为多重分派的机制(我们将在本章后面提到)生成更专用且通常更快的代码。function calculate_rectangle_area(length::Real, width::Real) if length < 0 || width < 0 println("Error: Length and width must be non-negative.") return 0 # 或者抛出错误,我们将在第8章看到 end return length * width end area1 = calculate_rectangle_area(5.0, 3.0) # 有效,Float64 是 Real 的子类型 println("Area 1: ", area1) area2 = calculate_rectangle_area(10, 2) # 有效,Int 是 Real 的子类型 println("Area 2: ", area2) # area3 = calculate_rectangle_area("5", "3") # 这会导致 MethodError # println("Area 3: ", area3)在 calculate_rectangle_area(length::Real, width::Real) 中,我们指定 length 和 width 都应该是 Real 的子类型。这意味着它们可以是整数、浮点数等,但不能是字符串。如果你尝试使用不正确类型(例如字符串)的实参调用此函数,Julia 会在函数内部的任何代码运行之前引发 MethodError,这有助于尽早发现问题。通过理解和使用位置实参、关键字实参和可变实参,以及类型标注,你可以对函数如何接收和处理信息获得精细的控制,从而编写出更易于维护的 Julia 程序。下一节将在此基础上,展示如何为这些实参提供默认值,使你的函数更加灵活。