大まかな説明を言うと、C言語でいう（文字列ではなく）「文字」とは基本、たった1文字からなる半角英数字および半角記号のことです。半角スペースも文字になります。実行環境にもよる可能性がありますが、日本語はプログラム上は文字ではありません。
「文字列」とは、文字を並べたものです。「Hello」 とか「Hello World」は文字列です。
C言語には、直接的に文字列を扱う専用機能は組み込まれていないので、文字列は文字の配列として定義されています。Cでは、文字配列を文字のリストではなく、文字列で表現することができ、末尾には自動的にヌル終端文字（'\0'）が追加されます。例えば、"String" という文字列を格納するには、次のように書きます。
char string[] = "String";
あるいは
char string[] = { 'S', 't', 'r', 'i', 'n', 'g', '\0' };
最初の例では、文字列の末尾にコンパイラによって自動的にヌル文字が付加されます。規約としてライブラリ関数は文字列の末尾にヌル文字('\0')が付加されることを想定しています。 
後者の宣言では、個々の要素を示しているため、ヌル文字の終端を手動で追加する必要があります。
文字列は、必ずしも明示的な変数に代入する必要はありません。文字列は無名の文字列（文字列リテラルといいます）として直接作成し使用することができます（printf関数の第一引数など）。 
特別に長い文字列を作成するには、文字列を複数のセクションに分割し、最初のセクションを引用符で閉じて、次の行で文字列を再開する必要があります（これも引用符で始まり、引用符で終わります）。
char string[] = "This is a very, very long "
                "string that requires two lines.";
