为什么在PHP中使用sprintf函数?


188

我正在尝试学习有关PHP函数sprintf()的更多信息,但由于我仍然感到困惑,php.net并没有太大帮助,您为什么要使用它?

看下面我的例子。

为什么使用这个:

$output = sprintf("Here is the result: %s for this date %s", $result, $date);

当这样做相同并且更容易编写IMO时:

$output = 'Here is the result: ' .$result. ' for this date ' .$date;

我在这里想念什么吗?


40
+1,好问题。我自己很少使用它,所以我很想看看答案。
zombat

6
请注意,您的两个示例并不等效:第一个示例仅将结果分配给$output,而第二个示例将其分配给echo
Daniel Pryden 09年

当我串联很多东西时,我会亲自使用它。我才真正发现它有用,主要是为了提高可读性。
AlexMorley-Finch 2014年

Answers:


130

sprintf 具有原始printf的所有格式化功能,这意味着您不仅可以在字符串中插入变量值,还可以做更多的事情。

例如,指定数字格式(十六进制,十进制,八进制),小数位数,填充等。Google for printf,您会发现很多例子。关于printf维基百科文章应该可以帮助您入门。


46
多年前,我写了一个很好的零填充数字函数。在意识到我可以做之前花了很sprintf('%03d', $num)
长时间

5
PHP sprintf具有所有格式设置功能 ...实际上缺少重要的功能,%,8d例如获得正确对齐的8位数字和逗号分隔的整数。使用number_format这种printf样式不太方便。
e2-e4

3
@DisgruntledGoat您可以分解该格式字符串('%03d')吗?我认为这真的很有帮助。谢谢。
Smee先生2014年

3
您还可以重复并重新排列要显示的参数。例如,sprintf('%2$s %1$s, my name is also %1$s.', 'World', 'Hello');导致Hello World, my name is also World.
fyrye '17

77

sprintf有许多用例,但是我使用它们的一种方式是通过在数据库中存储这样的字符串:“你好,我的名字是%s”或在PHP类中作为常量。这样,当我想使用该字符串时,我可以简单地做到这一点:

$name = 'Josh';
// $stringFromDB = 'Hello, My Name is %s';
$greeting = sprintf($stringFromDB, $name);
// $greetting = 'Hello, My Name is Josh'

本质上,它允许代码中的某些分离。如果我在代码中的很多地方都使用了“ Hello,我的名字是%s”,则可以在一个地方将其更改为“%s是我的名字”,并且它会自动更新其他所有地方,而不必转到每个实例并四处移动级联。


8
这个。现在添加编号的参数和翻译:]
Konerak

46

的另一种用法sprintf是在本地化应用程序中,因为参数sprintf不必按照它们在格式字符串中出现的顺序排列。

例:

$color = 'blue';
$item = 'pen';

sprintf('I have a %s %s', $color, $item);

但是像法语这样的语言对单词的排序却有所不同:

$color = 'bleu';
$item = 'stylo';

sprintf('J\'ai un %2$s %1$s', $color, $item);

(是的,我的法语很烂:我在学校学习德语!)

实际上,您可以使用gettext来存储本地化的字符串,但是您可以理解。



37

翻译起来更容易。

echo _('Here is the result: ') . $result . _(' for this date ') . $date;

翻译(gettext)字符串现在是:

  • 结果如下:
  • 在这个日期

当翻译成其他语言时,这可能是不可能的,或者导致非常奇怪的句子。

现在如果你有

echo sprintf(_("Here is the result: %s for this date %s"), $result, $date);

翻译(gettext)字符串现在是:

  • 结果如下:此日期%s中的%s

这更有意义,而且翻译成其他语言要灵活得多。


1
我看到这将是非常有用的+1
JasonDavis

6
尽管对于翻译而言,最好使用位置说明符,以便在翻译时提供一些额外的灵活性,即:echo sprintf(_("Here is the result: %1$s for this date %2$s"), $result, $date);这样一来,翻译就可以修改短语For the date %2$s, the result is %1$s
PatrikAkerstrand 2012年

1
尽管即使它们不存在于原始字符串中,您也可以将它们放入翻译中,它们将起作用。
频谱

