C语言指针

index


1. 内存、内存地址、指针 简单说明

1.1 内存
  • 多个储存单元线性排列到一起构成了内存
  • 一个存储单元是一个字节,也就是8位
  • 32 位架构下的计算机只有32根地址总线也就是说只能存放2^32 的内存地址,4GB
  • 64 为架构下的计算机有64根地址总线,也就可以存放 2^ 64 的内存地址,16GB

    1.2 内存地址
  • 对每一个字节(存储单元进行一个编号),这个编号就可以称之为内存地址

    1.3 指针
  • 我们想要访问数据(内存的内容),通过内存地址可以找到这个数据,现在将地址的值存到指针中,通过访问指针就可以获取到内存地址,进而获取数据。

  • 指针就是用来存放内存地址的变量

    2.普通变量和指针变量的区别

  • 首先都是变量,是变量就会有 内存地址 和 值,例如: int a = 1,我们用 &a 来 去出内存地址,而 a 则代表值是 1.

  • 区别 : 普通变量和指针变量都是 地址 + 值 ,普通变量的值一般只是一个 基本的数据类型的值,而指针变量的值存的是另外一个变量的地址。所以前者的值只是值,而后者的值代表的却是地址。

    3.指针和指针变量

  • 很多时候我们将指针变量也简化称之为指针,但是严格意义上来讲不是准确的

  • 指针是一个概念,一般我们说指针就会想到计算机的内存地址,而真正存放内存地址的是具体哪一个指针变量。为了简化理解,我们可以将指针看做是一个类,而指针变量看做是这个类中具体的某个对象。

    4.指针变量的定义和赋值

    `` int p; // 定义一个指针变量p,指向整型变量char cp; // 定义一个指针变量cp,指向字符型变量float int fp; // 定义一个指针变量fp,指向实数型变量double dp1, *dp2; ` // 定义两个指针变量p,指向双精度型变量
  1. **为什么前面要加上不同的数据类型?**<br />上面已经提到了,指针变量存放的是其它变量的内存地址,显然不同数据类型变量的字节数不一样,这就一定导致存储单元的编号不同,也就是说我们还需知道要指针变量指向的那个变量的数据类型才能够确定它的内存地址。
  2. - 指针变量的类型不是指它本身的类型,而是指这个指针变量指向的那个变量的数据类型。
  3. - C语言中空指针,不指向任何单元。
  4. - `*p` 并不是指指针变量 `p` 才是的,* 的作用就像 PHP 和 Jquery 中的 `$` ,只是一个标识符。
  5. - 不能以数值作为指针变量的初始值, `int *p = 100;` 这是错误的, 但`int *拼= 0;` 这是对的,应为这里的0是 ASCII字符中的NULL。含义就是 定义一个 空指针。

int i; int p = &i; p = 0; p = null; p = (int) 1732; // 指向内存地址为1732的int型 变量

  1. #### 5.指针的基本运算
  2. > - 为间接运算符 , & 为取地址运算符。
  3. **例8-2**

include

int main(void) {

  1. int a = 3, *p;
  2. p = &a;
  3. printf("a = %d , *p = %d \n", a, *p); // a = 3 , *p = 3
  4. *p = 10;
  5. printf("a = %d , *p = %d \n", a, *p); // a = 10 , *p = 10
  6. printf("Enter a:"); // 输入5
  7. scanf("%d", &a);
  8. printf("a = %d , *p = %d \n", a, *p); // a = 5 , *p = 5
  9. (*p)++;
  10. printf("a = %d , *p = %d \n", a, *p); // a = 6 , *p = 6
  11. return 0;

}

  1. **例8-3** 变量值的交换

include

int main(void) {

  1. int a = 1, b = 2;
  2. int *pa = &a, *pb = &b;
  3. void swap1(int x, int y), swap2(int *px, int *py), swap3(int *px, int *py);
  4. swap1(a , b);
  5. printf("use func swap1: a = %d , b = %d \n", a , b);
  6. a = 1, b =2;
  7. swap2(pa , pb);
  8. printf("use func swap2: a = %d , b = %d \n", a , b);
  9. a = 1, b =2;
  10. swap3(pa , pb);
  11. printf("use func swap3: a = %d , b = %d \n", a , b);
  12. return 0;

}

void swap1(int x, int y) { int t ; t = x; x = y; y = t; }

void swap2(int px, int py) { int t; t = px; px = py; py = t; }