文字列は、行末にバックスラッシュ文字を置くことで複数行にまたがることもできますが、この方法は非推奨です。
文字列処理ルーチンの便利なライブラリがありますので、別のヘッダーをインクルードすることで使用できます。
この標準的な文字列ライブラリ  は、文字列に対して様々な処理を行うことができます。
文字定数（リテラル）は、'p'のように引用符（'）で囲まれ、指定されたcharの値の整数値にコンパイルされます。
文字定数の型はconst intです。
文字列定数（リテラル）は、"Hello world!"のように二重引用符（"）で囲まれ、指定されたcharの値の配列にコンパイルされ、さらに文字列の終わりを示すヌル終端文字（'\0'）コードが付加されます。
文字列定数の型はchar const []です。
文字列リテラルは、ソースコードに直接、改行などの制御文字や、文字列の中で特別な意味を持つ文字を埋め込んではいけません。
このような文字を文字列に含めるためには、次のようにバックスラッシュ・エスケープJIS語では逆斜線表記を使用することができます。
なお、バックスラッシュを入力した場合、多くの日本語環境では 円マーク \ が表示されます。
C言語では、char型の配列が、文字列を表現する際に使われます。
文字列を「""（ダブルクォーテーション）」で囲むと、その文字列を表現する配列となります。
文字列は「'\0'」( ヌル文字、 0x00 )で終わります。
配列を用いて文字列を扱う:
int main(void) {
  const char str[] =
      "Hello, World!"; // char型のconstな配列strに文字列"Hello, World!"を格納します。
  printf("%s\n", str); // strを表示します。
}
実行結果:
Hello, World!
上の例では、配列strの値は次の表のようになります。
また、同様の処理をポインタを用いて記述することができます。
ポインタを用いて文字列を扱う:
int main(void) {
	char *p = 
      "Hello, World!"; //char型へのポインタpに文字列"Hello, World!"のアドレスを格納します。
	printf("%s\n", p); //ポインタpが指す文字列を表示します。
}
実行結果:
Hello, World!
複数の文字列を共通の名前でまとめて扱いたい場合、多次元の配列を用います。
一番右の次元の要素数で、扱う文字列1個あたりの長さ（ヌル文字を含む）を指定し、残りの次元の要素数で、扱う文字列の数を指定します。
たとえば英数字10文字（終端の '\0' も含め）までの長さで、4単語までを格納できる用意したい場合、
char words[4][10];
のようになります。
つまり構文は、
char 配列変数名[文字列の個数][文字列1個あたりの長さ];
です。
文字列の配列のある文字列にアクセスしたい場合、配列の添字に一番右の次元を除く次元を指定します。
int main(void) {
  char num[3][6] = {"zero", "one", "two"};
  printf("%s \n", num[1]); //文字列の配列の配列の[i]番目の文字列を表示します。
}
実行結果:
one
上記例のように、char 宣言時の配列は2次元配列の書式であっても(たとえばnum[3][6] のような書式)、
printfなど利用時には次元が一段階だけ下がるので書式は1次元配列でアクセスすることになるので(たとえばnum[1] のような書式)、間違えないように注意せよ（初心者はよく間違えてエラーになり悩む）。
[https://paiza.io/projects/8ulRTEyutT4inCp5cJGUYw 文字列の配列の配列の使用例]:
int main(void) {
  char num[][6] = {//文字列の配列に0から9までの数字の英語の名前を格納します。
                   "zero", "one", "two",   "three", "four",
                   "five", "six", "seven", "eight", "nine"};
  for (int i = 0; i < sizeof num / sizeof *num; i++)
    printf("%s ", num[i]); //文字列の配列の配列の[i]番目の文字列を表示します。
  printf("\n");
}
実行結果:
zero one two three four five six seven eight nine 
また、同様の処理をポインタの配列を用いて記述することができます。
int main(void) {
  char *p[3] = { "zero", "one", "two"  } ;
  printf("%s\n", p[1]); //文字列へのポインタから[i]番目の文字列を表示します。
}
実行結果:
one
上記コードのようにポインタ形式で *p[3] で定義した場合の数値「3」の意味は、文字配列の要素数です（文字列長ではありません）。
なので、たとえば"one"を"onevvvvvvvvvvvv"のように長くしても、コンパイラの許可する範囲内の長さならエラーになりません。
また、当然だが要素数を足していって "three", "four" などと足していくと、宣言時の3個ぶんの確保をオーバーするのでエラーになります。
ともかく上記コードのようにポインタ形式で宣言することにより、宣言時の書式の次元とprintfなど利用時の書式が次元が一致するので、用途によっては、ポインタ方式で宣言するのもよいでしょう。
[https://paiza.io/projects/_xV2C1X3MugqDZBCu2-WHw?language=c 文字列へのポインタの配列の使用例]:
int main(void) {
  //ポインタの配列に0から9までの数字の英語の名前を格納する。
  const char * p[10] = {
    "zero",
    "one",
    "two",
    "three",
    "four",
    "five",
    "six",
    "seven",
    "eight",
    "nine"
  };
  //文字列へのポインタから[i]番目の文字列を表示する。
  for (int i = 0; i < sizeof p / sizeof * p; i++)
    printf("%s ", p[i]);
  printf("\n");
}
実行結果:
zero one two three four five six seven eight nine 
C言語はワイド文字列をサポートしています。ワイド文字列はwchar_t型の配列として定義され、（少なくとも）16ビットの値を持ちます。文字列の前にLを付けて次のように書きます。
wchar_t *p = L "Hello world!";
この機能により、256種類以上の文字が必要な文字列が可能になります（可変長のchar文字列も使用可能です）。これらの文字列はゼロ値のwchar_tで終わります。これらの文字列はの関数ではサポートされていません。その代わり、で宣言された独自の関数を持っています。
ワイド文字は、型のサイズや文字集合とエンコーディングが規定されておらずプラットフォームおよび処理系依存でした。
この問題を統一的な文字集合とエンコーディングの体型を考案し解決すべく、Unicodeが国際規格として登場し、CもC11から言語仕様レベルで対応が始まりました。
尚、JIS規格の、『プログラム言語Ｃ』はC99の和訳なのでUnicodeへの言及はありません。
ISO/IEC 9899:2011（通称C11) では、新たに2つの文字型 char16_t と char32_t が導入されました。
これらはそれぞれ UTF-16 と UTF-32 を内部表現とします。
u'c' や U'c' あるいは u"str" や U"str" のように小文字の u あるいは大文字の U を前置することで、それぞれ char16_t と char32_t の文字定数・文字列リテラルを表現します。
ただし、C23で明文化されましたが、code>char16_t も char32_tコードポイントの値が 1 つのコードユニットとして符号化できる場合に限ります。そうでない場合は２つのコードユニットのペア「サロゲートペア」としてエンコードされます[https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3054.pdf N3054 working draft — September 3, 2022 ISO/IEC 9899:2023 (E) p65, § 6.4.4.4 ''Character constants'' 13,14]。
また、u8 を前置することで UTF-8 の文字列リテラルを表現します[https://ja.cppreference.com/w/cpp/language/string_literal 文字列リテラル - cppreference.com]が、C11には、char8_t 型は存在せず、従来の char 型で代用することにしました。
これは単純で大きな間違いで、Cではcharの符号の有無を実装依存としており、UTF-8のcode unitの値域は0x80から0xffの範囲にも及んでおり、符号の有無が曖昧なcharでは表現できることが担保できないのです。
このため、C23ではchar8_t 型が定義されました（後述）。
char16_tおよびchar32_tはそれぞれuint_least16_tおよびuint_least32_tのtypedefエイリアスです。
char16_t型のサイズは16ビットよりも大きい可能性があるが、格納される値は16ビット幅です[https://ja.cppreference.com/w/c/string/multibyte/char16_t char16_t - cppreference.com]。同様に、char32_t型のサイズは32ビットよりも大きい可能性があるが、格納される値は32ビット幅である[https://ja.cppreference.com/w/c/string/multibyte/char32_t char32_t - cppreference.com]。
[https://paiza.io/projects/r7WpP0MBwxWq3vcYWaMQow?language=c char16_t の使用例] : 
int main(void) {
  char16_t c16s[] = u"👿"; // または u"\U0001F47F"
  printf("%zu UTF-16 code units: [ ", sizeof c16s / sizeof *c16s);
  for (size_t n = 0; n < sizeof c16s / sizeof *c16s; n++)
    printf("%#x ", c16s[n]);
  printf("]\n");
}
実行結果 : 
3 UTF-16 code units: [ 0xd83d 0xdc7f 0 ]
この様にUTF-16の配列からは、サロゲートペアはコードポイントを得ることができません。
2022年10月現在策定中のC23では、UTF-8 を格納する方として正式にchar8_t 型が定義されました[https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3054.pdf N3054 working draft — September 3, 2022 ISO/IEC 9899:2023 (E) p65, § 6.4.4.4 ''Character constants'' 12]。
UTF-8 文字定数は char8_t 型です。UTF-8 文字定数は、16 進数または 8 進数のエスケープシーケンスで生成されない場合、その値は ISO/IEC 10646 のコードポイント値に等しく、ただしコードポイント値が単一の UTF-8 コードユニットとして符号化されることが条件です。それ以外の場合、UTF-8 文字定数の値は、16 進数または 8 進数のエスケープシーケンスで指定された数値になります。
charとwchar_tがどのような文字エンコーディングを表すかは、C言語の標準では規定されていませんが、0x00と0x0000という値は文字列の終わりを示しており文字ではありません。
文字エンコーディングの影響を直接受けるのは、入力コードと出力コードです。その他のコードはあまり影響を受けないはずです（この仮定でプロクラムを作るとエンコーディングの種類によっては（例えば ShiftJISでは）1つの文字の2バイト目に '\ の様なエスケープ文字が現れた時、正しくエンコーディング出来ず、場合によってはバッファオーバーフロー等を引き起こします。）。また、ソースコードに文字列を書き込めるようにするためには、エディターもエンコーディングに対応していなければなりません。
符号化方式には大きく分けて3種類あります。
1文字に1バイト。通常はASCIIをベースにしています。文字数は255文字までで、これに0の終端文字を加えたものになります。
可変長のchar文字列で、255種類以上の文字が使用できます。このような文字列は、通常のcharベースの配列として書かれます。これらのエンコーディングは通常ASCIIベースで、UTF-8やShiftJISなどがその例です。
ワイド文字列。wchar_tの値の配列です。UTF-16は最も一般的なエンコーディングで、可変長であるため、1つの文字が2つのwchar_tになることもあります。
ワイド文字列にUTF-16を選んでしまった場合（WindowsやJavaが該当します）、サロゲートペアの存在が問題になります。サロゲートペアは一部の文字を16bit整数2個で表す機能で、1文字がwchar_tの配列の要素に対応する前提が崩れていまいます。典型的なサロゲートペアでエンコードされた文字にemoji（👿など）があります。
ユーザ定義関数の引数に文字列を使いたい場合、例えば下記のようになります。
[https://paiza.io/projects/bp_AzC25Mx-i3bvJg91aRA?language=c 例]:
// 文字列を引数とする関数
void test(const char s[]) {
  puts(s);
}
int main(void) {
  char s[20] = "Never used";
  strcpy(s, "test"); // 文字列をコピーする標準ライブラリー関数
  printf("char 関数の実験\n");
  test(s);
  test(s);
}
実行結果 : 
文字列をあつかうユーザ定義関数の実験
test
test
文字列は配列であるので、関数の定義側の引数も、配列またはポインタにする必要があります。
とすべきです。