17

我发现的最好原因是,它允许您将所有语言字符串放置在您的语言文件中,以便人们可以根据需要对其进行翻译和排序-但您仍然知道,无论字符串采用哪种格式-您都希望显示用户名。

例如,您的网站在页面顶部会显示“欢迎回来[[用户]]”。作为程序员,您不知道或不在乎 UI家伙将如何编写-您只知道用户名将显示在消息中的某个位置。

因此,您确实可以将消息嵌入代码中,而不必担心该消息的实际含义。

郎文件(EN_US):

...
$lang['welcome_message'] = 'Welcome back %s';
...

然后,您可以在实际的php代码中使用此语言,以任何语言支持任何类型的消息。

sprintf($lang['welcome_message'], $user->name())

我从未担心过要处理语言文件,但这对我来说似乎是最有用的用途之一
JasonDavis,2009年

因为我没有做太多的多语言应用程序,所以这个示例对我也很有意义。
MKN Web Solutions

9

您为什么要使用它?

当使用(外部)语言字符串源时,它非常有用。如果在给定的多语言字符串中需要固定数量的变量,则只需要知道正确的顺序即可:

en.txt

not_found    = "%s could not be found."
bad_argument = "Bad arguments for function %s."
bad_arg_no   = "Bad argument %d for function %s."

hu.txt

not_found    = "A keresett eljárás (%s) nem található."
bad_argument = "Érvénytelen paraméterek a(z) %s eljárás hívásakor."
bad_arg_no   = "Érvénytelen %d. paraméter a(z) %s eljárás hívásakor."

插入的变量甚至不必位于多种语言的开头或结尾,而仅是它们的顺序很重要。

当然,即使性能有所提高,您也可以编写自己的函数来执行此替换,但是这样做的速度要快得多(假设您有一个类Language可以读取语言字符串)

/**
 * throws exception with message($name = "ExampleMethod"):
 *  - using en.txt: ExampleMethod could not be found.
 *  - using hu.txt: A keresett eljárás (ExampleMethod) nem található.
 */
throw new Exception(sprintf(Language::Get('not_found'), $name));

/**
 * throws exception with message ($param_index = 3, $name = "ExampleMethod"):
 *  - using en.txt: Bad argument 3 for function ExampleMethod.
 *  - using hu.txt: Érvénytelen 3. paraméter a(z) ExampleMethod eljárás hívásakor.
 */
throw new Exception(sprintf(Language::Get('bad_arg_no'), $param_index, $name));

它还具有以下功能 printf,因此也是格式化多种类型变量的一种方法,例如:


8

如前所述,它允许格式化输入数据。例如,强制使用2dp,4位数字等。这对于构建MySQL查询字符串非常有用。

另一个优点是,它允许您将字符串的布局与正在馈入其中的数据分开,就像在传入参数中一样。例如,对于MySQL查询:

// For security, you MUST sanitise ALL user input first, eg:
$username = mysql_real_escape_string($_POST['username']); // etc.
// Now creating the query:
$query = sprintf("INSERT INTO `Users` SET `user`='%s',`password`='%s',`realname`='%s';", $username, $passwd_hash, $realname);

当然,此方法还有其他用途,例如将输出打印为HTML等时。

编辑:出于安全原因,在使用上述技术时,必须mysql_real_escape_string()在使用此方法之前清理所有输入变量,以防止MySQL插入攻击。如果您解析未经验证的输入,则您的站点和服务器将被黑客入侵。(当然,除了那些完全由您的代码构造并保证安全的变量之外)。


2
该代码容易受到SQL注入的攻击。使用占位符,而不是查询插值!
duskwuff -inactive- 09年

@duskwuff-按原样使用,您是对的。抱歉,我已经修改了答案。
塞缪尔·贾斯凯

7

使用sprintf()格式化字符串更干净,更安全。

例如,当您处理输入变量时,它通过预先指定期望的格式(例如,您期望字符串[ %s]或数字[ %d])来防止意外的意外。这可能有助于缓解SQL注入的风险,但是并不能防止字符串包含引号。

它还有助于处理浮点数,您可以显式指定位数精度(例如%.2f),从而避免使用转换函数。

