如何比较 C 中的字符串#

比较字符串以回答以下两个问题之一:“这两个字符串是否相等?”或“排序时应按什么顺序放置这些字符串?”

以下因素使这两个问题复杂化:

  • 可以选择序数或语言学比较。
  • 您可以选择大小写是否重要。
  • 可以选择文化特定的比较。
  • 语言比较是区域性和依赖于平台的。

System.StringComparison枚举字段表示以下选项:

  • CurrentCulture:使用文化相关的排序规则以及当前区域比较字符串。
  • CurrentCultureIgnoreCase:使用对区域性敏感的排序规则、结合当前区域性,并忽略被比较字符串的大小写来比较字符串。
  • 不变区域性:使用文化敏感的排序规则和不变区域性比较字符串。
  • InvariantCultureIgnoreCase:使用区分区域性的排序规则、固定区域性比较字符串,并忽略要比较的字符串的大小写。
  • 序号:使用序号(二进制)排序规则比较字符串。
  • OrdinalIgnoreCase:使用序号(二进制)排序规则比较字符串,并忽略要比较的字符串的大小写。

比较字符串时,可以定义它们之间的顺序。 比较用于对字符串序列进行排序。 序列按已知顺序排列后,无论是软件还是人类都可以更轻松地进行搜索。 其他比较可能会检查字符串是否相同。 这些相同性检查类似于相等性,但可能会忽略某些差异,例如事例差异。

默认序数比较

默认情况下,最常见的操作:

string root = @"C:\users";
string root2 = @"C:\Users";

bool result = root.Equals(root2);
Console.WriteLine($"Ordinal comparison: <{root}> and <{root2}> are {(result ? "equal." : "not equal.")}");

result = root.Equals(root2, StringComparison.Ordinal);
Console.WriteLine($"Ordinal comparison: <{root}> and <{root2}> are {(result ? "equal." : "not equal.")}");

Console.WriteLine($"Using == says that <{root}> and <{root2}> are {(root == root2 ? "equal" : "not equal")}");

比较字符串时,默认序号比较不会考虑语言规则。 它会比较两个字符串中每个 Char 对象的二进制值。 因此,默认序号比较也区分大小写。

使用String.Equals==!=运算符进行相等性测试与使用String.CompareToCompare(String, String)方法进行字符串比较不同。 它们都执行区分大小写的比较。 但是,虽然相等性测试执行顺序比较,CompareToCompare 方法使用当前区域性执行文化感知语言比较。 通过调用显式指定要执行的比较类型的重载来明确说明代码的意图。

可以将 is 运算符和 常量模式 用作当右作数为常量时的替代 == 方法。

不区分大小写的顺序比较

使用 String.Equals(String, StringComparison) 方法可以为不区分大小写的序号比较指定 StringComparison.OrdinalIgnoreCaseStringComparison 值。 如果为StringComparison参数指定值StringComparison.OrdinalIgnoreCase,则还有一个静态String.Compare(String, String, StringComparison)方法执行不区分大小写的顺序比较。 以下代码显示了这些比较:

string root = @"C:\users";
string root2 = @"C:\Users";

bool result = root.Equals(root2, StringComparison.OrdinalIgnoreCase);
bool areEqual = String.Equals(root, root2, StringComparison.OrdinalIgnoreCase);
int comparison = String.Compare(root, root2, comparisonType: StringComparison.OrdinalIgnoreCase);

Console.WriteLine($"Ordinal ignore case: <{root}> and <{root2}> are {(result ? "equal." : "not equal.")}");
Console.WriteLine($"Ordinal static ignore case: <{root}> and <{root2}> are {(areEqual ? "equal." : "not equal.")}");
if (comparison < 0)
{
    Console.WriteLine($"<{root}> is less than <{root2}>");
}
else if (comparison > 0)
{
    Console.WriteLine($"<{root}> is greater than <{root2}>");
}
else
{
    Console.WriteLine($"<{root}> and <{root2}> are equivalent in order");
}

在执行不区分大小写的序数比较时,这些方法使用 不变文化 的大小写约定。

语言比较

许多字符串比较方法(例如 String.StartsWith)默认使用 当前区域性 的语言规则来对输入进行排序。 这种语言比较有时称为“单词排序顺序”。执行语言比较时,某些非字母 Unicode 字符可能分配了特殊权重。 例如,连字符“-”可能为其分配了一个小权重,以便“co-op”和“coop”按排序顺序显示在彼此旁边。 程序可能会忽略某些非打印控制字符。 此外,某些 Unicode 字符可能等效于一系列 Char 实例。 下面的示例使用短语“他们在街上跳舞”。在德语中使用一个字符串中的“ss”(U+0073 U+0073),另一个字符串中使用“ß”(U+00DF)。 语言上(在 Windows 中),“ss”等于德语Esszet:“en-US”和“de-DE”文化中的'ß'字符。

string first = "Sie tanzen auf der Straße.";
string second = "Sie tanzen auf der Strasse.";

