一、指针数组和数组指针
什么是指针?指针的全称指针变量,它是存放地址的变量。
地址有两种运算:1 偏移 ± N int a; &a+1 &a 2 间接运算 *一元运算
定义指针变量 int a; int* p = &a; 在声明定义的语句中的*不是运算符,而是说明变量是指针变量身份的符号。
*指针变量
[] 数组
() 函数
1. 指针数组
在分析一个标识符的含义时,如果标识符被很多符号修饰,由近及远先右后左。
是数组。 每个元素都是一个指针。 array of pointer
int *a[5]; |int *|int *|int *|int *|int *|
[]说明a是数组,*说明数组的元素是指针,int说明指针指向的是整型。
每个元素都是指针。指针名称:
a[0] int*
a[1] int*
a[2] int*
a[3] int*
a[4] int*
示例1:
#include <stdio.h>
int main()
{
int a = 90,b = 55,x = 10;
int *p[3] = {&a,&b,&x};//定义指针数组p,分别使用a b x的地址,对p[0] p[1] p[2] 初始化
//于是p[0]指向 a p[1]指向 b p[2]指向 x
int i;
for(i=0; i<3; i++)
{
//间接运算得到的结果不是值,而是地址指向的变量本身
printf("%d\n ",*p[i]);//p[i]是一个指针变量 需要间接运算来访问它指向的变量
}
}
示例2:
#include <stdio.h>
int main()
{
int a[5] = {1,2,3,4,90};
int b[5] = {55,6,7,8,9};
int c[5] = {7,8,7,8,6};
int *p[3] = {a,b,c}; //使用abc三个数组的首地址初始化p的三个元素
//于是p[0]指向数组a p[1]指向数组b p[2]指向数组c
int i,x;
for(x=0; x<3; x++)//遍历数组p
{
for(i=0; i<5; i++)
{
printf("%5d", p[x][i]); //和二维数组的区别,a b c三个数组可能不是连续的;abc三个数组在这里是可以不等长的,如果数组不等长,内层for循环的写法也要发生变化。
//p[0] 指向a p[0]存放的是数组a的首地址
//p[1] 指向b
//p[2] 指向c
//p[x][i] 等价于a[i] 或者b[i] 或者c[i]
}
printf("\n");
}
}
强调:
不要使用sizeof求数组的长度,因为求不出来。
sizeof()只有在给它数组名的时候才能求出数组的长度。我们什么时候才能获得到数组名?1 数组是全局的 2 使用sizeof()的时候和数组在同一个作用域内。
在C/C++语言中,别人定义的数组如果不告诉你数组有多长,你永远也不知道数组有多长。求不出来数组长度。
2.字符指针数组
是数组。每个元素都是字符指针。
定义: char *p[3]; p[0] p[1] p[2] |char *|char *|char *|
示例3:
在定义的语句中,* 不是运算符,而是用来表达身份的符号。
间接运算得到什么结果?不是值!!!!而是地址对应的变量的本身。
间接运算是针对地址的运算,只有地址才能进行间接与运算。
#include <stdio.h>
int main()
{
char str[] = "hello";//数组长度6 因为字符串的结尾有'\0'
char ss[] = "better";//7
char *p[2] = {str, ss};//p[0]指向数组str p[1]指向数组ss
int i;
for(i=0; i<2; i++)
{
printf("%s\n", p[i]);//%s需要字符串的首地址 p[i]是字符数组的首地址,字符数组中存放字符串,所以字符数组的首地址也是字符串的首地址
}
return 0;
}
练习1:
接示例3中代码,将两个字符串中的e换成a,使用p遍历字符串。
#include <stdio.h>
int main()
{
char str[] = "hello";
char ss[] = "better";
char* p[] = {str, ss};
int i;
for(i = 0;i < 2;i++)
{
int j = 0;
while(p[i][j] != '\0')//遍历字符串
{
if(p[i][j] == 'e')
{
p[i][j] = 'a';
}
j++;
}
}
for(i = 0;i < 2;i++)
{
printf("%s\n", p[i]);
}
return 0;
}
练习2:
随机点名。 定义字符指针数组,初始化人名(字符串常量)。每次运行程序随机打印一个人名。
随机数组的角标
#include <stdlib.h>
srand(time(0));//初始化随机种子,种子只能初始化一次
rand()%N;//获得一个随机数
#include <stdio.h>
#include <stdlib.h>
int main()
{
/*char* str = "hello";
char* ss = "better";
char* p[] = {str, ss};*/
char* p[] = {
"xiaozhao",
"xiaoqian",
"xiaosun",
"xiaoli"
};
srand(time(0));//初始化随机种子
int index = rand()%4;
printf("%s\n", p[index]);
return 0;
}
练习3:
定义字符指针数组 char *names[5]分别指向 5 个字符串常量,从小到大输出字符串的内容。
交换的是指针的指向
#include <stdio.h>
#include <string.h>
int main()
{
char* p[] = {
"xiaozhao",
"xiaoqian",
"xiaosun",
"xiaoli",
"xiaozhang",
"xiaowang",
"xiaoli",
"xiaozhao"
};
int i, j;
for(i = 0;i < 7;i++)
{
for(j = 0;j < 7-i;j++)
{
if(strcmp(p[j], p[j+1]) == 1)
{
char* t = p[j];
p[j] = p[j+1];
p[j+1] = t;
}
}
}
for(i = 0;i < 8;i++)
{
printf("%s\n", p[i]);
}
return 0;
}
3.数组指针
是指针。 是为了指向多维数组的指针。
数组名是数组的首元素地址,类型是数组的元素类型,使用指针指向数组时,需要将指针定义成数组的元素类型,
把数组名赋值给指针。
int a[3][4] = {{1,2,3,4},{6},{8}};
int (*p)[4]; // *离p最近,因为有()括起来了,所以这里p是指针,[]说明指针指向的是数组类型,int说明数组的元素是int类型。
p = a;
定义指针变量指向数组,需要把指针变量定义成数组的元素类型,然后把数组名赋值给指针变量
int main()
{
int a[3][5] = {{1,2,3,4},{6},{8}};
int(*p)[5]; // \*离p最近,因为有()括起来了,所以这里p是指针,[]说明指针指向的是数组类型,int说明数组的元素是int类型。
p = a;
//int* q = a;//语法错误
//a是数组名,是数组的首元素地址,类型是元素类型的地址,所以a的地址类型是int [5]
int i, j;
for(i = 0;i < 3;i++)
{
for(j = 0;j < 5;j++)
{
printf("%d ", p[i][j]);
}
printf("\n");
}
return 0;
}
示例4:
#include <stdio.h>
void print_var(int (*p)[4], int n)
{
int i,j;
for(i=0; i<n; i++)
{
for(j=0; j<4; j++)
{
printf("%5d", p[i][j]);
}
printf("\n");
}
return;
}
int main()
{
int a[3][4] = {{1,2,3,4},{6},{6}};
print_var( a,3 );
return 0;
}
二、二级指针
指向指针的指针变量
int a = 100; int *p = &a; int **q = &p;
右边的*说明q是一个指针变量,*说明q指向的还是一个指针变量,int类型的指针变量。q是一个指向int类型指针变量的指针变量。给q赋值,需要int*类型的变量地址。
------------------------------------------------------
地址: 0x00001234 0x00001238 0x0000123c
变量: a p q
值 : 100 0x00001234 0x00001238
-------------------------------------------------------
表示:
a = 100 *p = 100 **q = 100
&a=0x00001234 p=0x00001234 *q=0x00001234
&p=0x00001238 q = 0x00001238
&q= 0x0000123c
示例5:
#include <stdio.h>
#include <stdlib.h>
//函数功能是在堆空间申请一块内存,返回给主调函数。函数在堆空间创建了一个int类型数组。
int *get_memory(int n)
{
//malloc是在堆空间申请内存,堆空间内存不会自动释放。
//参数:内存的大小(字节)。 n*sizeof(int) 就是N个int类型的大小。就是N个元素的int类型数组。
//返回值: void* 类型,无类型的指针。 无类型地址仅仅只一个地址,不能间接运算,因为没有void类型的变量。
//所以我们会把返回的void*地址转换成我们需要的类型。在这段代码中,因为我们要创建int类型的数组,所以将地址转换成int*。
int *p = (int *)malloc( n * sizeof(int) );
return p; //将申请到内存的地址返回给主调函数
}
int main()
{
int *p;
int n;
puts("Enter n:");
scanf("%d", &n);
p = get_memory( n ); //根据输入的整数,创建对应长度的整型数组
int i, sum = 0;
for(i=0; i<n; i++)
{
scanf("%d", &p[i]);
sum += p[i];
}
printf("%d\n", sum);
free(p);//释放堆空间数组
p = NULL;//在释放堆空间的内存后,将指针置空是好习惯,尽管在当前这段代码中没什么作用。
return 0;
}
示例6:
#include <stdio.h>
#include <stdlib.h>
//在示例5的基础上改进,不是通过返回值的形式把地址返回给主调函数,而是通过参数的形式将地址返回给主调函数。
//想把被调函数中的地址通过参数的形式返回主调函数,被调函数返回的数据类型是int*,所以参数应该定义成能够存放int*类型变量地址的指针变量,所以参数类型应该是int**
void get_memory( int ** q, int n)
{
*q = (int *)malloc( n * sizeof(int) );
return ;
}
int main()
{
int *p;
int n;
puts("Enter n:");
scanf("%d", &n);
get_memory( &p, n ); //使用主调函数中的变量p的地址,初始化被调函数的形参q
//通过形参q把get_memory函数中申请的内存的地址赋值给了p
int i, sum = 0;
for(i=0; i<n; i++)
{
scanf("%d", &p[i]);
sum += p[i];
}
printf("%d\n", sum);
free(p);
p = NULL;
return 0;
}
三、指针函数、函数指针
1. 指针函数
返回值是指针类型的函数,叫指针函数。
char * strcpy(char *dest,const char *src);
char * strcat(char *dest,const char *s2);
void * malloc( size_t size );
void* 空类型指针,可以指向任何类型的变量。
2. 函数指针
是指针。 是用来指向函数的指针。
函数名就是函数的地址。
函数是在内存的代码段,函数名就是函数在内存中的地址。
函数指针实现多态。 盒饭 二楼食堂和盒饭,一荤两素,红烧鱼、芹菜土豆丝、西红柿炒鸡蛋
对象
买房
int get_sum(int *p, int n);
int (*p1)(int *, int); //()里的内容,取决于参数列表的类型
p1 = get_sum;
void print_arr(int *p, int n);
void (*p2)(int *, int );
p2 = print_arr;
void swap(char *q, char *p);
void (*p3)(char *, char *);
p3 = swap;
//或者写成
void (*p3)(char *, char *) = swap;//初始化
//调用函数:把函数名换成函数指针
get_sum(&a, b);
p1(&a, b);
(*p1)(&a, b);
练习4:
请定义指针,指向对应的函数,并调用。
int rand( );
int strcmp(const char*s1, const char* s2);
通过指针变量调用函数。
打印一个100以内的随机数
打印"hello world"和"hello farsight"的比较结果
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
int(*pRand)() = rand;//定义函数指针pRand指向函数rand
int(*pStrcmp)(const char*, const char*) = strcmp;
srand(time(0));
printf("%d\n", pRand()%100);//通过函数指针pRand调用函数rand
printf("%d\n", pStrcmp("hello world", "hello farsight"));
return 0;
}
问题:
以下变量的类型是?
int a; 整型变量
int a[10]; 10个元素的整型数组
int *a; 整型指针
int *a[10]; 10个元素的整型指针数组,数组中是10个int类型的指针
int (*a)[10]; 指向数组的指针,数组10个int类型元素
int (*a[3])[10]; 3个元素的指针数组,指针指向10 个int类型元素的数组
int *a(int); 返回值是int类型指针的函数
int (*a)(int); 函数指针,指向参数列表是int,返回值也是int的函数
int *(*a)(int); 函数指针,指向参数是int,返回值是int*的函数
int (*a[2])(int); 2个元素的指针数组,指针指向参数是int,返回值是int的函数
3. 函数指针数组
定义: int (*p[4])(int ,int );
示例7:
#include <stdio.h>
//add sub mul返回值类型和参数列表都一样,所以可以使用同样类型的函数指针指向它们。
int add(int x,int b)
{
return x+b;
}
int sub(int x,int b)
{
return x-b;
}
int mul(int x,int b)
{
return x*b;
}
int main()
{
//数组p中的三个函数指针,分别指向了add sub mul
int (*p[3])(int ,int ) = {add, sub, mul};
int i;
for(i=0; i<3; i++)
{
printf("%d\n", p[i](6,9));//i=0 add i=1 sub i=2 mul
}
return 0;
}
作业:
选择题
- [单选题-C语言]
关于if语句,下面哪一种说法是错误的 ( ) (牛客网)
A: 一个if只能有一个else与之配对
B: if语句中可以有多个else if子句
C: if语句中可以包含循环语句
D: if语句中不能包含switch语句
- [单选题-位运算]
下面关于位运算符的叙述,正确的是?( ) (牛客网)
A: #表示"按位异或"的运算
B: &表示"按位或"的运算
C: ~表示"按位取反"的运算
D: ||表示"按位或"的运算
3.[单选题-C语言]
下面哪个语句无法通过编译? ( ) (牛客网)
A: if (x>y);
B: if (x=y) && (x!=0) x+= y;
C: if (x!=y) scanf("%d",&x); else scanf("%d",&y);
D: if (x<y) {x++; y++;}
- [单选-linux]
若基于Linux操作系统所开发的ARM应用程序源文件名为test.c,
那么要生成该程序代码的调试信息,编译时使用的GCC命令正确的是? ( ) (阿里巴巴)
A: arm-linux-gcc -c -o test.o test.c
B: arm-linux-gcc -S -o test.o test.c
C: arm-linux-gcc -o test test.c
D: arm-linux-gcc -g -o test test.c
- [单选-C]
若整型变量a、b、c、d中的值依次为:1、4、3、2。则条件表达式a<b?a:c<d?c:d的值 ( ) 。(牛客网)
A: 1
B: 2
C: 3
D: 4
6.[单选题-优先级]
已知int i=1, j=2;,则表达式i+++j的值为( )。
A: 1
B: 2
C: 3
D: 4
- [单选题]
在软件生命周期中,( )阶段负责“写出正确、易懂,容易维护的程序模块”。
A: 详细设计
B: 编码和单元测试
C: 确认测试
D: 总体设计
- [单选题]
break和continue两个都是( ) (美行科技)
(A)控制循环指令 (B) 数据结构
© 相等的判断 (D) 不包含在C++中
- [单选题]
有以下程序
main()
{ int a=666,b=888;
printf("%d\n",a,b);
}
程序运行后的输出结果是( )。
A: 错误信息
B: 666
C: 888
D: 666,888
编程题:
编写函数,[采用递归方法]实现将输入的字符串按反序输出。(北京麦邦)
函数自己调用自己
并没有改变字符串,仅仅是反着输出 “abc” 输出"cba"
void out_char(char *p)
{
}
#include <stdio.h>
void printNum(int num)
{
printf("%d\n", num);
if(num == 10)
return;
printNum(num+1);//递归
}
int main()
{
printNum(1);
return 0;
}
6.[单选题-优先级]
已知int i=1, j=2;,则表达式i+++j的值为( )。
A: 1
B: 2
C: 3
D: 4
- [单选题]
在软件生命周期中,( )阶段负责“写出正确、易懂,容易维护的程序模块”。
A: 详细设计
B: 编码和单元测试
C: 确认测试
D: 总体设计
- [单选题]
break和continue两个都是( ) (美行科技)
(A)控制循环指令 (B) 数据结构
© 相等的判断 (D) 不包含在C++中
- [单选题]
有以下程序
main()
{ int a=666,b=888;
printf("%d\n",a,b);
}
程序运行后的输出结果是( )。
A: 错误信息
B: 666
C: 888
D: 666,888
编程题:
编写函数,[采用递归方法]实现将输入的字符串按反序输出。(北京麦邦)
函数自己调用自己
并没有改变字符串,仅仅是反着输出 “abc” 输出"cba"
void out_char(char *p)
{
}
#include <stdio.h>
void printNum(int num)
{
printf("%d\n", num);
if(num == 10)
return;
printNum(num+1);//递归
}
int main()
{
printNum(1);
return 0;
}