另一个优势是,大多数主要的编程语言都有自己的实施sprintf(),因此,一旦您熟悉了它,它就会更容易使用,而不是学习一种新语言(例如,如何连接字符串或转换浮点数)。

总而言之,使用好习惯是为了使代码更简洁,可读性更好。

例如,请参见下面的实际示例

$insert .= "('".$tr[0]."','".$tr[0]."','".$tr[0]."','".$tr[0]."'),";

或一些简单的示例,例如打印'1','2','3','4'

print "foo: '" . $a . "','" . $b . "'; bar: '" . $c . "','" . $d . "'" . "\n";

并使用格式化的字符串打印:

printf("foo: '%d','%d'; bar: '%d','%d'\n", $a, $b, $c, $d);

where printf()等效于sprintf(),但是它输出格式化的字符串,而不是将其返回(返回到变量)。

哪个更易读?


1
我不同意,但也值得一提,我在6到7年前问过这个问题,回想起我从那时起一直用不着的回忆!我确实希望有一天能找到它的地方,并且确实相信它有一个地方,但是自从多年前问这个问题以来我一直没有使用它,真是可笑!
JasonDavis

我可以说这有点类似于在PHP中为PDO设置变量。另外,如果您想给它提供可读性参数,那么在这种情况下,我更喜欢使用sprintf()分成多行的方式,其中带有占位符的字符串在1行上,并且值在其下一行或什至多行上,都正确缩进课程。以我描述的方式使用您的演示... i.imgur.com/DFi3ur8.png
JasonDavis 2015年

与存储过程一起使用预准备语句是防止sql注入(而不是sprintf)的最佳方法。如果字符串恰好包含单引号,您仍然可以轻松地使用sprintf进行sql注入
HumbleWebDev

4

除非我最近使用过,否则即使我也一样。当您根据用户输入生成文档时,这将很方便。

"<p>Some big paragraph ".$a["name"]." again have tot ake care of space and stuff .". $a["age"]. "also would be hard to keep track of punctuations and stuff in a really ".$a["token"]. paragarapoh.";

可以很容易地写成

sprintf("Some big paragraph %s. Again have to take care of space and stuff.%s also wouldnt be hard to keep track of punctuations and stuff in a really %s paragraph",$a,$b,$c);

或者,您可以这样做:"<p>Some big paragraph {$a['name']} more words {$a['age']}</p>"。但是,请不要忘记清理用户输入的内容,否则您最多可以打开XSS漏洞的代码。
第三方

您还可以使用HEREDOC语法。我认为,两者都比sprintf语法更易于阅读。
第三者

4

这个:

"<p>Some big paragraph ".$a["name"]." again have to take care of space and stuff .". $a["age"]. "also would be hard to keep track of punctuations and stuff in a really ".$a["token"]. paragraph.";

也可以写成:

"<p>Some big paragraph {$a['name']} again have to take care of space and stuff .{$a['age']} also would be hard to keep track of punctuations and stuff in a really {$a['token']} paragraph.";

我认为这更易于阅读,但是我可以看到它用于本地化或格式化的用法。


3

在某些典型情况下,您需要对输出格式进行更精确的控制。例如,要确保一个特定的值在其前面填充特定数量的空格(根据其长度),或者以特定的精确格式输出数字,可能会很棘手。

PHP手册中有很多示例

同样,您的“更容易编写”的示例也是如此。尽管回声可能更易于编写,但sprintf却更易于阅读,尤其是在您有很多变量的情况下。

使用sprintf或printf的另一个原因可能是您想让用户定义某些值的输出格式-您可以放心地允许他们定义与sprintf兼容的输出格式。

哦,您的示例实际上是错误的。sprintf返回字符串,但echo不- echo立即输出它,什么也不返回,而sprintf只是返回它。


3
  1. 如果您使用过C / C ++,那么您将习惯于sprintf函数。

  2. 第二行很有可能效率较低。Echo被设计为输出命令,而sprintf被设计为进行字符串令牌替换。我不是PHP人士,但我怀疑回声涉及更多对象。如果它的行为类似于Java,则每次将新内容添加到列表中时都会创建一个新字符串,因此最终会创建4个字符串。