Console.WriteLine($"First sentence is <{first}>");
Console.WriteLine($"Second sentence is <{second}>");

bool equal = String.Equals(first, second, StringComparison.InvariantCulture);
Console.WriteLine($"The two strings {(equal == true ? "are" : "are not")} equal.");
showComparison(first, second);

string word = "coop";
string words = "co-op";
string other = "cop";

showComparison(word, words);
showComparison(word, other);
showComparison(words, other);
void showComparison(string one, string two)
{
    int compareLinguistic = String.Compare(one, two, StringComparison.InvariantCulture);
    int compareOrdinal = String.Compare(one, two, StringComparison.Ordinal);
    if (compareLinguistic < 0)
    {
        Console.WriteLine($"<{one}> is less than <{two}> using invariant culture");
    }
    else if (compareLinguistic > 0)
    {
        Console.WriteLine($"<{one}> is greater than <{two}> using invariant culture");
    }
    else
    {
        Console.WriteLine($"<{one}> and <{two}> are equivalent in order using invariant culture");
    }

    if (compareOrdinal < 0)
    {
        Console.WriteLine($"<{one}> is less than <{two}> using ordinal comparison");
    }
    else if (compareOrdinal > 0)
    {
        Console.WriteLine($"<{one}> is greater than <{two}> using ordinal comparison");
    }
    else
    {
        Console.WriteLine($"<{one}> and <{two}> are equivalent in order using ordinal comparison");
    }
}

在 Windows 上,在 .NET 5 之前,当从语言比较更改为序号比较时,“cop”、“coop”和“co-op”的排序顺序会发生变化。 这两个德语句子在使用不同类型的比较时表现出不同的对比。 在 .NET 5 之前,.NET 全球化 API 使用了 国家语言支持 (NLS) 库。 在 .NET 5 及更高版本中,.NET 全球化 API 使用 国际组件库(ICU),从而统一所有受支持的操作系统中的 .NET 全球化行为。

使用特定文化的比较

以下示例存储用于 en-US 和 de-DE 区域性文化的 CultureInfo 对象。 使用 CultureInfo 对象来执行比较,以确保符合区域性特定的比较。 使用的文化会影响语言比较。 以下示例显示了使用“en-US”区域性和“de-DE”区域性比较两个德语句子的结果:

string first = "Sie tanzen auf der Straße.";
string second = "Sie tanzen auf der Strasse.";

Console.WriteLine($"First sentence is <{first}>");
Console.WriteLine($"Second sentence is <{second}>");

var en = new System.Globalization.CultureInfo("en-US");

// For culture-sensitive comparisons, use the String.Compare
// overload that takes a StringComparison value.
int i = String.Compare(first, second, en, System.Globalization.CompareOptions.None);
Console.WriteLine($"Comparing in {en.Name} returns {i}.");

var de = new System.Globalization.CultureInfo("de-DE");
i = String.Compare(first, second, de, System.Globalization.CompareOptions.None);
Console.WriteLine($"Comparing in {de.Name} returns {i}.");

bool b = String.Equals(first, second, StringComparison.CurrentCulture);
Console.WriteLine($"The two strings {(b ? "are" : "are not")} equal.");

string word = "coop";
string words = "co-op";
string other = "cop";

showComparison(word, words, en);
showComparison(word, other, en);
showComparison(words, other, en);
void showComparison(string one, string two, System.Globalization.CultureInfo culture)
{
    int compareLinguistic = String.Compare(one, two, en, System.Globalization.CompareOptions.None);
    int compareOrdinal = String.Compare(one, two, StringComparison.Ordinal);
    if (compareLinguistic < 0)
    {
        Console.WriteLine($"<{one}> is less than <{two}> using en-US culture");
    }
    else if (compareLinguistic > 0)
    {
        Console.WriteLine($"<{one}> is greater than <{two}> using en-US culture");
    }
    else
    {
        Console.WriteLine($"<{one}> and <{two}> are equivalent in order using en-US culture");
    }

    if (compareOrdinal < 0)
    {
        Console.WriteLine($"<{one}> is less than <{two}> using ordinal comparison");
    }
    else if (compareOrdinal > 0)
    {
        Console.WriteLine($"<{one}> is greater than <{two}> using ordinal comparison");
    }
    else
    {
        Console.WriteLine($"<{one}> and <{two}> are equivalent in order using ordinal comparison");
    }
}

敏感于文化差异的比较通常用于比较和排序用户输入的字符串。 这些字符串的字符和排序约定可能因用户计算机的区域设置而异。 即使是包含相同字符的字符串,也可能因当前线程的文化差异以不同的方式排序。

在数组中进行语言学排序和搜索字符串

以下示例演示如何使用依赖于当前区域性的语言比较对数组中的字符串进行排序和搜索。 使用静态的System.StringComparer方法时,需传入Array参数。

以下示例演示如何按照当前语言文化设置对字符串数组进行排序:

string[] lines =
[
    @"c:\public\textfile.txt",
    @"c:\public\textFile.TXT",
    @"c:\public\Text.txt",
    @"c:\public\testfile2.txt"
];

Console.WriteLine("Non-sorted order:");
foreach (string s in lines)
{
    Console.WriteLine($"   {s}");
}

Console.WriteLine("\n\rSorted order:");

// Specify Ordinal to demonstrate the different behavior.
Array.Sort(lines, StringComparer.CurrentCulture);

foreach (string s in lines)
{
    Console.WriteLine($"   {s}");
}

对数组进行排序后,可以使用二进制搜索来搜索条目。 二进制搜索从集合中间开始,以确定集合的一半将包含所寻求的字符串。 每个后续比较将集合的剩余部分细分为一半。 该数组已使用 StringComparer.CurrentCulture 进行排序。 本地函数 ShowWhere 显示有关找到字符串的位置的信息。 如果未找到字符串,则返回的值指示如果找到时它应该所在的位置。

string[] lines =
[
    @"c:\public\textfile.txt",
    @"c:\public\textFile.TXT",
    @"c:\public\Text.txt",
    @"c:\public\testfile2.txt"
];
Array.Sort(lines, StringComparer.CurrentCulture);

string searchString = @"c:\public\TEXTFILE.TXT";
Console.WriteLine($"Binary search for <{searchString}>");
int result = Array.BinarySearch(lines, searchString, StringComparer.CurrentCulture);
ShowWhere<string>(lines, result);

Console.WriteLine($"{(result > 0 ? "Found" : "Did not find")} {searchString}");

void ShowWhere<T>(T[] array, int index)
{
    if (index < 0)
    {
        index = ~index;

        Console.Write("Not found. Sorts between: ");

        if (index == 0)
        {
            Console.Write("beginning of sequence and ");
        }
        else
        {
            Console.Write($"{array[index - 1]} and ");
        }

        if (index == array.Length)
        {
            Console.WriteLine("end of sequence.");
        }
        else
        {
            Console.WriteLine($"{array[index]}.");
        }
    }
    else
    {
        Console.WriteLine($"Found at index {index}.");
    }
}

集合中的顺序排序和搜索

以下代码使用 System.Collections.Generic.List<T> 集合类来存储字符串。 使用该方法对字符串进行 List<T>.Sort 排序。 此方法需要一个委托来比较和排序两个字符串。 该方法 String.CompareTo 提供比较函数。 运行示例并观察顺序。 此排序操作使用的是顺序区分大小写的排序。 将使用静态 String.Compare 方法指定不同的比较规则。

List<string> lines =
[
    @"c:\public\textfile.txt",
    @"c:\public\textFile.TXT",
    @"c:\public\Text.txt",
    @"c:\public\testfile2.txt"
];

Console.WriteLine("Non-sorted order:");
foreach (string s in lines)
{
    Console.WriteLine($"   {s}");
}

Console.WriteLine("\n\rSorted order:");

lines.Sort((left, right) => left.CompareTo(right));
foreach (string s in lines)
{
    Console.WriteLine($"   {s}");
}

排序后,可以使用二进制搜索来搜索字符串列表。 以下示例演示如何使用相同的比较函数搜索已排序的列表。 本地函数 ShowWhere 显示所查找文本的位置或将要显示的位置。

List<string> lines =
[
    @"c:\public\textfile.txt",
    @"c:\public\textFile.TXT",
    @"c:\public\Text.txt",
    @"c:\public\testfile2.txt"
];
lines.Sort((left, right) => left.CompareTo(right));

string searchString = @"c:\public\TEXTFILE.TXT";
Console.WriteLine($"Binary search for <{searchString}>");
int result = lines.BinarySearch(searchString);
ShowWhere<string>(lines, result);

Console.WriteLine($"{(result > 0 ? "Found" : "Did not find")} {searchString}");

void ShowWhere<T>(IList<T> collection, int index)
{
    if (index < 0)
    {
        index = ~index;

        Console.Write("Not found. Sorts between: ");

        if (index == 0)
        {
            Console.Write("beginning of sequence and ");
        }
        else
        {
            Console.Write($"{collection[index - 1]} and ");
        }

        if (index == collection.Count)
        {
            Console.WriteLine("end of sequence.");
        }
        else
        {
            Console.WriteLine($"{collection[index]}.");
        }
    }
    else
    {
        Console.WriteLine($"Found at index {index}.");
    }
}

始终确保使用同一类型的比较进行排序和搜索。 使用不同的比较类型进行排序和搜索会产生意外的结果。

集合类(例如System.Collections.HashtableSystem.Collections.Generic.Dictionary<TKey,TValue>System.Collections.Generic.List<T>)的构造函数在元素或键的类型为string时接受System.StringComparer参数。 一般情况下,您应尽可能使用这些构造函数,并指定StringComparer.OrdinalStringComparer.OrdinalIgnoreCase

另请参阅