07- C言語ポインタ(C言語)

ポインタの導入

1. 一般に、メモリ内のバイトはメモリユニットと呼ばれます。
2. これらのメモリ ユニットに正しくアクセスするには、各メモリ ユニットに番号を付ける必要があります。メモリユニットのシリアル番号により、メモリユニットを正確に見つけることができます。メモリユニットの番号はアドレスとも呼ばれ、このアドレスは通常ポインタと呼ばれます。
3. プログラム内で変数が定義されている場合、プログラムがコンパイルまたは実行されると、システムはその変数にメモリ ユニットを割り当て、そのメモリ アドレス (番号) を決定します。
4.変数のアドレスが変数のポインタであり、変数のアドレスが格納されている変数がポインタ変数です
5.メモリユニットのポインタメモリユニットの内容は2 つの異なる概念です。よくある例を使用して、それらの関係を説明できます。私たちが銀行に行って入出金をすると、銀行員が口座番号から入金伝票を探してきて、それを見つけて入出金金額を入金伝票に記入します。ここで、口座番号は預金証書のポインタであり、預金金額は預金証書の内容である。メモリユニットの場合、ユニットのアドレスがポインタであり、そこに格納されているデータがユニットの内容です。

2 つのポインター変数の定義と使用

2.1 ポインタ変数定義の構文

データ型 *ポインタ変数名注: 1. データ型は、
C 言語でサポートされているすべてのデータ型です。
2. ポインタ変数名はC言語変数の命名規則に従います。

例:

int *p; //定义了一个指针变量p,简称指针p,p是变量, int *是类型
char* p2;

ポインタもデータ型であると考えます

2.2 ポインタ変数の代入

1)ポインタ変数の値は、最初のアドレスがこの値であるメモリ空間をポインタが指していることを意味します

2)ポインタ変数の割り当ては、他の変数のアドレスです: &: アドレス演算子を取得します

//指针变量 = &变量名;
&:取地址运算符
int a = 10;
int *p = &a;

3)ポインタ変数操作によって指定されたメモリ空間は、指定されたメモリ空間内のポインタ変数を介してアクセスおよび変更
できます

*:指针运算符(或称“间接访问” 运算)。
int a = 10;
int *p;
p = &a;
printf("*p: %d\n", *p);
*p = 100;
printf("*p: %d\n", *p);

4) ポインタ変数は通常の変数と同じですが、定義後に初期化しないとポインタ変数の値は不定になります

5)ワイルドポインタ ポインタ
変数の値は不定であるため、このポインタを「ワイルドポインタ」と呼びます。
ワイルド ポインタの害: ポインタが指す空間が不確実であるため、ポインタが不正なメモリ空間に対して動作し、プログラムがクラッシュする可能性があります。 

int a = 100;
int *p;
*p = 1000;
/*因为p没有初始化/赋值,所以p的值是不确定的,如果此时p的值恰好等于a的地址(p == &a), 那么*p=1000将a的值修改为如果p的值恰好是内存上一块只读的内存空间,*p = 1000将导致程序异常退出,你可能会看到程序运行报错(段错误/核心内容*/

6) Null ポインタ ポインタ
変数がどの変数 (自由で利用可能な) も指していないことをマークするために、C 言語では、このポインタに NULL を代入して、このポインタを null ポインタとしてマークできます。

int *p = NULL;

NULL は値 0 のマクロ定数です。

#define NULL ((void *)0)

注: NULL ポインターの役割は、ポインター変数がワイルド ポインターになるのを防ぐことです* を使用して null ポインタが指すメモリ空間にアクセスすると、プログラムはエラーを報告します。

7) 筆記試験の問題: 組み込みシステムは、多くの場合、プログラマーが特定のメモリ位置にアクセスすることを要求するという特徴があります。プロジェクトでは、絶対アドレスが0x67a9~0xaa66の整数変数の値を設定する必要があります。

