序文
この記事では、開発で日常的に参照できるように、C言語の基本的な知識をまとめています。
Cデータ型
の種類 | 説明 |
---|---|
ベーシックタイプ | 整数型と浮動小数点型の2種類の算術型です。 |
列挙型 | これらは算術型でもあり、プログラムで特定の離散整数値のみを指定できる変数を定義するために使用されます |
派生型 | それらには、ポインター型、配列型、構造体型、共用体型、および関数型が含まれます。 |
ボイドタイプ | タイプ指定子voidは、使用可能な値がないことを示します。 |
printf
コード形式 | 説明 |
---|---|
%c | キャラクター |
%d | 符号付き整数 |
%私 | 符号付き整数 |
%e | 科学的記数法、小文字のeを使用 |
%E | 科学的記数法、大文字のEを使用 |
%f | 浮動小数点 |
%g | %eまたは%fの短い方を使用してください |
%G | %Eまたは%fの短い方を使用してください |
%O | オクタル |
%s | 文字列 |
%u | 符号なし整数 |
%バツ | 符号なし16進数、小文字を使用 |
%p | ポインタ |
%n | パラメータは、文字数が配置される位置を指す整数へのポインタである必要があります |
%% | サイン |
文字ポインタと文字配列
char *p = "hello"; // 字符串常量池地址赋值给p,即p指向了字符串常量池的hello
char *p1 = "hello";
char c[10] = "hello"; // 是把字符串常量池里的hello strcpy给了c
printf("%p, %p, %p\n", p, p1, c); // p和p1指向同一个地方
printf("%d, %d, %d, %d\n", (p == p1), (*p == *p1), (c == p), (*c == *p));
printf("%c, %c\n", *c, *p);
c[0] = 'H'; // 可以赋值,在栈空间上
// p[0] = 'H'; // 会崩溃,在常量池里,不可写
puts(c);
char *p2 = malloc(10 * sizeof(char));
strcpy(p2, "hello");
p2[0] = 'H';
puts(p2); // 可以修改,在堆上分配
printf("===");
char *p3 = malloc(10 * sizeof(char));
p3 = "hello"; // 把常量池hello地址赋给p3,造成内存泄露,刚才在堆上分配的10个空间大小没法释放
// p3[0] = 'H'; // 不能修改,因为是常量池的
puts(p3); // 可以修改,在堆上分配
p = "world";
strcpy(c, "world"); // 重新给c赋值
puts(p);
puts(c);
配列ポインタとポインタ配列
- 配列ポインタ(intポインタ):
配列ポインタは、配列の最初のアドレスを指す「配列ポインタ」と言えます。
int (*p)[2]
:配列ポインタ、pzは2つのint型値を含む配列を指します
// 传递数组指针,列数为2;
void printArr(int (*p)[2], int row) {
// *p指向行,*(*p)指向具体元素
for (int i = 0; i < row; ++i) {
for (int j = 0; j < sizeof(*p) / sizeof(int); ++j) {
printf("%d,", *(*(p + i) + j)); // *p指向行,*(*p)指向具体元素
printf("%d,", p[i][j]); // 同样可以这么访问
}
printf("\n");
}
}
void test() {
int zippo[4][2] = {
{
2, 4},
{
6, 8},
{
1, 3},
{
5, 7}};
int (*pz)[2]; // 数组指针,pz指向一个内含2个int类型值的数组
pz = zippo; // 该二维数组首地址就是一个数组指针
printArr(zippo, 4);
printArr(pz, 4);
}
- ポインタ配列(int配列)
ポインタ配列は「ポインタの配列」と言えます。つまり、この配列の各要素はポインタ変数です。32ビットシステムでは、ポインタは4バイトを占めます。
int *p[2]
:ポインタ配列、長さ2のintポインタ変数配列。
char *a = "1";
char *b = "2";
char *arrA[] = {
a, b}; // 字符串数组,用指针数组来表达
char *arrB[] = {
"1", "2"}; // 字符串数组,用指针数组来表达
printf("%s,%s\n", arrA[0], arrA[1]);
printf("%s,%s\n", arrB[0], arrB[1]);
関数ポインタとポインタ関数
- 関数ポインター(intポインター)、ポインターは関数を指します
int (*pfun)(int, int); // pfun是一个指向返回值为int的函数的指针
- ポインタ関数(int関数)、つまり、ポインタを返す関数:
type name * function name(function parameter list column);
int *pfun(int, int); // pfun是一个返回值为整型指针的函数
ポインタ定数と定数ポインタ
int a = 1;
int b = 2;
// 常量指针,指针指向的值不可用改,但指针的指向可以改
const int *p = &a;
// *p = b; 错误,指向的值不可用改
p = &a; // 指针的指向可以改
// 指针常量,指针的指向可以改,指针指向的值可以改
int *const p2 = &a;
*p2 = b; // 指针指向的值可以改
// p2 = &b; 错误,指针的指向不可用改
// const修饰指针和常量,都不可用改
const int *const p3 = a;
// *p3 = b;
// p3 = &b;
ポインタとリファレンス
&
変数のアドレス値を取得できるアドレス演算子。
値演算子は*
逆参照とも呼ばれ、アドレスの場所に対応するデータを取得できます。「解決策」はアドレスに対応することです。パッケージを開くのと同じように、変数の値であるが解凍および解凍されるため、「逆参照」と呼ばれます。言い換えると、間接参照は、メモリアドレス内の対応するオブジェクトを返すことです。
ポインターと参照は概念であり、通常、特定の変数への参照はその変数へのポインターであると言います。
しかし、ポインターとポインター変数は2つの概念です。つまり、変数が別の変数のアドレスを格納する場合、その変数はポインター変数と呼ばれます。
ポインタの長さは、32ビットでは4バイト、64ビットでは8バイトです。
ポインタの本質は間接アクセスです。
- ポインターの使用シナリオ:転送とオフセット
関数呼び出しの原則:値の転送;実際のパラメーターの値は仮パラメーターに割り当てられます(コンパイルの原則)
ポインタオフセット
// 需要区分清楚指针++的操作
int arr[3] = {
1, 5, 9};
int *p;
int j;
p = arr;
j = *p++;
// j = (*p)++;
// j = *++p;
// j = ++*p;
// j = *p++;
printf("arr[0]=%d, j=%d, *p=%d\n", arr[0], j, *p);
j = p[0]++;
printf("arr[0]=%d, j=%d, *p=%d, p[0]=%d", arr[0], j, *p, p[0]);
動的ストレージと静的ストレージ
変数のスコープの観点からは、グローバル変数とローカル変数に分けることができます。変数値の存在の観点からは、静的ストレージメソッドと動的ストレージメソッドに分けることができます。
静的ストレージメソッド:は、プログラム動作中のシステムによる固定割り当てストレージスペース方式;
動的ストレージ方式:プログラムの実行中に必要に応じてストレージスペースを動的に割り当てる方式です。
-
次のストレージカテゴリがあります。変数のストレージカテゴリに
auto
応じて、変数のスコープと有効期間を知ることができます。静的として宣言されていないローカル変数はすべて動的ストレージメソッドであり、デフォルトの修復シンボルは自動です。これらのストレージスペースは、関数の終了時に自動的に解放されます。
static
:静的ストレージ領域にスペースを割り当て、コンパイル中に値を割り当て、1回だけ初期化します。
extern
:別のファイルで定義されている変数を借用して、これが宣言であり、定義ではないことを示します。コンパイラにその定義を他の場所でクエリ
register
させます。:変数を登録します。ほとんど使用されません。 -
ヒント:
関数はデフォルトでexternです。
静的変更関数はこのファイルで有効であることを意味します。
静的変更変数は静的変数であり、このファイルで有効です。
静的ローカル変数:静的ですが、他の関数はそれを参照できません。
オペレーター
構造
構造体は整列されます。
構造体は関数内の値によって渡され、すべての内容がスタックスペースに直接コピーされるため、呼び出し元の値は変更されません。参照を渡すには、ポインターを使用する必要があります。参照パスはこの問題を回避できます。
typedef struct student {
int num;
struct student *pNext; // 此时不能省略student结构体标签
} stu;
typedef和#define
-
typedef
プログラマーが既存の型のエイリアスを作成できるようにします。
これにより、コメントのコードを作成できます。通常、定義された名前はtypedef定義で大文字で表され、型名が実際には記号の省略形であることをユーザーに思い出させます。
typedefを使用して構造体タイプに名前を付ける場合、構造体のラベルを省略でき
ます。typedefを使用する場合、typedefは新しい型を作成せず、既存の型に便利なラベルを追加するだけであることに注意してください。 -
#define
マクロ定義とも呼ばれる識別子は、マクロと呼ばれる定義済みマクロの名前です。
その特徴は次のとおりです。定義された識別子はメモリを占有しませんが、プリコンパイル後に存在しない一時的なシンボルです。前処理は前処理とも呼ばれます。事前コンパイルはコンパイルではなく、コンパイル前の処理です。この操作は、正式なコンパイルの前にシステムによって自動的に完了します。- 実際、事前コンパイルによって実行される操作は、単純な「テキスト」置換です。
マクロ定義の場合、プリコンパイルすると、プログラム内で「識別子」が表示されるすべての場所が、「マクロ置換」または「マクロ拡張」と呼ばれるこの「定数」に置き換えられます。
置換が完了すると、正式なコンパイルが実行されます。したがって、「コンパイル」をクリックすると、実際には2つの操作が実行されます。つまり、最初にプリコンパイルしてから、正式にコンパイルします。#include <stdio.h>は同じです。つまり、前処理では、最初にプログラムの#include <stdio.h>行をヘッダーファイルstdio.hのすべての「テキスト」コンテンツに置き換えてから、正式なコンパイルに進みます。
- 実際、事前コンパイルによって実行される操作は、単純な「テキスト」置換です。
-
typedef
また#define
、
#defineとの違いは、作成されるtypedefシンボル名は、使用できない値のタイプによってのみ制限されます。
Typedefは、プリプロセッサではなくコンパイラによって解釈されます。
限られた範囲内で、typedefは#defineよりも柔軟性があります。
#define PI 3.1415926 // 可以用于值
typedef int SOCKET; // 给int命个别名SOCKET,之后代码里可以使用SOCKET来表示socket的fd,做到了代码即注释,方便阅读
#define SOCKET int //等同于上句
typedef char* STRING;
STRING a,b; // 等同于 char *a, char *b
#define STRING char*
STRING a,b // 等同于char *a, b; 只有a是指针,文本替换而已;
ヘッドファイル
#
これが前処理コマンドであることを示します。「#」で始まるものはすべて前処理コマンドです。
ヘッダーファイルは、通常はソースコードの形式のファイルであり、別のソースファイルを処理するときにコンパイラによって自動的にインクルードされます。
ヘッダーファイルは通常、同じ名前の.cファイルで定義された変数、マクロ、システムグローバル変数、関数プロトタイプの宣言、および.cの外部で使用する必要のある宣言を配置します。
- ヘッダーファイル関数:
- 便利な開発:一部のファイルに必要な共通の定数、構造、型定義、関数、および変数宣言が含まれています。
- 関数のスコープを、関数定義の位置ではなく、関数宣言の位置から開始します(練習の概要)
- インターフェイスの提供:ソフトウェアパッケージは、外部へのインターフェイスを提供できます(例:stdio.h)。
#includeの2つの構文
-
#include <file>
このフォームは、システムヘッダーファイルを参照するために使用されます。システムディレクトリの標準リストでfileという名前のファイルを検索します。ソースコードをコンパイルするときは、-Iオプションを使用して、ディレクトリをリストの前に置くことができます。 -
#include "file"
このフォームは、ユーザーヘッダーファイルを参照するために使用されます。現在のファイルを含むディレクトリでfileという名前のファイルを検索します。ソースコードをコンパイルするときは、-Iオプションを使用して、ディレクトリをリストの前に置くことができます。
ヘッダーファイルを1回だけ引用します
-
ヘッダーファイルが繰り返し引用されるのを防ぐために、#ifndef /#define /#endifがヘッダーファイルに追加されます。
-
重複する参照
は、ヘッダーファイルが同じcppファイルに複数回インクルードされることを意味します。このエラーは、ネストされたインクルードが原因であることがよくあります。
例:ahファイル#include "ch"があり、b.cppファイルはこの時点で#include "ah"と#include "ch"をインポートします。これにより、chが繰り返し引用符で囲まれます。 -
ヘッダーファイルへの繰り返しの参照によって引き起こされる結果:
- 一部のヘッダーファイルで重複する参照は、コンパイル作業の作業負荷を増やすだけで、それほど問題にはなりません。コンパイル効率が低いだけですが、大規模なプロジェクトの場合、コンパイル効率が低いのは苦痛です。
- 一部のヘッダーファイルは繰り返しインクルードされるため、ヘッダーファイルでグローバル変数を定義するなどのエラーが発生する可能性があります(この方法は推奨されませんが、C仕様では実際に許可されています)。これにより、定義が繰り返される可能性があります。
-
#ifndef HEADER_FILE
HEADER_FILEが存在しない場合、「HEADER_FILEを定義しない場合」を意味します
#ifndef HEADER_FILE
#define HEADER_FILE
the entire header file file
#endif
この構造は一般にラッパーと呼ばれます#ifndef
。ヘッダーファイルが再度参照されると、HEADER_FILEがすでに定義されているため、条件はfalseになります。この時点で、プリプロセッサはファイルの内容全体をスキップし、コンパイラはそれを無視します。
#define 定义一个预处理宏
#undef 取消宏的定义
#if 编译预处理中的条件命令, 相当于C语法中的if语句
#ifdef 判断某个宏是否被定义(#define过), 若已定义, 执行随后的语句
#ifndef 与#ifdef相反, 判断某个宏是否未被定义
#elif 若#if, #ifdef, #ifndef或前面的#elif条件不满足, 则执行#elif之后的语句, 相当于C语法中的else-if
#else 与#if, #ifdef, #ifndef对应, 若这些条件不满足, 则执行#else之后的语句, 相当于C语法中的else
#endif #if, #ifdef, #ifndef这些条件命令的结束标志.
defined 与#if, #elif配合使用, 判断某个宏是否被定义
ifdef的使用和#if defined()的用法是一样的。
ifndef又和#if !defined()的用法一样。
#pragma 说明编译器信息
#warning 显示编译警告信息
#error 显示编译错误信息
整数定数
/*
* 符号常量,简单替换,使用时要注意。
*/
#define PI 3 + 2
// #define PI (3 + 2),这样就对了
void test(void) {
int i = PI;
int j = PI;
printf("i = %d, j = %d\n", i, j);
int k = PI * PI; // 3 + 2 * 3 + 2 = 11
printf("k = %d", k);
}
補体
補数コードは負の数を表すことです
補数コード=元のコードを反転+1
0000 0000 0000 0000 0000 0000 0000 0101 5
1111 1111 1111 1111 1111 1111 1111 1011 -5 // 取反+1
整数変数
符号付き数値の最上位ビットは符号を表します。
void test() {
int i = 0x80fb0000; // 首位为符号位
unsigned int ui = 0x80fb0000;
short j = 32767; // 0111 1111 1111 1111
long k = 0;
printf("%d, %d, %d\n", sizeof(j), sizeof(i), sizeof(k));
printf("%d, %u\n", i, ui); // 使用%u输出unsiged
printf("%d\n", j);
++j;
printf("%d", j); // 溢出,使用时防止溢出
}
浮動小数点型
- 浮動小数点型の精度が失われる原理(バイナリアナログは浮動小数点数を表します):浮動小数点型(float、double)はどのよう
にメモリに格納されますか?
文字タイプ
char c = 'c'; // 1字节
printf("%c\n", c);
printf("abc\rd\n"); // \r回到行首,只输出d
printf("abc\b\b"); // 退格
char a = '\0'; // 空字符,用于标示字符串结尾,不是空格,打印不出
printf("%c", a);