09-C语言指针
2018-09-16 本文已影响0人
低头看云
什么是指针
-
指针
- 存放地址的是指针
-
指针变量
- 指针是存放指针的变量
- 指针变量和普通变量一样,都可以用来保存数据,当做形参,当做返回值;
- 只不过指针变量只能保存内存地址
- 简而言之,指针变量,就是专门用于保存内存地址的变量
-
定义指针变量的格式
int num = 6; // 定义一个普通变量
int *p; // 定义一个指针变量
// num变量的占用内存最小的那个地址存储到指针变量p中
p = #
- p是代表取出p中存储的值
- &p是代表取出各自的内存地址
int num = 6;
int *p;
p = # // p指向num地址
printf("&num = %p\n", &num); // 0028FEBC
// 代表取出p中存储的值
printf("p = %p\n", p); // 0028FEBC
// &p和普通变量也一样,代表取出各自的内存地址
printf("&p = %p\n", &p); // 0028FEB8
指针变量的初始化方法
- 指针变量初始化的方法有两种:定义的同时进行初始化和先定义后初始化
- 定义的同时进行初始化
int a = 5;
int *p = &a;
- 先定义后初始化
int num = 6;
int *p;
p = #
- - 把指针初始化为NULL
```
int *p = NULL;
int *p = 0;
```
- 给指针变量赋值时,指针变量前不要再加 "*" 星号;
int num = 6;
int *p;
*p = # // 错误写法
指针的大小和指向问题
- 指标变量占用的内存大小都一样,在32位编译器中,占用4个字节;在64位编译器中,占用8个字节
- 只要一个变量存储了另一个变量的内存地址,那么我们就说这个变量指向了另外一个变量
- 只要一个变量存储另外一个变量的内存地址,我们就可以根据这个内存地址找到另外一个变量的内存,从而操作那一块内存
```
int num = 6;
int *p;
p = #
printf("修改前: num = %i\n", num); // 6
*p = 88;
printf("修改后: num = %i\n", num); // 88
printf("修改后: *p = %i\n", *p); // 88
```
- 指针的注意点:
- 1.指针只能保存地址,不能保存其它的值
int *p = 1.1; // 注意千万不要这样写
- 2.一个变量可以被多个指针指向
int num = 6; int *p1; int *p2; p1 = # p2 = # printf("*p1 = %i\n", *p1); // 6 printf("*p2 = %i\n", *p2); // 6
-
image
-
3.指针的指向可以发生改变
int num = 6; int *p = # // p ---> num int value = 555; p = &value; // p ---> value printf("%i\n", *p);
- 4.定义指针变量时前面的数据类型,必须和将来要保存的变量前面的数据类型一致;
指针为什么要有类型
- 由于指针变量指向的是某一个变量占用存储空间的首地址
- 所以在获取指向变量中存储数据的时候,指针是不知道要获取多少个字节的数据
- 所以指针变量的作用:就是告诉指针,在获取指向变量的数据的时候,需要获取几个字节的数据
int num = 888;
//0000 0000 0000 0000 0010 0010 1011 1000
//int *p;
char *p;
p = #
printf("*p = %i\n", *p); // 888 120(改成char类型会发生数据错误)

小例子
- 定义一个函数, 要求能够在函数中修改传入变量的值
#include <stdio.h>
// void change(int value);
int main()
{
int num = 6;
printf("调用前: num = %i\n", num); // 6 6
// change(num);
change(&num);
printf("调用后: num = %i\n", num); // 6 666
return 0;
}
//void change(int value){
void change(int *value){
*value = 666;
}
-
基本类型
- 基本类型作为形参,在函数内修改形参,不会影响到函数外的实参;
-
image
-
指针类型
-
image
- 要定定义一个函数, 在函数中交换传入变量的值
#include <stdio.h>
void swap(int *num1, int *num2);
int main()
{
// 需求: 要定定义一个函数, 在函数中交换传入变量的值
int a = 4;
int b = 6;
printf("a = %i, b = %i\n" ,a, b); // 4 6
swap(&a, &b);
printf("a = %i, b = %i\n", a, b); // 6 4
return 0;
}
// 传入的地址,则会修改实参
void swap(int *num1, int *num2){
int temp = *num1;
*num1 = *num2;
*num2 = temp;
}