void swap3(int px, int py) { int *pt; pt = px; px = py; py = pt; }

  1. **分析运行结果**<br />
  2. use func swap1: a = 1 , b = 2 <br />
  3. use func swap2: a = 2 , b = 1 <br />
  4. use func swap3: a = 1 , b = 2<br />运行第一个函数,并没有发生 变量 a、b值的互换。这是因为在调用函数发生的是`值传递`,改变形参的值,并不能够改变实参的值。<br />如运行 下面的程序,两次打印不都是 a = 1;<br />#include <stdio.h><br />int main(void) <br />
  5. {<br />
  6. int a = 1;<br />
  7. void fun1(int x);<br />
  8. printf("a = %d \n", a);<br />
  9. fun1(1);<br />
  10. printf("a = %d \n", a);<br />
  11. return 0;<br />
  12. }<br />void fun1(int x)<br />
  13. {<br />
  14. x = 3;<br />
  15. }<br />运行第二个函数,发生了变量值的互换。这是因为传递的是指针变量 **调用函数虽然不能够改变指针变量的值(地址),但是间接的通过地址是可以改变指针变量所指向变量的值**<br />运行第三个函数,没有发生了变量值的互换。懂得动懂,这写法就不对,这是 *p和p的区别。。
  16. > 总之,如果想要通过函数调用来改变主调函数中某个变量的值,可以将指针变量作为新参传递。<br />
  17. 函数一般通过 return 只能返回一个值,如果想要返回多个值,可以考虑指针。
  18. **例8-4 输入年份和天数,输出对应的年、月、日**<br />
  19. 这里并没出现过return,但是通过指针改变了,多个变量的值。起到了一个类似于return的作用

/ 使用指针作为函数参数返回多个函数值示例 /

include

int main(void) { int year, month, day, yearday; void month_day (int year, int yearday, int pmonth, int pday); printf(“inout year and day:”); scanf(“%d%d”, &year, &day); month_day (year, day, &month, &day); printf(“%d-%d-%d \n”, year, month, day);

  1. return 0;

}