Java的最新版本将在后台将String串联转换为StringBuilder。
RodeoClown

1
PHP中的字符串不是对象。第二个示例使用语言构造而不是函数调用,因此速度明显更快。
Tamlyn

3

有时我会得到这样的东西,我认为它更容易阅读:

$fileName = sprintf('thumb_%s.%s', 
                    $fileId,
                    $fileInfo['extension']);

3

我通常使用sprintf来确保来自用户输入的id是整数,例如:

// is better use prepared statements, but this is practical sometimes
$query = sprintf("SELECT * from articles where id = %d;",$_GET['article_id']);

也用于做基本模板(用于html邮件或其他东西),因此您可以在许多地方重复使用模板:

$mail_body = "Hello %s, ...";
$oneMail = sprintf($mail_body, "Victor");
$anotherMail = sprintf($mail_body, "Juan");

将数字格式化为不同的表示形式(八进制,控制小数位等)也非常有用。


仅使用intval($ _ GET [“ article_id”])难道不是很容易吗?好吧,实际上它需要花费更多的打字时间... *注意您的方法“ Dag nabbit。–
user97410

对于这类代码,最好使用内置的mysql_real_escape_string():$ query = sprintf(“ SELECT * from id =%d;的文章,”,mysql_real_escape_string($ _ GET ['article_id'])));
bruno.braga 2012年

3
define('TEXT_MESSAGE', 'The variable "%s" is in the middle!');

sprintf(TEXT_MESSAGE, "Var1");
sprintf(TEXT_MESSAGE, "Var2");
sprintf(TEXT_MESSAGE, "Var3");

3

在格式化使用数字的字符串时,sprintf特别有用。例如,

$oranges = -2.34;
echo sprintf("There are %d oranges in the basket", $oranges);

Output: There are -2 oranges in the basket

橘子的格式为整数(-2),但是如果将%u用作无符号值,则将换成正数。为了避免这种行为,我使用绝对函数abs()将数字四舍五入为零,如下所示:

$oranges = -5.67;
echo sprintf("There are %d oranges in the basket", abs($oranges));

Output: There are 5 oranges in the basket

最终结果是一个语句,具有很高的可读性,逻辑结构,清晰的格式以及根据需要添加其他变量的灵活性。随着变量数量的增加以及与操纵这些变量的函数的结合,收益变得更加明显。最后一个例子:

$oranges = -3.14;
$apples = 1.5;
echo sprintf("There are %d oranges and %d apples", abs($oranges), abs($apples));

Output: There are 3 oranges and 4 apples

sprintf语句的左侧清楚地表示了字符串和期望值的类型,而右侧清楚地表示了所使用的变量以及如何对其进行操作。


我正准备为它的出色的第一句话(“ sprintf在格式化使用数字的字符串时特别有用”)赞美您的答案,这确实是sprintfPHP中最重要的一点,但是随后看到了那些复杂的示例,它们从“遗漏”中获得了成果水果:),每个水果只重复显示一次%d(和abs(),与主题无关),而不是显示过多的数字格式设置选项(这是相关的),我改变了主意。但是,如果您更新答案,我仍然会在冰箱中为您提供支持。:)谢谢,加油!
Sz。

3

恩,它sprintf 具有许多功能,如下所示:

几个月前,我需要将秒转换为hour:minute:seconds格式,就像$t = 494050 //seconds我想打印一样,137 h 14 m 10 s所以我想出了php函数,springf()我只保留秒$techo sprintf("%02d h %s%02d m %s%02d s", floor($t/3600), $f, ($t/60)%60, $f, $t%60);给了我137 h 14 m 10 s

如果我们知道如何使用sprintf()函数,它将非常有用。


2

该参数与使用模板的参数相同。您需要将Textsleev与实际变量值分开。除了我们提到的sprintf的其他功能之外,这还只是一种样式。


2

使用sprintf的一个非常好的用例是输出带数字的填充格式,以及在字符串中混合使用不同类型时。在许多情况下,它可能更易于阅读,并且使打印同一变量(尤其是数字变量)的不同表示形式变得非常简单。