- 定义一个函数,可以同时返回两个变量的和, 差, 积, 商
#include <stdio.h>
void test(int a, int b, int *c, int *d,int *e,int *f);
int main()
{
//要求定义一个函数,可以同时返回两个变量的和, 差, 积, 商
int a = 10;
int b = 20;
int c, d, e, f;
test(a, b, &c, &d, &e, &f);
printf("和 = %i\n", c);
printf("差 = %i\n", d);
printf("积 = %i\n", e);
printf("商 = %i\n", f);
return 0;
}
// 注意点:return的作用是用于结束当前函数,return后面的代码执行不到;
// 可以通过操作指针,实现地址的传递;来实现多个返回值;
void test(int a, int b, int *c, int *d,int *e,int *f){
*c = a + b;
*d = a - b;
*e = a * b;
*f = a / b;
}
二级指针
-
二级指针,就是指向指针的指针
-
定义指针的规律
- 1.将要指向变量的定义拷贝过来
- 2.在拷贝过来的代码的类型和变量名称中间加上一颗星 *;
- 3.修改变量名称
int num;
num = 6;
// 一级指针
int *p;
p = #
// 二级指针
int **pp;
pp = &p;
- 访问二级指针
int num;
num = 6;
// 一级指针
int *p;
p = #
printf("p
= %i\n", *p);
// 二级指针
int **pp;
pp = &p;
printf("pp = %i\n", **pp); // 6

-
什么是多级指针
- 多级指针就是指针中保存的有事其它指针的地址,我们就称为多级指针
-
指针访问规律: 如果想通过多级指针获取某个变量的值,那么是几级指针,前面就写几颗星即可;
指针和数组
- 数组名称保存的就是数组占用内存最小的那个地址
- 既然数组名称保存就是地址,而指针也是用于保存地址,所以指针也可以指向数据
int ages[3] = {1, 3, 5};
int *p = &ages; // p = ages = &ages;
- 结论:如果利用指针保存数组的地址之后,那么p = ages = &ages;
- 指针遍历数组
#include <stdio.h>
int main()
{
int ages[3] = {1, 3, 5};
for(int i = 0; i< 3; i++){
printf("ages[%i] = %i\n", i, ages[i]);
}
printf("------------------------\n");
int *p = &ages;
for(int i = 0; i < 3; i++){
//printf("agee[%i] = %i\n", i , *p++);
printf("agee[%i] = %i\n", i , p[i]);
}
return 0;
}
- 操作数组
int ages[3] = {1, 3, 5];
int *p = &ages;
// ages[0] = 8;
p[0] = 88;
- 访问数组的三种写法
int ages[3] = {1, 3, 5};
// 1.第一种
printf("ages[0] = %i\n", ages[0]); // 1
// 2.第二种
int *p = ages;
printf("p[0] = %i\n", p[0]); // 1
// 3.第三种
printf("0[p] = %i\n", 0[p]); // 1
- 指针可以进行加减法
- 指针 +1, -1;
- 应用场景: 一般都是用于指针数组中
int nums[3] = {1, 3, 5};
int *p = &nums; // 等同于 int *p = nums;
printf("*p = %i\n", *p); // 1;
printf("*(p + 1) = %i\n", *(p + 1)); // 3
printf("*(p + 2) = %i\n", *(p + 2)); // 5
printf("*p = %i\n", *p); // 1;
// 此时p还是执行nums[0]的地址
printf("*p = %i\n", *p++); // 1;
printf("*p = %i\n", *p++); // 3;
printf("*p = %i\n", *p); // 5;
printf("*p = %i\n", *(--p)); // 3
// 指针先++,再取出里面的内容;
- 注意点: 指针变量 +1是加多少, 加的就是指针变量类占用的字节数;
- 如果指针的类型是int类型, 那么 +1 相当于 +4 个字节
指针和字符串
- 字符串的本质就是数组,所以指针也可以指向字符串
char str1[] = {'c','w','w','\0'};
char str2[10] = {'c','w','w'};
char str3[] = "cww";
// printf("str1 = %s\n",str1); // cww
// printf("str2 = %s\n",str2); // cww
// printf("str3 = %s\n",str3); // cww
// 注意下面写法是错误的
char *str4 = {'c','w','w','\0'};
printf("str4 = %s\n",str4); // 罢工
-
利用数组和指针定义字符串的区别
- 1.存储的位置不同
- 如果是通过数组定义的字符串,那么存储在内存的栈中
- 如果是通过指针定义的字符串,那么存储在内存的常量区中
- 2.由于在内存中存储的位置不一样
- 如果通过数组定义的字符串,我们是可以手动修改的
- 但是通过指针定义的字符串,我们是不能手动修改的
char str[] = "cww"; char *str2 = "cww"; str[1] = 'T'; //printf("str = %s\n", str); // cTw //str2[1] = 'T'; printf("str2 = %s\n", str2); // 罢工
- 3.由于内存中的存储位置不一样
- 如果是通过数组定义的字符串,每次定义都会重新开辟存储空间;
- 如果是通过指针定义的字符串,重复定义不会重新开辟存储空间;
// 存储地址不一样 char str1[] = "cww"; char str2[] = "cww"; printf("str1 = %p\n",str1); // 0028FEB4 printf("str2 = %p\n",str2); // ))28FEB0 // 存储地址一样 char *str3 = "cww"; char *str4 = "cww"; printf("str3 = %p\n",str3); // 0040407A printf("str4 = %p\n",str4); // 0040407A
- 1.存储的位置不同
-
注意点:
- 接收字符串的时候,只能通过字符数组,不能通过指针
char str1[10]; char *str2; scanf("%s",str1); // 可以接收 scanf("%s",str2); // 不可以接收 printf("str = %s\n",str1);
- 将过去形参的数组类型修改为指针类型
void test(int str1[]){ 被执行语句; } // 修改为 void test(int *str1){ 被执行语句; }
- 如果函数中返回的字符串是通过数组创建的,那么外界无法获取
#include <stdio.h> char* test(); int main() { char* res = test(); printf("res = %s\n", res); // 无法获取 return 0; } char* test(){ char str[] = "cww"; return str; }
- 如果函数中返回的字符串是通过指针创建的,那么外界可以获取
#include <stdio.h> char* demo(); int main() { char* res = demo(); printf("res = %s\n",res); // 可以获取 return 0; } char* demo(){ char *str = "cww"; return str; }
image
指向函数指针
- 为什么指针可以指向一个函数
- 函数作为一段程序,计算机也会给函数分配存储空间,既然函数会分配内存空间,
- 所以函数也有自己的地址,所以指针变量也可以保存函数的地址
- 数组名保存的就是数组的地址,同样,**函数名中保存的就是函数的地址**
- 如果是数组,我们可以直接将数组名称赋值给一个指针变量
- 如果是函数,我们也可以直接将函数名称赋值给一个指针变量
```
#include <stdio.h>
void test();
int main()
{
test();
printf("test = %p\n", test); // 00401672
printf("&test = %p\n", &test); // 00401672
return 0;
}
void test(){
printf("test\n");
}
```
-
如何定义保存函数的指针变量
- 1.将函数的声明拷贝过来
- 2.在函数返回值和函数名称中间添加一个 * 星
- 3.修改函数名称
- 4.注意:需要将星号和变量名称用括号括起来
#include <stdio.h> void test(); int main() { void (*funcP)(); // 指向函数的指针 // funcP = &test; // 将函数的地址赋给指针 // 等同于 funcP = test; return 0; } // test函数 void test(){ printf("test\n"); }
-
如果一个指针指向了数组,那么访问数组有三种方式
- 1.数组名称[索引];
- 2.指针变量名称[索引];
- 3.*(指针编码名称 + 索引);
int nums[] = {1, 3, 5}; int *p = &nums; // 数组名称[索引]; printf("nums[1] = %i\n", nums[1]); // 指针变量名称[索引]; printf("nums[1] = %i\n", p[1]); // *(指针编码名称 + 索引); printf("nums[1] = %i\n", *(p +1));
-
如果一个指针指向了函数,那么也有多种访问方式
- 1.函数名称();
- 2.(*指针变量名称)();
- 3.指针变量名称();
#include <stdio.h> void test(); int main() { // 第一种:函数名称(); test(); void (*funcP)(); // 指向函数的指针 funcP = &test; // 等同于 // funcP = test; // 第二种:(*指针变量名称)(); (*funcP)(); // 第三种:指针变量名称(); funcP(); return 0; } void test(){ printf("test\n"); }
- 小练习
#include <stdio.h>
void say();
void sum(int a, int b);
int minus(int a, int b);
char* test();
int main()
{
// 指针指向函数的练习
// 1.
// void (*p1)();
// p1 = say;
// p1();
// 2.
// void (*p2)(int , int);
// p2 = sum;
// p2(10, 20);
// 3.
// int (*p3)(int, int);
// p3 = minus;
// int res = p3(30,20);
// printf("res = %i\n", res);
// 4.
char* (*p4)();
p4 = test;
char* res = p4();
printf("res = %s\n", res);
return 0;
}
void say(){
printf("Hello World!\n");
}
void sum(int a, int b){
printf("sum = %i\n",a + b);
}
int minus(int a, int b){
return a-b;
}
char* test(){
char *name = "cww";
return name;
}
#include <stdio.h>
int sum(int a , int b);
int minus(int a, int b);
int test(int num1, int num2, int (*func)(int ,int));
int main()
{
int (*p1)(int ,int);
p1 = sum;
// p1 = ∑ // 二者写法一致;
int res1 = p1(10, 20);
printf("res1 = %i\n", res1); // 30
p1 = −
int res2 = p1(10, 20);
printf("res2 = %i\n", res2); // -10
int res3 = test(10, 20, sum);
printf("res3 = %i\n", res3); // 30
// int res4 = test(10, 20, &minus);
int res4 = test(10, 20, minus);
printf("res4 = %i\n", res4); // -10
return 0;
}
/*
指向函数的指针,作为函数的形参时,指针变量的名称, 就是形参的名称
如果指向函数的指针作为函数的参数,那么这个可以称之为回调函数;
这里相当于,给test函数传入了一个sum或minus函数
然后又在test函数中调用了一个sum函数或者是minus函数
*/
int test(int num1, int num2, int (*func)(int ,int)){
return func(num1, num2);
// return (*func)(num1,num2);
}
int sum(int a , int b){
return a + b;
}
int minus(int a, int b){
return a -b;
}

- 指向函数的指针,作为函数的形参时,指针变量的名称, 就是形参的名称
如果指向函数的指针作为函数的参数,那么这个可以称之为回调函数;
这里相当于,给test函数传入了一个sum或minus函数
然后又在test函数中调用了一个sum函数或者是minus函数