void month_day (int year, int yearday, int pmonth, int pday) { int k, leap; int tab [2][13] = { {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 30, 31, 30, 31}, // index = 0 存放非闰年 {0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 30, 31, 30, 31}, // index = 1 存放闰年 }; // leap 作为闰年和非闰年的判别条件 => 转 int true => 1 ….. leap = ( (year % 4 == 0 && year %100 != 0) || year % 400 == 0 ); // leap 经过赋值运算,只会被转换为 0/1 for (k = 1; yearday < tab[leap][k]; k++) { year -= tab[leap][k]; } pmonth = k; pday = yearday; }

  1. #### 6. 指针变量和数组
  2. - 数组名本身就是一个地址(指针)
  3. - 在访问内存上指针和数组几乎是相同的,但有些区别
  4. - 指针是一个变量,变量中存放地址。数组名是中存放的是一个固定的地址。
  5. - 可以将数组看做是一个常量指针

// 如下代码,他们的作用都是等价的 int a[100], *p;

p = a; p = &a[0];

p = a + 1; 0 = &a[1];

  1. **所以我们可以通过指针来对数组求和**

// 用指针对数组求和

include

int main(void) { int p_a, a[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}, sum = 0; for (p_a = &a[0]; p_a <= &a[9]; p_a++){ sum += p_a; } printf(“a = %d \n”, sum);

  1. return 0;

}

  1. **例8-7 输入10个整数,分别使用数组和指针计算数组中的元素之和**

include

int main(void) { int i, a[10], *p;

  1. printf("请输入10个整数: \n");
  2. for(i = 0; i < 10; i++)
  3. scanf("%d", &a[i]);
  4. int sum = 0;
  5. for(i = 0; i < 10; i++)
  6. sum += a[i];
  7. printf("fun1: sum = %d \n", sum);
  8. sum = 0;
  9. for(p = a; p <= &a[9]; p++)
  10. sum += *p;
  11. printf("fun2: sum = %d \n", sum);
  12. return 0;

}

  1. #### 7.数组名作为函数的参数
  2. 阅读下面的代码

int sum (int a[], int n) { int i, s = 0; for( i = 0; i < n; i++) s += a[i]; return s;
}

  1. > 当使用数组作为进行参数传递时,主函数传递的是数组a的基地址,也就是a[0] 的地址,数组元素本身并不被复制,最为编译习惯,提示可读性加上了中括号
  2. **例8-8 利用指针将数组中的元素逆序存放**

include

int main(void) { int i, a[10], n; void reverse(int p[], int n);

  1. printf("Enter n : \n");
  2. scanf("%d", &n);
  3. for (i = 0; i < n; i++)
  4. {
  5. scanf("%d", &a[i]);
  6. }
  7. reverse(a, n);
  8. for (i = 0; i < n; i++)
  9. {
  10. printf("%d \t", a[i]);
  11. }
  12. return 0;

}

void reverse(int p[], int n) { int i, j, t; for (i = 0, j = n - 1; i < j; i++, j—) { t = p[i]; p[i] = p[j]; p[j] = t; } }

  1. #### 8. 字符串和字符指针
  2. **例8-9 输入一串字符串,要求每个字母后一一位,例如 z =>a, a =>b, ....**

include

include

define MAXLINE 100

void encrypt(char *);

int main(void) {

  1. char line[MAXLINE];
  2. printf("Input the string:");
  3. gets(line); // 取到了输入的字符串
  4. encrypt(line); //调用函数对字符串进行加密
  5. printf("加密后的字符串为: %s \n", line);
  6. return 0;

}

void encrypt(char s) { for(; s != ‘\0’; s++) // S 相当于 指针指向char 数组中对应的值 , s++ 就相当于指针中存放的地址移动到下一个 char 类型的数组元素 if ( s == ‘z’) s = ‘a’; else s += 1;
}

  1. **上述代码就是,通过指针的移动(触发内存地址的改变,间接性的改变内存地址中存放的值) 实现了了对数组中每个元素的遍历和处理。**
  2. - C语言中并没有对字符串单独设立一个基本的数据类型,它就是一个一维的字符数组
  3. - 字符数组在内存中连续存放,变量名存放的是第一个元素的内存地址,也就是首地址。
  4. - 所以定义一个字符指针来接受字符串常量的值,这个指针就指向了字符串第一个字符的地址

include

int main(void) {

  1. char sa[] = "array";
  2. char *sp = "point";
  3. printf("%s \n",sa); // 数组名作为参数输出
  4. printf("%s \n",sp); // 字符指针作为参数输出
  5. printf("%s \n","hello word!"); // 字符常量作为参数输出
  6. // 调用printf(),无论是数组名、还是字符指针、还是字符常量此时都是作为字符串的首地址
  7. // 程序从改地址所指向的存储单元连续输出存储的内容,直到遇到'\0'结束。
  8. // 因此字符串中的其他字符的地址也可以作为输出参数
  9. printf("-------------------------------\n");
  10. printf("%s \n",sa + 1);
  11. printf("%s \n",sp + 1);
  12. printf("%s \n","hello word!" + 1);
  13. return 0;

}

  1. 运行结果如下图:<br />
  2. ![](https://cdn.nlark.com/yuque/0/2020/png/719308/1578318619564-39888415-9a25-4564-894e-fdf21de85601.png)<br />**定义一个字符指针,记得给初始值**<br />如下代码, 只是错误的,定义字符指针没有给指针赋值,它的值是不确定的。不能明确的指向它的内存单元<br />`char *s;`<br />
  3. `scanf("%s", s);`<br />正确的应该是这样<br />`char *s , str[20];`<br />
  4. `s = str;`<br />
  5. `scanf("%s", s);`<br />为了避免这样的问题,可以将指针的初始值设置为空。<br />`char *s = NULL;`<br />**常用字符串处理函数**
  6. - sacnf() 和 gets() 用来输入字符串
  7. - printf() 和 puts() 用来输出字符串
  8. **字符串的复制、连接、比较长度**<br />**例8-11 输入5个字符串,输出其中最小的字符串**

include

include

int main(void) { int i; char str[80], smin[80];

  1. printf("请输入字符串 : \n");
  2. gets(str); // 获取输入的字符串
  3. strcpy(smin , str);
  4. for(i = 1; i < 5; i ++){
  5. gets(str);
  6. if (strcmp(str, smin) < 0){ // str < smin
  7. strcpy(smin, str);
  8. }
  9. }
  10. puts("最小的字符串是");
  11. puts(smin);
  12. return 0;

}

#### 9. 用指针实现内存的动态分配
**例8-12 任意个整数求和。先输出一个正整数n,在输出任意n个整数,计算这n个整数的和,要求使用动态内存分配的方法为这n个整数分配空间**

include

include

int main(void) { int i, n, sum, *p; printf(“Enter n : \n”);

scanf("%d", &n);
// 为数组动态分配n个整数类型的空间
if ((p = (int *)calloc(n, sizeof(int))) == NULL)
{
    printf("内存不够了亲! \n");
    exit(1); // 终止程序
}
printf("Enter %d integers: \n", n);
for (i = 0; i < n; i++)
{
    scanf("%d", p + i);
}
sum = 0;
for (i = 0; i < n; i++)
{
    sum += *(p + i);
}
printf("The sum is %d \n", sum);
free(p); // 释放分配的内存
return 0;

}

``` 理解:
calloc() 这个函数是在 stdlib.h 这个库中定义的。用于分配内存,
第一个参数是 : 要分配内存的元因素的个数
第二个参数是 : 每个元素要分配的内存空间 sizeof(int) 单位肯定是字节了,
如过分配成功,就会返回一个指针,指向第一个元素所在的内存地址,失败返回 NULL
中间的计算过程就是通过指针的移动来来计算这些变量的和
最后 free() 释放分配的内存。

  1. 程序中用变量来保存数据信息,变量在被使用前需要非配储存空间(内存的起始地址、存储单元的大小)
  2. C 语言中 全局变量 和 静态的局部变量 在编译时确定了储存,内存的分配在程序运行前就已经OK了。
  3. 对于局部变量、在程序运行到这一行语句时才会被分配存储单元。
  4. 以静态的方式安排存储的优点就是:效率高(减小运行的消耗)、实现简单。但缺点很也明显:经常有些时候我们是无法提前获知程序运行时某个变量需要多大的存储空间,这就需要我们动态实现内存的分配了。
  5. C 语言实际上是 提供了动态存储的管理机制,允许程序动态的申请和释放存储空间。

1.动态分配内存的步骤

  • 获知需要多少内存空间
  • 使用C提供的 内存动态分配函数来分配内存
  • 使用指针指向获取的内存空间,以指针的移动来实现数据的运算和操作
  • 实用完后,释放内存

2.动态分配内存的函数

  • alloc
  • calloc
  • free
  • realloc