//方法1:
int *ptr;
ptr = (int *)0x67a9; //在内存地址编号的前面加上(int *)将地址编号这个无符号整型数据强制转换为
//(int*)指针类型,这样赋值符号左值和右值的数据类型一致
*ptr = 0xaa66;
//方法2:
*(int *)(0x67a9) = 0xaa55;

注: 実際の作業では、特定のメモリ アドレスをポインタ変数に割り当てることは通常ほとんどありません。これは、プログラマは一般にどのメモリ アドレスが利用可能であるかを知らないためです。

2.3 各種ポインタ変数の違い

1. int *p1 と char *p2 の類似点は何ですか?

int x = 100;
int *p1 = &x;
char y = 'A';
char *p2 = &y;

同じ点:

  • ポインタ変数です
  • メモリアドレス番号を保存するために使用されます
  • 占有されるメモリ空間は同じです
int *p1;
char *p2;
printf("%d %d\n", sizeof(p1), sizeof(p2));

p1 と p2 が占めるメモリ空間は 4/8 で、32 ビット マシンでは結果は 4、64 ビット マシンでは結果は 8 であることがわかりました。
考える:

ポインタ変数によってメモリ空間が 4 バイトまたは 8 バイト占有されるのはなぜですか?
ポインタ変数はメモリアドレスの番号を保持しているからです。
32 ビット マシンのメモリ アドレス番号の最大値は 2^32-1 で、4 バイトの変数に格納できます。
64 ビット マシンの最大メモリ アドレス番号は 2^64-1 で、8 バイトの変数に格納できます。

2. int *p1 と char *p2 の違いは何ですか?
まず第一に、メモリに保存されるものはバイナリのみであることを知っておく必要があります。
int float char などのデータ型がある理由は、プログラマがメモリに格納されているバイナリを特定のデータ型として扱うことを望んでいるからです。

int x = 65;
printf("%c\n", x);
  • int *p1 の機能は、ポインタ変数 p1 がそれ​​が指すメモリ空間内のバイナリをint 型として扱うことです。
  • char *p2 の機能は、ポインタ変数 p2 が、それが指すメモリ空間内のバイナリをchar 型として扱うことです。

3. p1++ と p2++ の違い

#include <iostream>

int main() {
    int x = 10;
    int *p1 = &x;
    char y = 'A';
    char *p2 = &y;
    printf("p1: %p, p2: %p\n", p1, p2);
    p1++;
    p2++;
    printf("p1: %p, p2: %p\n", p1, p2);
    return 0;
}

自己インクリメント後の p1 の値と自己インクリメント前の p1 の値の差は4 です
自己インクリメント後の p2 の値と自己インクリメント前の p2 の値の差は1 です
ポインター変数 + n は、ポインターが n バイトだけオフセットされていないが、ポインター変数が n データ型によってオフセットされていることを意味します。たとえば、p1+3。これは、ポインター p1 が 3 つの int 型データによってオフセットされていることを意味し、
ポインター変数 p1 +12 ( 3*sizeof(int) )の値

3 つのポインターと配列

3.1 配列ポインタ

1. 変数にはアドレスがあり、配列には複数の要素が含まれます。各配列要素はメモリ内の記憶単位を占有し、すべてに対応するアドレスがあります。いわゆる配列ポインタは、配列の開始アドレスを指します
2.配列名は配列の先頭アドレスを示すため、配列名はポインタでもあります
3. 配列名を使用して配列内の要素にアクセスします。

int ch[] = {1,2,3,4};
//假如我们想访问ch中的第3个元素:ch[3] == 4
//我们也可以通过指针法引用数组中的元素:比如 *(ch + 3)
//那么,如果想访问第n个元素呢?
*(ch + n) 注意:n<=sizeof(ch)/sizeof(ch[0])-1

4. 演習: 配列 int a[4] がある場合、次の機能を実現するコードを記述します。

  • キーボードから数値を入力して、配列 a の各要素に値を割り当てます。
  • 配列内の各要素のアドレスを出力します。
  • ポインタメソッドにより配列内の各要素の値を出力します。
