C Language
Smyczki
Szukaj…
Wprowadzenie
W C łańcuch nie jest wewnętrznym typem. Ciąg C to konwencja polegająca na tym, że ma jednowymiarową tablicę znaków, która jest zakończona znakiem null, '\0'
.
Oznacza to, że ciąg C o treści "abc"
będzie miał cztery znaki 'a'
, 'b'
, 'c'
i '\0'
.
Zobacz podstawowe wprowadzenie do przykładu ciągów .
Składnia
- char str1 [] = "Witaj, świecie!"; / * Możliwość modyfikacji * /
- char str2 [14] = "Witaj, świecie!"; / * Możliwość modyfikacji * /
- char * str3 = "Witaj, świecie!"; /* Niemodyfikowalny*/
Oblicz długość: strlen ()
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(int argc, char **argv)
{
/* Exit if no second argument is found. */
if (argc != 2)
{
puts("Argument missing.");
return EXIT_FAILURE;
}
size_t len = strlen(argv[1]);
printf("The length of the second argument is %zu.\n", len);
return EXIT_SUCCESS;
}
Ten program oblicza długość drugiego argumentu wejściowego i zapisuje wynik w len
. Następnie drukuje tę długość na terminalu. Na przykład po uruchomieniu z parametrami program_name "Hello, world!"
, program wyświetli The length of the second argument is 13.
ponieważ ciąg Hello, world!
ma 13 znaków.
strlen
zlicza wszystkie bajty od początku łańcucha aż do kończącego znaku NUL '\0'
, ale bez niego. Jako taki, można go używać tylko wtedy, gdy ciąg znaków ma zagwarantowane zakończenie NUL.
Należy również pamiętać, że jeśli ciąg zawiera dowolne znaki Unicode, strlen
nie powie ci, ile znaków jest w ciągu (ponieważ niektóre znaki mogą mieć wiele bajtów). W takich przypadkach musisz sam policzyć znaki ( tj. Jednostki kodu). Rozważ wyniki następującego przykładu:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
char asciiString[50] = "Hello world!";
char utf8String[50] = "Γειά σου Κόσμε!"; /* "Hello World!" in Greek */
printf("asciiString has %zu bytes in the array\n", sizeof(asciiString));
printf("utf8String has %zu bytes in the array\n", sizeof(utf8String));
printf("\"%s\" is %zu bytes\n", asciiString, strlen(asciiString));
printf("\"%s\" is %zu bytes\n", utf8String, strlen(utf8String));
}
Wynik:
asciiString has 50 bytes in the array
utf8String has 50 bytes in the array
"Hello world!" is 12 bytes
"Γειά σου Κόσμε!" is 27 bytes
Kopiowanie i łączenie: strcpy (), strcat ()
#include <stdio.h>
#include <string.h>
int main(void)
{
/* Always ensure that your string is large enough to contain the characters
* and a terminating NUL character ('\0')!
*/
char mystring[10];
/* Copy "foo" into `mystring`, until a NUL character is encountered. */
strcpy(mystring, "foo");
printf("%s\n", mystring);
/* At this point, we used 4 chars of `mystring`, the 3 characters of "foo",
* and the NUL terminating byte.
*/
/* Append "bar" to `mystring`. */
strcat(mystring, "bar");
printf("%s\n", mystring);
/* We now use 7 characters of `mystring`: "foo" requires 3, "bar" requires 3
* and there is a terminating NUL character ('\0') at the end.
*/
/* Copy "bar" into `mystring`, overwriting the former contents. */
strcpy(mystring, "bar");
printf("%s\n", mystring);
return 0;
}
Wyjścia:
foo
foobar
bar
Jeśli dołączasz do lub z lub kopiujesz z istniejącego łańcucha, upewnij się, że jest on zakończony zerem!
Literały łańcuchowe (np. "foo"
) będą zawsze kończone przez NUL przez kompilator.
Porównanie: strcmp (), strncmp (), strcasecmp (), strncasecmp ()
Funkcje strcase*
nie są standardem C, ale rozszerzeniem POSIX.
Funkcja strcmp
leksykograficznie porównuje dwie tablice znaków zakończone znakiem null. Funkcje zwracają wartość ujemną, jeśli pierwszy argument pojawia się przed drugim w kolejności leksykograficznej, zero, jeśli porównują równe, lub dodatnią, jeśli pierwszy argument pojawia się po drugim w kolejności leksykograficznej.
#include <stdio.h>
#include <string.h>
void compare(char const *lhs, char const *rhs)
{
int result = strcmp(lhs, rhs); // compute comparison once
if (result < 0) {
printf("%s comes before %s\n", lhs, rhs);
} else if (result == 0) {
printf("%s equals %s\n", lhs, rhs);
} else { // last case: result > 0
printf("%s comes after %s\n", lhs, rhs);
}
}
int main(void)
{
compare("BBB", "BBB");
compare("BBB", "CCCCC");
compare("BBB", "AAAAAA");
return 0;
}
Wyjścia:
BBB equals BBB
BBB comes before CCCCC
BBB comes after AAAAAA
Jako strcmp
funkcja strcasecmp
porównuje również leksykograficznie swoje argumenty po przetłumaczeniu każdego znaku na jego małą literę:
#include <stdio.h>
#include <string.h>
void compare(char const *lhs, char const *rhs)
{
int result = strcasecmp(lhs, rhs); // compute case-insensitive comparison once
if (result < 0) {
printf("%s comes before %s\n", lhs, rhs);
} else if (result == 0) {
printf("%s equals %s\n", lhs, rhs);
} else { // last case: result > 0
printf("%s comes after %s\n", lhs, rhs);
}
}
int main(void)
{
compare("BBB", "bBB");
compare("BBB", "ccCCC");
compare("BBB", "aaaaaa");
return 0;
}
Wyjścia:
BBB equals bBB
BBB comes before ccCCC
BBB comes after aaaaaa
strncmp
i strncasecmp
porównują najwyżej n znaków:
#include <stdio.h>
#include <string.h>
void compare(char const *lhs, char const *rhs, int n)
{
int result = strncmp(lhs, rhs, n); // compute comparison once
if (result < 0) {
printf("%s comes before %s\n", lhs, rhs);
} else if (result == 0) {
printf("%s equals %s\n", lhs, rhs);
} else { // last case: result > 0
printf("%s comes after %s\n", lhs, rhs);
}
}
int main(void)
{
compare("BBB", "Bb", 1);
compare("BBB", "Bb", 2);
compare("BBB", "Bb", 3);
return 0;
}
Wyjścia:
BBB equals Bb
BBB comes before Bb
BBB comes before Bb
Tokenizacja: strtok (), strtok_r () i strtok_s ()
Funkcja strtok
dzieli łańcuch na mniejsze ciągi lub tokeny za pomocą zestawu ograniczników.
#include <stdio.h>
#include <string.h>
int main(void)
{
int toknum = 0;
char src[] = "Hello,, world!";
const char delimiters[] = ", !";
char *token = strtok(src, delimiters);
while (token != NULL)
{
printf("%d: [%s]\n", ++toknum, token);
token = strtok(NULL, delimiters);
}
/* source is now "Hello\0, world\0\0" */
}
Wynik:
1: [Hello]
2: [world]
Ciąg ograniczników może zawierać jeden lub więcej ograniczników, a do każdego wywołania strtok
można stosować różne ciągi ograniczników.
Wywołania strtok
celu kontynuowania tokenizacji tego samego łańcucha źródłowego nie powinny ponownie przekazywać łańcucha źródłowego, ale zamiast tego jako pierwszy argument przekazać NULL
. Jeśli ten sam ciąg źródłowy zostanie przekazany, pierwszy token zostanie zamiast tego ponownie tokenizowany. Oznacza to, że przy tych samych ogranicznikach strtok
po prostu zwróci pierwszy token.
Zauważ, że ponieważ strtok
nie przydziela nowej pamięci dla tokenów, modyfikuje łańcuch źródłowy . Oznacza to, że w powyższym przykładzie ciąg src
zostanie zmanipulowany w celu wytworzenia tokenów, do których odwołuje się wskaźnik zwracany przez wywołania strtok
. Oznacza to, że łańcuch źródłowy nie może być const
(więc nie może być literałem łańcucha). Oznacza to również, że tożsamość bajtu ograniczającego została utracona (tj. W przykładzie „,” i „!” Są skutecznie usuwane z ciągu źródłowego i nie można powiedzieć, który znak ogranicznika jest zgodny).
Zauważ też, że wiele kolejnych separatorów w ciągu źródłowym jest traktowanych jako jeden; w tym przykładzie drugi przecinek jest ignorowany.
strtok
nie jest bezpieczny dla wątków ani nie może strtok
ponownie uruchomiony, ponieważ podczas analizowania używa bufora statycznego. Oznacza to, że jeśli funkcja wywołuje strtok
, żadna funkcja, którą wywołuje podczas korzystania ze strtok
może również używać strtok
i nie może zostać wywołana przez żadną funkcję, która sama używa strtok
.
Przykład pokazujący problemy spowodowane tym, że strtok
nie jest ponownie strtok
jest następujący:
char src[] = "1.2,3.5,4.2";
char *first = strtok(src, ",");
do
{
char *part;
/* Nested calls to strtok do not work as desired */
printf("[%s]\n", first);
part = strtok(first, ".");
while (part != NULL)
{
printf(" [%s]\n", part);
part = strtok(NULL, ".");
}
} while ((first = strtok(NULL, ",")) != NULL);
Wynik:
[1.2]
[1]
[2]
Oczekiwaną operacją jest to, że zewnętrzna pętla do while
while powinna utworzyć trzy tokeny składające się z każdego ciągu liczb dziesiętnych ( "1.2"
, "3.5"
, "4.2"
), dla których strtok
wywołanie strtok
dla wewnętrznej pętli powinno podzielić ją na osobne ciągi cyfr ( "1"
, "2"
, "3"
, "5"
, "4"
, "2"
).
Ponieważ jednak strtok
nie jest ponownie strtok
, tak się nie dzieje. Zamiast tego pierwszy strtok
poprawnie tworzy token „1.2 \ 0”, a wewnętrzna pętla poprawnie tworzy tokeny "1"
i "2"
. Ale wtedy strtok
w zewnętrznej pętli znajduje się na końcu łańcucha używanego przez wewnętrzną pętlę i natychmiast zwraca NULL. Drugi i trzeci podciąg tablicy src
nie są w ogóle analizowane.
C11 ma opcjonalną część, załącznik K, która oferuje bezpieczną dla wątków i ponownie strtok_s
wersję o nazwie strtok_s
. Możesz przetestować tę funkcję za pomocą __STDC_LIB_EXT1__
. Ta opcjonalna część nie jest szeroko obsługiwana.
Funkcja strtok_s
różni się od funkcji strtok_r
POSIX, chroniąc przed przechowywaniem poza strtok_r
łańcuchem i sprawdzając ograniczenia środowiska wykonawczego. Jednak w poprawnie napisanych programach strtok_s
i strtok_r
zachowują się tak samo.
Użycie strtok_s
z przykładem daje teraz poprawną odpowiedź, taką jak:
/* you have to announce that you want to use Annex K */
#define __STDC_WANT_LIB_EXT1__ 1
#include <string.h>
#ifndef __STDC_LIB_EXT1__
# error "we need strtok_s from Annex K"
#endif
char src[] = "1.2,3.5,4.2";
char *next = NULL;
char *first = strtok_s(src, ",", &next);
do
{
char *part;
char *posn;
printf("[%s]\n", first);
part = strtok_s(first, ".", &posn);
while (part != NULL)
{
printf(" [%s]\n", part);
part = strtok_s(NULL, ".", &posn);
}
}
while ((first = strtok_s(NULL, ",", &next)) != NULL);
Wyjście będzie:
[1.2]
[1]
[2]
[3.5]
[3]
[5]
[4.2]
[4]
[2]
Znajdź pierwsze / ostatnie wystąpienie określonego znaku: strchr (), strrchr ()
Funkcje strchr
i strrchr
znajdują znak w ciągu znaków, czyli w tablicy znaków zakończonej znakiem NUL. strchr
zwraca wskaźnik do pierwszego wystąpienia, a strrchr
do ostatniego.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main(void)
{
char toSearchFor = 'A';
/* Exit if no second argument is found. */
if (argc != 2)
{
printf("Argument missing.\n");
return EXIT_FAILURE;
}
{
char *firstOcc = strchr(argv[1], toSearchFor);
if (firstOcc != NULL)
{
printf("First position of %c in %s is %td.\n",
toSearchFor, argv[1], firstOcc-argv[1]); /* A pointer difference's result
is a signed integer and uses the length modifier 't'. */
}
else
{
printf("%c is not in %s.\n", toSearchFor, argv[1]);
}
}
{
char *lastOcc = strrchr(argv[1], toSearchFor);
if (lastOcc != NULL)
{
printf("Last position of %c in %s is %td.\n",
toSearchFor, argv[1], lastOcc-argv[1]);
}
}
return EXIT_SUCCESS;
}
Wyjścia (po wygenerowaniu pliku wykonywalnego o nazwie pos
):
$ ./pos AAAAAAA
First position of A in AAAAAAA is 0.
Last position of A in AAAAAAA is 6.
$ ./pos BAbbbbbAccccAAAAzzz
First position of A in BAbbbbbAccccAAAAzzz is 1.
Last position of A in BAbbbbbAccccAAAAzzz is 15.
$ ./pos qwerty
A is not in qwerty.
Jednym z powszechnych zastosowań strrchr
jest wyodrębnienie nazwy pliku ze ścieżki. Na przykład, aby wyodrębnić C:\Users\eak\myfile.txt
myfile.txt
z C:\Users\eak\myfile.txt
:
char *getFileName(const char *path)
{
char *pend;
if ((pend = strrchr(path, '\')) != NULL)
return pend + 1;
return NULL;
}
Iteracja po znakach w ciągu
Jeśli znamy długość łańcucha, możemy użyć pętli for, aby iterować jego znaki:
char * string = "hello world"; /* This 11 chars long, excluding the 0-terminator. */
size_t i = 0;
for (; i < 11; i++) {
printf("%c\n", string[i]); /* Print each character of the string. */
}
Alternatywnie możemy użyć standardowej funkcji strlen()
aby uzyskać długość ciągu, jeśli nie wiemy, co to jest:
size_t length = strlen(string);
size_t i = 0;
for (; i < length; i++) {
printf("%c\n", string[i]); /* Print each character of the string. */
}
Na koniec możemy skorzystać z faktu, że łańcuchy w C mają zagwarantowane zerowanie (co już zrobiliśmy, gdy przekazaliśmy je do strlen()
w poprzednim przykładzie ;-)). Możemy iterować tablicę niezależnie od jej wielkości i przestać iterować, gdy osiągniemy znak zerowy:
size_t i = 0;
while (string[i] != '\0') { /* Stop looping when we reach the null-character. */
printf("%c\n", string[i]); /* Print each character of the string. */
i++;
}
Podstawowe wprowadzenie do łańcuchów
W C łańcuch jest ciągiem znaków zakończonym znakiem pustym („\ 0”).
Możemy tworzyć łańcuchy za pomocą literałów łańcuchowych , które są ciągami znaków otoczonymi podwójnymi cudzysłowami; na przykład weźmy dosłowny ciąg "hello world"
. Literały łańcuchowe są automatycznie kończone na zero.
Możemy tworzyć ciągi znaków za pomocą kilku metod. Na przykład możemy zadeklarować char *
i zainicjować go, aby wskazywał pierwszy znak ciągu:
char * string = "hello world";
Podczas inicjowania char *
do stałej łańcucha, jak powyżej, sam łańcuch jest zwykle przydzielany w danych tylko do odczytu; string
jest wskaźnikiem do pierwszego elementu tablicy, którym jest znak 'h'
.
Ponieważ literał ciągów jest alokowany w pamięci tylko do odczytu, nie można go modyfikować 1 . Każda próba jego modyfikacji spowoduje niezdefiniowane zachowanie , więc lepiej dodać const
aby uzyskać błąd czasu kompilacji taki jak ten
char const * string = "hello world";
Ma podobny efekt 2 jak
char const string_arr[] = "hello world";
Aby utworzyć modyfikowalny ciąg, możesz zadeklarować tablicę znaków i zainicjować jej zawartość za pomocą literału łańcucha, tak jak poniżej:
char modifiable_string[] = "hello world";
Jest to równoważne z następującymi:
char modifiable_string[] = {'h', 'e', 'l', 'l', 'o', ' ', 'w', 'o', 'r', 'l', 'd', '\0'};
Ponieważ druga wersja używa inicjalizatora nawiasowego, ciąg nie jest automatycznie kończony zerem, chyba że znak '\0'
jest wyraźnie zawarty w tablicy znaków, zwykle jako ostatni element.
1 Niemodyfikowalna oznacza, że znaki w literale łańcucha nie mogą być modyfikowane, ale pamiętaj, że string
wskaźnika może być modyfikowany (może wskazywać gdzie indziej lub może być zwiększany lub zmniejszany).
2 Oba ciągi mają podobny efekt w tym sensie, że znaki obu ciągów nie mogą być modyfikowane. Należy zauważyć, że string
jest wskaźnikiem do char
i jest modyfikowalną wartością l, więc można ją zwiększać lub wskazywać w inne miejsce, podczas gdy tablica string_arr
jest niemodyfikowalną wartością l, nie można jej modyfikować.
Tworzenie tablic ciągów
Tablica ciągów może oznaczać kilka rzeczy:
- Tablica, której elementami są
char *
s - Tablica, której elementami są tablice
char
Możemy stworzyć tablicę wskaźników postaci, takich jak:
char * string_array[] = {
"foo",
"bar",
"baz"
};
Pamiętaj: kiedy przypisujemy literałom ciąg znaków do char *
, same ciągi są przydzielane w pamięci tylko do odczytu. Jednak tablica string_array
jest przydzielana w pamięci do odczytu / zapisu. Oznacza to, że możemy modyfikować wskaźniki w tablicy, ale nie możemy modyfikować ciągów, na które wskazują.
W C parametrem main argv
(tablica argumentów wiersza poleceń przekazanych podczas uruchamiania programu) jest tablica char *
: char * argv[]
.
Możemy również tworzyć tablice tablic postaci. Ponieważ ciągi są tablicami znaków, tablica ciągów jest po prostu tablicą, której elementami są tablice znaków:
char modifiable_string_array_literals[][4] = {
"foo",
"bar",
"baz"
};
Jest to równoważne z:
char modifiable_string_array[][4] = {
{'f', 'o', 'o', '\0'},
{'b', 'a', 'r', '\0'},
{'b', 'a', 'z', '\0'}
};
Zauważ, że określamy 4
jako rozmiar drugiego wymiaru tablicy; każdy z ciągów w naszej tablicy ma w rzeczywistości 4 bajty, ponieważ musimy dołączyć znak kończący zerem.
strstr
/* finds the next instance of needle in haystack
zbpos: the zero-based position to begin searching from
haystack: the string to search in
needle: the string that must be found
returns the next match of `needle` in `haystack`, or -1 if not found
*/
int findnext(int zbpos, const char *haystack, const char *needle)
{
char *p;
if (((p = strstr(haystack + zbpos, needle)) != NULL)
return p - haystack;
return -1;
}
strstr
przeszukuje argument haystack
(pierwszy) w poszukiwaniu łańcucha wskazanego przez needle
. Jeśli zostanie znaleziony, strstr
zwraca adres wystąpienia. Jeśli nie może znaleźć needle
, zwraca NULL. Używamy zbpos
, aby nie zbpos
ciągle tej samej igły. Aby pominąć pierwszą instancję, dodajemy przesunięcie zbpos
. Klon Notatnika może wywołać findnext
ten sposób, aby zaimplementować dialog „Znajdź następny”:
/*
Called when the user clicks "Find Next"
doc: The text of the document to search
findwhat: The string to find
*/
void onfindnext(const char *doc, const char *findwhat)
{
static int i;
if ((i = findnext(i, doc, findwhat)) != -1)
/* select the text starting from i and ending at i + strlen(findwhat) */
else
/* display a message box saying "end of search" */
}
Literały łańcuchowe
Literały łańcuchowe reprezentują zakończone znakiem null, statyczne tablice char
. Ponieważ mają one statyczny czas przechowywania, literał łańcuchowy lub wskaźnik do tej samej podstawowej tablicy mogą być bezpiecznie używane na kilka sposobów, których nie potrafi wskaźnik do tablicy automatycznej. Na przykład zwracanie literału ciągu z funkcji ma dobrze zdefiniowane zachowanie:
const char *get_hello() {
return "Hello, World!"; /* safe */
}
Ze względów historycznych elementy tablicy odpowiadające literałowi ciągu nie są formalnie const
. Niemniej jednak każda próba ich modyfikacji ma nieokreślone zachowanie . Zazwyczaj program, który próbuje zmodyfikować tablicę odpowiadającą literałowi ciągu, ulega awarii lub w inny sposób działa nieprawidłowo.
char *foo = "hello";
foo[0] = 'y'; /* Undefined behavior - BAD! */
W przypadku gdy wskaźnik pokazywał ciągiem znaków - lub gdzie czasami może zrobić - wskazane jest, aby oświadczyć, że kursora, za referent const
aby unikać angażowania takiej niezdefiniowanej zachowanie przypadkowo.
const char *foo = "hello";
/* GOOD: can't modify the string pointed to by foo */
Z drugiej strony wskaźnik do lub pod tablicę literału łańcucha nie jest sam w sobie wyjątkowy; jego wartość można dowolnie modyfikować, aby wskazywała na coś innego:
char *foo = "hello";
foo = "World!"; /* OK - we're just changing what foo points to */
Ponadto, chociaż inicjalizatory dla tablic char
mogą mieć tę samą formę co literały łańcuchowe, użycie takiego inicjalizatora nie nadaje właściwości literałowi łańcuchowemu inicjowanej tablicy. Inicjator po prostu określa długość i początkową zawartość tablicy. W szczególności elementy można modyfikować, jeśli nie zostaną wyraźnie zadeklarowane const
:
char foo[] = "hello";
foo[0] = 'y'; /* OK! */
Zerowanie łańcucha
Możesz wywołać memset
aby wyzerować ciąg (lub dowolny inny blok pamięci).
Gdzie str
to ciąg zerowy, a n
to liczba bajtów w ciągu.
#include <stdlib.h> /* For EXIT_SUCCESS */
#include <stdio.h>
#include <string.h>
int main(void)
{
char str[42] = "fortytwo";
size_t n = sizeof str; /* Take the size not the length. */
printf("'%s'\n", str);
memset(str, '\0', n);
printf("'%s'\n", str);
return EXIT_SUCCESS;
}
Wydruki:
'fortytwo'
''
Inny przykład:
#include <stdlib.h> /* For EXIT_SUCCESS */
#include <stdio.h>
#include <string.h>
#define FORTY_STR "forty"
#define TWO_STR "two"
int main(void)
{
char str[42] = FORTY_STR TWO_STR;
size_t n = sizeof str; /* Take the size not the length. */
char * point_to_two = strstr(str, TWO_STR);
printf("'%s'\n", str);
memset(point_to_two, '\0', n);
printf("'%s'\n", str);
memset(str, '\0', n);
printf("'%s'\n", str);
return EXIT_SUCCESS;
}
Wydruki:
'fortytwo'
'forty'
''
strspn i strcspn
Biorąc pod uwagę ciąg, strspn
oblicza długość początkowego podłańcucha (span) składającego się wyłącznie z określonej listy znaków. strcspn
jest podobny, z tym wyjątkiem, że oblicza długość początkowego podłańcucha składającego się z dowolnych znaków poza wymienionymi:
/*
Provided a string of "tokens" delimited by "separators", print the tokens along
with the token separators that get skipped.
*/
#include <stdio.h>
#include <string.h>
int main(void)
{
const char sepchars[] = ",.;!?";
char foo[] = ";ball call,.fall gall hall!?.,";
char *s;
int n;
for (s = foo; *s != 0; /*empty*/) {
/* Get the number of token separator characters. */
n = (int)strspn(s, sepchars);
if (n > 0)
printf("skipping separators: << %.*s >> (length=%d)\n", n, s, n);
/* Actually skip the separators now. */
s += n;
/* Get the number of token (non-separator) characters. */
n = (int)strcspn(s, sepchars);
if (n > 0)
printf("token found: << %.*s >> (length=%d)\n", n, s, n);
/* Skip the token now. */
s += n;
}
printf("== token list exhausted ==\n");
return 0;
}
Analogiczne funkcje wykorzystujące łańcuchy szerokich znaków to wcsspn
i wcscspn
; są używane w ten sam sposób.
Kopiowanie ciągów
Przypisania wskaźnika nie kopiują ciągów
Można użyć operatora =
do skopiowania liczb całkowitych, ale nie można użyć operatora =
do skopiowania ciągów w C. Ciągi w C są reprezentowane jako tablice znaków z kończącym znakiem zerowym, więc użycie operatora =
spowoduje zapisanie tylko adresu ( wskaźnik) ciągu.
#include <stdio.h>
int main(void) {
int a = 10, b;
char c[] = "abc", *d;
b = a; /* Integer is copied */
a = 20; /* Modifying a leaves b unchanged - b is a 'deep copy' of a */
printf("%d %d\n", a, b); /* "20 10" will be printed */
d = c;
/* Only copies the address of the string -
there is still only one string stored in memory */
c[1] = 'x';
/* Modifies the original string - d[1] = 'x' will do exactly the same thing */
printf("%s %s\n", c, d); /* "axc axc" will be printed */
return 0;
}
Powyższy przykład został skompilowany, ponieważ użyliśmy char *d
zamiast char d[3]
. Użycie tego ostatniego spowodowałoby błąd kompilatora. Nie można przypisywać tablic w C.
#include <stdio.h>
int main(void) {
char a[] = "abc";
char b[8];
b = a; /* compile error */
printf("%s\n", b);
return 0;
}
Kopiowanie ciągów znaków przy użyciu standardowych funkcji
strcpy()
Aby faktycznie skopiować ciągi, funkcja strcpy()
jest dostępna w string.h
. Przed skopiowaniem należy zarezerwować wystarczającą ilość miejsca docelowego.
#include <stdio.h>
#include <string.h>
int main(void) {
char a[] = "abc";
char b[8];
strcpy(b, a); /* think "b special equals a" */
printf("%s\n", b); /* "abc" will be printed */
return 0;
}
snprintf()
Aby uniknąć przepełnienia bufora, można użyć snprintf()
. Nie jest to najlepsze rozwiązanie pod względem wydajności, ponieważ musi parsować ciąg szablonu, ale jest to jedyna funkcja bezpiecznego dla bufora kopiowania ciągów łatwo dostępna w standardowej bibliotece, z której można korzystać bez dodatkowych kroków.
#include <stdio.h>
#include <string.h>
int main(void) {
char a[] = "012345678901234567890";
char b[8];
#if 0
strcpy(b, a); /* causes buffer overrun (undefined behavior), so do not execute this here! */
#endif
snprintf(b, sizeof(b), "%s", a); /* does not cause buffer overrun */
printf("%s\n", b); /* "0123456" will be printed */
return 0;
}
strncat()
Drugą opcją o lepszej wydajności jest użycie strncat()
(wersja strcat()
sprawdzająca przepełnienie bufora) - pobiera trzeci argument, który określa maksymalną liczbę bajtów do skopiowania:
char dest[32];
dest[0] = '\0';
strncat(dest, source, sizeof(dest) - 1);
/* copies up to the first (sizeof(dest) - 1) elements of source into dest,
then puts a \0 on the end of dest */
Należy zauważyć, że ten preparat używa sizeof(dest) - 1
; jest to kluczowe, ponieważ strncat()
zawsze dodaje bajt zerowy (dobry), ale nie liczy tego w rozmiarze łańcucha (przyczyna pomyłki i nadpisania bufora).
Zauważ też, że alternatywa - łączenie po niepustym łańcuchu - jest jeszcze bardziej obciążona. Rozważać:
char dst[24] = "Clownfish: ";
char src[] = "Marvin and Nemo";
size_t len = strlen(dst);
strncat(dst, src, sizeof(dst) - len - 1);
printf("%zu: [%s]\n", strlen(dst), dst);
Dane wyjściowe to:
23: [Clownfish: Marvin and N]
Należy jednak pamiętać, że rozmiar określony jako długość nie był rozmiarem docelowej tablicy, ale ilością pozostałego w niej miejsca, nie licząc bajtu zerowego terminala. Może to powodować duże problemy z nadpisywaniem. Jest to również trochę marnotrawstwo; aby poprawnie podać argument długości, znasz długość danych w miejscu docelowym, więc możesz zamiast tego podać adres bajtu zerowego na końcu istniejącej zawartości, oszczędzając strncat()
przed ponownym skanowaniem:
strcpy(dst, "Clownfish: ");
assert(len < sizeof(dst) - 1);
strncat(dst + len, src, sizeof(dst) - len - 1);
printf("%zu: [%s]\n", strlen(dst), dst);
Daje to takie same wyniki jak poprzednio, ale strncat()
nie musi skanować istniejącej zawartości dst
przed rozpoczęciem kopiowania.
strncpy()
Ostatnią opcją jest funkcja strncpy()
. Chociaż możesz pomyśleć, że powinno to być na pierwszym miejscu, jest to raczej zwodnicza funkcja, która ma dwie główne przeszkody:
- Jeśli kopiowanie przez
strncpy()
przekroczy limit bufora, kończący znak null nie zostanie zapisany. -
strncpy()
zawsze całkowicie wypełnia miejsce docelowe, w razie potrzeby pustymi bajtami.
(Taka dziwna implementacja jest historyczna i początkowo była przeznaczona do obsługi nazw plików UNIX )
Jedynym prawidłowym sposobem korzystania z niego jest ręczne zapewnienie zerowego zakończenia:
strncpy(b, a, sizeof(b)); /* the third parameter is destination buffer size */
b[sizeof(b)/sizeof(*b) - 1] = '\0'; /* terminate the string */
printf("%s\n", b); /* "0123456" will be printed */
Nawet wtedy, gdy masz duży bufor, użycie strncpy()
powodu dodatkowego wypełnienia zerowego staje się bardzo nieefektywne.
Konwertuj ciągi na liczbę: atoi (), atof () (niebezpieczne, nie używaj ich)
Ostrzeżenie: Funkcje atoi
, atol
, atoll
i atof
są z natury niebezpieczne, ponieważ: Jeśli nie można przedstawić wartości wyniku, zachowanie jest niezdefiniowane. (7.20.1p1)
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char** argv)
{
int val;
if (argc < 2)
{
printf("Usage: %s <integer>\n", argv[0]);
return 0;
}
val = atoi(argv[1]);
printf("String value = %s, Int value = %d\n", argv[1], val);
return 0;
}
Gdy ciąg do konwersji jest prawidłową liczbą dziesiętną z zakresu, funkcja działa:
$ ./atoi 100
String value = 100, Int value = 100
$ ./atoi 200
String value = 200, Int value = 200
W przypadku łańcuchów zaczynających się od liczby, po której następuje coś innego, analizowany jest tylko numer początkowy:
$ ./atoi 0x200
0
$ ./atoi 0123x300
123
We wszystkich innych przypadkach zachowanie jest niezdefiniowane:
$ ./atoi hello
Formatting the hard disk...
Z powodu powyższych dwuznaczności i tego nieokreślonego zachowania rodzina funkcji atoi
nigdy nie powinna być używana.
- Aby przekonwertować na
long int
, użyjstrtol()
zamiastatol()
. - Aby przekonwertować na
double
, użyjstrtod()
zamiastatof()
.
- Aby przekonwertować na
long long int
, użyjstrtoll()
zamiastatoll()
.
sformatowane ciągi danych do odczytu / zapisu
Zapisuj sformatowane dane do ciągu
int sprintf ( char * str, const char * format, ... );
użyj funkcji sprintf
aby zapisać dane zmiennoprzecinkowe w łańcuchu.
#include <stdio.h>
int main ()
{
char buffer [50];
double PI = 3.1415926;
sprintf (buffer, "PI = %.7f", PI);
printf ("%s\n",buffer);
return 0;
}
Odczytaj sformatowane dane z ciągu
int sscanf ( const char * s, const char * format, ...);
użyj funkcji sscanf
do parsowania sformatowanych danych.
#include <stdio.h>
int main ()
{
char sentence []="date : 06-06-2012";
char str [50];
int year;
int month;
int day;
sscanf (sentence,"%s : %2d-%2d-%4d", str, &day, &month, &year);
printf ("%s -> %02d-%02d-%4d\n",str, day, month, year);
return 0;
}
Bezpiecznie konwertuj ciągi na liczbę: funkcje strtoX
Od C99 biblioteka C ma zestaw bezpiecznych funkcji konwersji, które interpretują ciąg znaków jako liczbę. Ich nazwy mają postać strtoX
, gdzie X
jest jednym z l
, ul
, d
itp. W celu określenia docelowego typu konwersji
double strtod(char const* p, char** endptr);
long double strtold(char const* p, char** endptr);
Zapewniają sprawdzenie, czy konwersja miała przepełnienie lub niedopełnienie:
double ret = strtod(argv[1], 0); /* attempt conversion */
/* check the conversion result. */
if ((ret == HUGE_VAL || ret == -HUGE_VAL) && errno == ERANGE)
return; /* numeric overflow in in string */
else if (ret == HUGE_VAL && errno == ERANGE)
return; /* numeric underflow in in string */
/* At this point we know that everything went fine so ret may be used */
Jeśli łańcuch faktycznie nie zawiera żadnej liczby, to użycie strtod
zwraca 0.0
.
Jeśli nie jest to zadowalające, można zastosować dodatkowy parametr endptr
. Jest to wskaźnik do wskaźnika, który będzie wskazywany na końcu wykrytej liczby w ciągu. Jeśli jest ustawiony na 0
, jak wyżej lub NULL
, jest po prostu ignorowany.
Ten parametr endptr
wskazuje, czy konwersja zakończyła się powodzeniem, a jeśli tak, to gdzie zakończyła się liczba:
char *check = 0;
double ret = strtod(argv[1], &check); /* attempt conversion */
/* check the conversion result. */
if (argv[1] == check)
return; /* No number was detected in string */
else if ((ret == HUGE_VAL || ret == -HUGE_VAL) && errno == ERANGE)
return; /* numeric overflow in in string */
else if (ret == HUGE_VAL && errno == ERANGE)
return; /* numeric underflow in in string */
/* At this point we know that everything went fine so ret may be used */
Istnieją analogiczne funkcje do konwersji na szersze typy liczb całkowitych:
long strtol(char const* p, char** endptr, int nbase);
long long strtoll(char const* p, char** endptr, int nbase);
unsigned long strtoul(char const* p, char** endptr, int nbase);
unsigned long long strtoull(char const* p, char** endptr, int nbase);
Funkcje te mają trzeci parametr nbase
który przechowuje bazę liczb, w której zapisana jest liczba.
long a = strtol("101", 0, 2 ); /* a = 5L */
long b = strtol("101", 0, 8 ); /* b = 65L */
long c = strtol("101", 0, 10); /* c = 101L */
long d = strtol("101", 0, 16); /* d = 257L */
long e = strtol("101", 0, 0 ); /* e = 101L */
long f = strtol("0101", 0, 0 ); /* f = 65L */
long g = strtol("0x101", 0, 0 ); /* g = 257L */
Specjalna wartość 0
dla nbase
oznacza, że łańcuch interpretowany jest w taki sam sposób, jak literały liczbowe są interpretowane w programie C: prefiks 0x
odpowiada reprezentacji szesnastkowej, w przeciwnym razie wiodące 0
jest ósemkowe, a wszystkie inne liczby są traktowane jako dziesiętne.
Zatem najbardziej praktycznym sposobem jest interpretacja argumentu wiersza poleceń jako liczby
int main(int argc, char* argv[] {
if (argc < 1)
return EXIT_FAILURE; /* No number given. */
/* use strtoull because size_t may be wide */
size_t mySize = strtoull(argv[1], 0, 0);
/* then check conversion results. */
...
return EXIT_SUCCESS;
}
Oznacza to, że program można wywołać z parametrem ósemkowym, dziesiętnym lub szesnastkowym.