include <stdio.h>
int main(){
int a[6] = {0, 1, 2, 3, 4, 5};
int *p = a;
int len_a = sizeof(a) / sizeof(int);
int len_p = sizeof(p) / sizeof(int);
printf("len_a = %d, len_p = %d\n", len_a, len_p);
return 0;
}
运行结果:
len_a = 6, len_p = 1
- 编译器在编译过程中会创建一张专门的表格用来保存名字以及名字对应的数据类型、地址、作用域等信息,sizeof 是一个操作符,不是函数,使用 sizeof 时可以从这张表格中查询到符号的长度。
- C语言标准规定,当数组名作为数组定义的标识符(也就是定义或声明数组时)、sizeof 或 & 的操作数时,它才表示整个数组本身,在其他的表达式中,数组名会被转换为指向第 0 个元素的指针(地址)。
- 用 a[i] 这样的形式对数组进行访问总是会被编译器改写成(或者说解释为)像 *(a+i) 这样的指针形式。
#include <stdio.h>
int main(){
int a = 16, b = 932, c = 100;
//定义一个指针数组
int *arr[3] = {&a, &b, &c};//也可以不指定长度,直接写作 int *arr[]
//定义一个指向指针数组的指针
int **parr = arr;
printf("%d, %d, %d\n", *arr[0], *arr[1], *arr[2]);
printf("%d, %d, %d\n", **(parr+0), **(parr+1), **(parr+2));
return 0;
}
运行结果:
16, 932, 100
16, 932, 100
#include <stdio.h>
int main(){
char *str[3] = {
"c.biancheng.net",
"C语言中文网",
"C Language"
};
printf("%s\n%s\n%s\n", str[0], str[1], str[2]);
return 0;
}
运行结果:
c.biancheng.net
C语言中文网
C Language
#include <stdio.h>
int main(){
char *str0 = "c.biancheng.net";
char *str1 = "C语言中文网";
char *str2 = "C Language";
char *str[3] = {str0, str1, str2};
printf("%s\n%s\n%s\n", str[0], str[1], str[2]);
return 0;
}
指针数组和二级指针
#include <stdio.h>
int main(){
char *lines[5] = {
"COSC1283/1284",
"Programming",
"Techniques",
"is",
"great fun"
};
char *str1 = lines[1];
char *str2 = *(lines + 3);
char c1 = *(*(lines + 4) + 6);
char c2 = (*lines + 5)[5];
char c3 = *lines[0] + 2;
printf("str1 = %s\n", str1);
printf("str2 = %s\n", str2);
printf(" c1 = %c\n", c1);
printf(" c2 = %c\n", c2);
printf(" c3 = %c\n", c3);
return 0;
}
运行结果:
str1 = Programming
str2 = is
c1 = f
c2 = 2
c3 = E
#include <stdio.h>
int main(){
char *string0 = "COSC1283/1284";
char *string1 = "Programming";
char *string2 = "Techniques";
char *string3 = "is";
char *string4 = "great fun";
char *lines[5];
lines[0] = string0;
lines[1] = string1;
lines[2] = string2;
lines[3] = string3;
lines[4] = string4;
char *str1 = lines[1];
char *str2 = *(lines + 3);
char c1 = *(*(lines + 4) + 6);
char c2 = (*lines + 5)[5];
char c3 = *lines[0] + 2;
printf("str1 = %s\n", str1);
printf("str2 = %s\n", str2);
printf(" c1 = %c\n", c1);
printf(" c2 = %c\n", c2);
printf(" c3 = %c\n", c3);
return 0;
}
lines[1]:它是一个指针,指向字符串string1,即 string1 的首地址。
*(lines + 3)
:lines + 3 为数组中第 3 个元素的地址,*(lines + 3)
为第 3 个元素的值,它是一个指针,指向字符串 string3。
*(*(lines + 4) + 6)
:*(lines + 4) + 6 == lines[4] + 6 == string4 + 6
,表示字符串 string4 中第 6 个字符的地址,即 f 的地址,所以 *(*(lines + 4) + 6)
就表示字符 f。
(*lines + 5)[5]
:*lines + 5
为字符串 string0 中第 5 个字符的地址,即 2 的地址,(*lines + 5)[5]
等价于*(*lines + 5 + 5)
,表示第10个字符,即 2。
*lines[0] + 2
:lines[0] 为字符串 string0 中第 0 个字符的地址,即 C 的地址;*lines[0]
也就表示第 0 个字符,即字符 C。字符与整数运算,首先转换为该字符对应的 ASCII 码,然后再运算,所以*lines[0] + 2 = 67 + 2 = 69
,69 对应的字符为 E。
指向二维数组的指针
#include <stdio.h>
int main(){
int a[3][4] = { {0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11} };
int (*p)[4] = a;
printf("%d\n", sizeof(*(p+1)));
return 0;
}
运行结果:
16
#include <stdio.h>
int main(){
int a[3][4]={0,1,2,3,4,5,6,7,8,9,10,11};
int(*p)[4];
int i,j;
p=a;
for(i=0; i<3; i++){
for(j=0; j<4; j++) printf("%2d ",*(*(p+i)+j));
printf("\n");
}
return 0;
}
运行结果:
0 1 2 3
4 5 6 7
8 9 10 11
int *(p1[5]); //指针数组,可以去掉括号直接写作 int *p1[5];
int (*p2)[5]; //二维数组指针,不能去掉括号
- 指针数组和二维数组指针有着本质上的区别:指针数组是一个数组,只是每个元素保存的都是指针,以上面的 p1 为例,在32位环境下它占用 4×5 = 20 个字节的内存。二维数组指针是一个指针,它指向一个二维数组,以上面的 p2 为例,它占用 4 个字节的内存。
指向函数的指针
#include <stdio.h>
//返回两个数中较大的一个
int max(int a, int b){
return a>b ? a : b;
}
int main(){
int x, y, maxval;
//定义函数指针
int (*pmax)(int, int) = max; //也可以写作int (*pmax)(int a, int b)
printf("Input two numbers:");
scanf("%d %d", &x, &y);
maxval = (*pmax)(x, y);
printf("Max value: %d\n", maxval);
return 0;
}
运行结果:
Input two numbers:10 50↙
Max value: 50
int *p1[6]; //指针数组
int *(p2[6]); //指针数组,和上面的形式等价
int (*p3)[6]; //二维数组指针
int (*p4)(int, int); //函数指针
C语言标准规定,对于一个符号的定义,编译器总是从它的名字开始读取,然后按照优先级顺序依次解析。对,从名字开始,不是从开头也不是从末尾,这是理解复杂指针的关键!
- 有几种运算符的优先级非常容易混淆,它们的优先级从高到低依次是:
- 定义中被括号( )括起来的那部分。
- 后缀操作符:括号( )表示这是一个函数,方括号[ ]表示这是一个数组。
- 前缀操作符:星号*表示“指向xxx的指针”。
main()函数的高级用法
int main();
int main(int argc, char *argv[]);
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <math.h>
#include <stdlib.h>
int isPrime(int n);
int main(int argc, char *argv[]) {
int i, n, result;
if (argc <= 1) {
printf("Error: no input integer!\n");
exit(EXIT_SUCCESS);
}
for (i = 1; i < argc; i++) {
n = atoi(argv[i]); //C 库函数 int atoi(const char *str) 把参数 str 所指向的字符串转换为一个整数(类型为 int 型)
result = isPrime(n);
if (result < 0) {
printf("%3d is error.\n", n);
}
else if (result) {
printf("%3d is prime number.\n", n);
}
else {
printf("%3d is not prime number.\n", n);
}
}
return 0;
}
//判断是否是素数
int isPrime(int n) {
int i, j;
if (n <= 1) { //参数错误
return -1;
}
else if (n == 2) { //2是特例,单独处理
return 1;
}
else if (n % 2 == 0) { //偶数不是素数
return 0;
}
else { //判断一个奇数是否是素数
j = (int)sqrt(n);
for (i = 3; i <= j; i += 2) {
if (n % i == 0) {
return 0;
}
}
return 1;
}
}
int main(int argc, char *argv[]) {
int a = 9.5, b;
b = (int)a;
printf("%d\n", b);
return 0;
}
9
int main(int argc, char *argv[]) {
int a = 9.5, b;
char s[] = "152";
b = atoi(s);
printf("%d\n", b);
return 0;
}
152
程序在运行过程中需要的是数据和指令的地址,变量名、函数名、字符串名和数组名在本质上是一样的,它们都是地址的助记符:在编写代码的过程中,我们认为变量名表示的是数据本身,而函数名、字符串名和数组名表示的是代码块或数据块的首地址;程序被编译和链接后,这些名字都会消失,取而代之的是它们对应的地址。
int *p
; p 可以指向 int 类型的数据,也可以指向类似int arr[n]
的数组。int **p
; p 为二级指针,指向int *
类型的数据。int *p[n]
; p 为指针数组。[ ] 的优先级高于 *,所以应该理解为int *(p[n])
;int (*p)[n]
; p 为二维数组指针。int *p()
; p 是一个函数,它的返回值类型为 int *。int (*p)()
; p 是一个函数指针,指向原型为 int func() 的函数。
-
指针变量可以进行加减运算,例如p++、p+i、p-=i。指针变量的加减运算并不是简单的加上或减去一个整数,而是跟指针指向的数据类型有关。
-
给指针变量赋值时,要将一份数据的地址赋给它,不能直接赋给一个整数,例如int *p = 1000;是没有意义的,使用过程中一般会导致程序崩溃。
-
使用指针变量之前一定要初始化,否则就不能确定指针指向哪里,如果它指向的内存没有使用权限,程序就崩溃了。对于暂时没有指向的指针,建议赋值
NULL
。 -
两个指针变量可以相减。如果两个指针变量指向同一个数组中的某个元素,那么相减的结果就是两个指针之间相差的元素个数。
-
数组也是有类型的,数组名的本意是表示一组类型相同的数据。在定义数组时,或者和 sizeof,& 运算符一起使用时数组名才表示整个数组,表达式中的数组名会被转换为一个指向数组的指针。