#include <iostream>

int main() {
    int a[4];
    int i;
    for (i = 0; i < 4; i++)
        scanf("%d", &a[i]);
    for (i = 0; i < 4; i++) {
        printf("%p %d\\n", &a[i], *(a+i));
    }
    }

5. ポインタ変数を介した配列への間接アクセス

#include <iostream>

int main() {
    int a[4] = {1,2,3,4};
    int *p;
    p = a;
    *(p + 2) = 100;
    char ch[] = {'a', 'b', 'c'};
    char *p2;
    p2 = ch;
    *(p2+1) = 'A';
    }
#include <iostream>

int main() {
    int ch[] = {1, 2, 3, 4};
    printf("%d\n", ch[4]);
    printf("%p %p\n", &ch[3], &ch[4]);
    //数组名:数组的首地址
    printf("ch: %p\n", ch);
    //数组中的第0个元素的地址:数组的首地址
    printf("&ch[0]: %p\n", &ch[0]);
    printf("%d %d\n", ch[3], *(ch+3));
    int a[4];
    int i;
    for (i = 0; i < 4; i++)
    {
        scanf("%d", &a[i]); //a+i
        getchar();
    }
    //打印数组中每个元素的地址
    for (i = 0; i < 4; i++)
        printf("%p\n", &a[i]);
    //通过指针法将数组a中的每一个元素的值打印出来
    for (i = 0; i < 4; i++)
        printf("%d\n", *(a+i));
    int *p;
    p = a; //指针p指向了数组a

    //指针指向了一个数组,可以将指针当数组看待
    for (i = 0; i < 4; i++)
        printf("%d\n", p[i]); //通过下标发访问数组中的元素
        // printf("%d\n", *(p+i));
    }

6.配列ポインタが範囲外です

#include <iostream>

int main() {
    int b[4] = {10, 20, 30, 40};
    int a[4];
    a[4] = 100;
    printf("a[4]: %d\n", a[4]);
    printf("b[0]: %d\n", b[0]);
    printf("a: %u, b: %u\n", &a, &b); //打印数组a和b的首地址
    }

3.2 ポインタの配列

1. 名前が示すように、ポインタ配列はポインタを格納する配列であり、本質的には配列であり、配列内の各要素はポインタです。 

#include <stdio.h>

int main()
{
    int a = 10, b = 20, c = 30;
    int *p[3];
    p[0] = &a;
    p[1] = &b;
    p[2] = &c;
    return 0;
}

2. 注: [] は * より優先順位が高く、最初に p に一致するため、int *p[3]; は (int *) p[3]; と同等です。
3. 考える: 10 人の名前を配列に保存するにはどうすればよいでしょうか?
char *name[10] = {"zhangsan", "lisi", "wangwu", "zhaoliu, "tianqi"}; 10 個の文字列定数の最初のアドレスが name 配列
に保存されます (注: 文字列定数は保存されません)ただし、定数の最初のアドレスです)。

3.3 ポインタ変数のアドレス

1.ポインタ変数を定義すると、コンパイラはポインタ変数の値を格納するための空間を割り当てます。割り当てられたメモリ空間にはアドレス コードが必要です。そのため、アドレス コードはポインタ変数のアドレスでなければなりません。

#include <stdio.h>

int main()
{
    int a = 10;
    int *p;
    p = &a;
    //将指针变量p的值以及变量a的地址打印出来(结果应该是两者相等)
    printf("p: %p, &a: %p\n", p, &a);
    //打印指针变量p的地址(存储指针变量p的内存空间的首地址)
    printf("&p: %p\n", &p);
    return 0;
}

2. 強調: ポインタ変数 p の値は別の変数 a のアドレス 0x300800 を保存し、ポインタ変数のアドレスはポインタ変数 p の値を保存するメモリ空間の最初のアドレスです: 0x3007F8、保存された値このスペースの値は 0x300800 です 

3.4 関数の仮パラメータとしてのレベル 1 ポインタ