2

在普通串联中使用sprintf()函数的优点是,可以对要串联的变量应用不同类型的格式。

就你而言

$output = sprintf("Here is the result: %s for this date %s", $result, $date);

$output = 'Here is the result: ' .$result. ' for this date ' .$date;

让我们来 $result = 'passed'; date = '23rd';

使用普通的串联,您只能得到输出:

Here is the result: passed for this date 23rd

但是,如果使用sprintf(),则可以得到修改的输出,例如:

$output = sprintf('Here is the result: %.4s for this date %.2s',$result,$date);
echo $output;

输出:

Here is the result: pass for this date 23

2

sprintf()与...非常相似printf()。如果您知道printf()详细信息sprintf(),甚至vsprintf()不难理解。

的事情之一不同sprintf()printf()是,你需要声明一个变量可以捕捉到该函数的输出,因为它不直接打印/回声什么。让我们看下面的代码片段:

printf("Hello %s", "world"); // "Hello world"

sprintf("Hello %s", "world"); // does not display anything

echo sprintf("Hello %s", "world"); // "Hello world"

$a = sprintf("Hello %s", "world"); // does not display anything

echo $a;// "Hello world"

希望能有所帮助。



0

在循环中使用sprintf时必须小心:

$a = 'Anton';
$b = 'Bert';
$c = 'Corni';
$d = 'Dora';
$e = 'Emiel';
$f = 'Falk';
$loops = 10000000;

$time = microtime(true);

for ($i = 0; $i < $loops; $i++)
{
    $test = $a . $b . $c . $d . $e . $f;
}

$concatTime = microtime(true) - $time;

$time = microtime(true);

for ($i = 0; $i < $loops; $i++)
{
    $test = "$a $b $c $d $e $f";
}

$concat2Time = microtime(true) - $time;

$time = microtime(true);

for ($i = 0; $i < $loops; $i++)
{
    $test = sprintf('%s %s %s %s %s %s', $a, $b, $c, $d, $e, $f);
}

$sprintfTime = microtime(true) - $time;

echo 'Loops: ' . $loops . '<br>';
echo '\'$a . $b . $c . $d . $e . $f\'' . ' needs ' . $concatTime  . 's<br>';
echo '"$a $b $c $d $e $f"' . ' needs ' . $concat2Time  . 's<br>';
echo 'sprintf(\'%s %s %s %s %s %s\', $a, $b, $c, $d, $e, $f)' . ' needs ' . $sprintfTime  . 's<br>';

这导致以下情况(在我的本地计算机上使用PHP 7.2):

循环次数:10000000

'$ a。$ b。$ c。$ d。$ e。$ f'需要1.4507689476013s

“ $ a $ b $ c $ d $ e $ f”需要1.9958319664001s

sprintf('%s%s%s%s%s%s',$ a,$ b,$ c,$ d,$ e,$ f)需要9.1771278381348s


0

我将其用于发送给用户或其他“漂亮”功能的消息。例如,如果我知道我将使用用户名。

$name = 'Some dynamic name';

并在这种情况下使用多个消息。(即阻止或关注另一个用户)

$messageBlock = 'You have blocked %s from accessing you.';
$messageFollow = 'Following %s is a great idea!';

您可以创建一个对用户执行某些操作的通用函数,并添加此字符串,无论句子的结构如何,它都应该看起来不错。我总是不喜欢仅将字符串附加在一起,并不断使用点符号以及关闭和重新打开字符串以使句子看起来不错。我一开始像大多数粉丝一样,但是当需要操纵多个字符串并且您不想每次都硬编码变量的位置时,这似乎很有用。

想一想,看起来更好的是什么?

return $messageOne === true ? $name.'. Please use the next example' : 'Hi '.$name.', how are you?'

要么

$message = $messageOne === true ? 'Option one %s' 
: ($messageTwo === true ? 'Option Two %s maybe?' : '%s you can choose from tons of grammatical instances and not have to edit variable placement and strings');

return sprintf($message, $name);

当然,这是一个额外的步骤,但是如果您的条件检查执行了许多其他功能操作,那么引号和附加内容就会开始妨碍功能编码的方式。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.