1. 関数の仮引数が配列の
場合 関数の仮引数が配列の場合、仮引数の定義方法は以下のとおりです。

void func(int a[], int n)
{}

仮パラメータをポインタ型として定義することもできます
 

void func(int *a, int n)
{}

実際の作業では通常 2 番目の方法を使用します。

2. 関数を呼び出すときは文字列を渡す必要があります。仮パラメータを char * 型として設計できます

#include <stdio.h>

void func(char *p) //调用函数时将字符串的地址赋值给指针变量p
{
    printf("%c\n", p[0]);   //返回值为 'h'
}
int main()
{
    func("hello");
    return 0;
}

3. 仮パラメータが配列の場合、配列の長さを取得するにはどうすればよいですか? //のサイズ( )

sizeof(a) と sizeof(b) の値が両方とも 8 なのはなぜですか?
理由: コンパイラは、コンパイル中にa と bをポインタとして扱います。! 

4. 注:関数の仮パラメータがポインタの場合、ポインタの値が NULL かどうかを判断するために、関数本体内で最初にポインタの値が判断されるのが一般的です。

3.5 セカンダリポインタ

1. ポインター変数を使用して、第 1 レベルのポインター変数のアドレスを保存します。このポインターを第 2 レベルのポインターと呼びます
。2. 第 2 レベルのポインターの定義:

  • データ型**変数名;

3. セカンダリポインタの適用 

void func_1(){
    //二级指针的使用
    int a = 10;
    int *p = &a;
    int **p2 = &p; //二级指针p2保存了一级指针p的地址(p2指向了p )
    int ***p3 = &p2; //三级指针
    //*p2 == p == &a
    printf("%p %p %p\n", *p2, p, &a);  //000000000061FDE4 000000000061FDE4 
    //**p2 == *p == *(&a) == a
    printf("%d %d %d %d\n", **p2, *p, *(&a), a);   //10 10 10 10

    **p2 = 100;
    printf("%d %d %d %d\n", **p2, *p, *(&a), a);  //100 100 100 100
    //*p3 == p2 = &p
    //**p3 == *p2 == p == &a
    //***p3 == **p2 == *p == *(&a) == a
    printf("***p3: %d\n", ***p3);   //***p3: 100
}

3.6 メモリの割り当て

1. 実際の作業において、複数のデータを格納する必要がある場合、多くの学生はまず配列の使用を考えますが、配列の長さは定義後に固定されるため、十分な柔軟性が得られないことがよくあります
2.まず、格納するデータのタイプに応じてポインタ変数 (例: int *p;) を定義し、次にmalloc 関数を使用して
実際のニーズに応じてスペースを割り当てます。

3. malloc 関数:

#include <stdlib.h>
void *malloc(size_t size);

機能: malloc 関数は、システムのような size バイトのメモリ空間に適用され、割り当てられたメモリ空間の最初のアドレスを指すポインタを返します。要求されたメモリ空間は「ヒープ」上にあります。ヒープ上のスペースは手動で適用し、手動で解放する必要がありますそうしないとメモリ リークが発生します。

int *p;
//假如我们需要存储10个int类型的数据
p = (int *)malloc(10*sizeof(int));

注: malloc によって割り当てられる領域はバイト単位であるため、割り当てられる領域は 10*sizeof(int) です。

int *p;
//通过指针变量p操作一块空间,可以存储4个int数据
//向系统申请 4*sizeof(int)字节的内存空间
p = (int *)malloc(4*sizeof(int)); //在堆上申请了4*sizeof(int)字节的内存空间
//p的值:申请到的堆上的内存空间的首地址 (指针p指向申请到的堆上的空间)
printf("p: %p\n", p);
//通过指针变量p 来操作申请到的堆空间
p[0] = 100;
p[1] = 200;
*(p+2) = 300;
*(p+3) = 400;

4.メモリ解放:無料機能 

#include <stdlib.h>
void free(void *ptr);

機能: ptr が指すメモリ空間を解放します。

注:
free 関数はポインター変数の値を変更しません。しかし、free の実行が完了した後は、ポインタが指す元のアドレス空間の内容は不確かになります。
質問:
スペースを解放すると、具体的にはどのような効果が得られますか?
最も重要なことは、このメモリ空間が他のユーザーによって使用可能であることをシステムに伝えることです。

  • mallocが配置されているソース ファイル: #include <stdlib.h>
  • strcpyが配置されているソース ファイル: #include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char *argv[]){
    char *p;
    //malloc分配的空间是在堆上的,需要手动释放
    p = (char *)malloc(10);
    strcpy(p, "hello");
    printf("p所指向的空间的内容: %s\n", p); //结果是hello

    //将p所指向的地址空间的首地址打印出来(就是将指针变量p的值打印出来)
    printf("p的值: %p\n", p);
    free(p);
    //将p所指向的地址空间的首地址打印出来
    printf("p的值: %p\n", p);

    //仔细观察,下面这条打印语句的结果
    printf("p所指向的空间的内容: %s\n", p);  //结果不是hello

    strcpy(p, "world");
    //再仔细观察,下面这条打印语句的结果
    printf("p所指向的空间的内容: %s\n", p);  //结果是world

    return 0;
}

無料関数が呼び出された後にスキルを使用します。 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char *argv[]){
    char *p;
    //malloc分配的空间是在堆上的,需要手动释放
    p = (char *)malloc(10);
    strcpy(p, "hello");
    
    //释放申请的内容
    free(p);
    p = NULL;
    return 0;
}

5. 以前に割り当てられたスペースが十分でない場合はどうすればよいですか? realloc 関数
を使用できます。 

#include <stdlib.h>
void *realloc(void *ptr, size_t size);

機能: size で指定された新しいメモリ空間をヒープ上に割り当てます。空間サイズの単位はバイトです。また、ptr で指定された空間の内容を新しいメモリ空間にコピーし、最後に新しいメモリ空間の前頭アドレスを返します
サンプルコード: 

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main()
{
    char *p;
    p = (char *)malloc(10);
    strcpy(p, "hello"); //向分配的空间中拷贝字符串
    printf("p所指向空间的首地址: %p\n", p);
    printf("p所指向空间的内容: %s\n", p);
    p = (char *)realloc(p, 20); //重新分配新的空间
    printf("p所指向新的空间的首地址: %p\n", p);
    printf("p所指向新的空间的内容: %s\n", p);
    //注意:分配的新的空间的首地址有可能有之前分配的空间首地址一样,也有可能不一样
    strcat(p, " world"); //追加字符串
    printf("p所指向新的空间的内容: %s\n", p);
    return 0;
}

6. char *dest, *src; というシナリオを考えます。 src が指すアドレス空間の内容を、関数を通じて dest が指すアドレス空間にコピーしますが、関数を呼び出す前に src の長さがわからないと仮定します。関数を使用する場合、この時点では関数の仮パラメータを二次ポインタとして設計する必要があります。

void test2(char **dest, char *src){
    //通过二级指针dest给形参一级指针dest分配内存空间!
    *dest = (char *)malloc(strlen(src) + 1);
    if (NULL == *dest || NULL == src)
        return ;
    strcpy(*dest, src);
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void func4(char **dst)
{
    //*dst == p
    *dst = (char *)malloc(10); //在堆上申请了10个字节
    strcpy(*dst, "hello");
}

int main()
{
    char *p; //指针指向某个函数调用结束后 在函数体中申请的堆空间的首地址
    /*
    * 既然我希望让p指向一块堆空间,其实就是希望对p进行赋值,赋值为在函数中申请的堆空间的首地址
    * 如何在函数中对p进行赋值呢?必须在调用函数的时候传递p的地址!!!!
    * */
    func4(&p);
    printf("%s\n", p);
    free(p);
    return 0;
}

おすすめ

転載: blog.csdn.net/March_A/article/details/131333982