指针变量
在C语言中,最重要的就是对于指针和地址的理解,因为C语言是更接近底层的编程语言,所以它可以允许开发者对内存操作,这也是区别于其它编程语言的一个重要特性。
如何对内存进行操作呢。我们知道在编程过程中,在任何语言中,我们在定定义一个变量的过程中,实际上就是向操作系统申请了一块内存,但由于其它编程语言不支持我们对于内存进行操作,所以我们只知道申请了一块内存,但不知道该位置在哪。由于C语言支持我们对于内存的操作,所以我们可以通过在变量前加一个取地址符&,得到该内存的地址,数组的话无需加取地址符。
#include<stdio.h>
int main(){
int a[5];
int b;
printf("&b = %p, sizeof(&b) = %lu\n", &b, sizeof(&b));
printf("a[0] = %p\n", a);
printf("a[1] = %p\n", a + 1);
printf("a[2] = %p\n", a + 2);
printf("a[3] = %p\n", a + 3);
printf("a[4] = %p\n", a + 4);
}
运行结果
>>&b = 0x7fffe87408ec, sizeof(&b) = 8
a[0] = 0x7fffe87408f0
a[1] = 0x7fffe87408f4
a[2] = 0x7fffe87408f8
a[3] = 0x7fffe87408fc
a[4] = 0x7fffe8740900
可以看到整型数组的地址间隔刚好为4,也就是说明整型变量大小是四个字节。而且指针的大小是8个字节。
指针的大小
在我所演示的运行环境是在64位操作系统下运行的,64bit刚好是8字节,也就是说如果在32位操作系统下运行该程序的话sizeof(&b)会是4字节。
- 指针的大小是与系统环境相关的
- 以int为例,int占用四个字节,在取地址时从相关地址的首地址开始
- 在一个32bit操作系统中,地址最多为4GB,所以假设插入一个8GB的内存条则会有另外4GB无法使用
变量的地址
指针变量也是变量,也就说,指针变量同样也有地址。由此,我们就可以对数组的各个地址取地址符,这样就构成了二维数组。
#include<stdio.h>
int main(){
int a;
int *p = &a;
int arr[3][3];
printf(" &a = %p\n p = %p\n &p = %p\n", &a, p, &p);
printf(" arr[0][0] = %p, sizeof(arr[0][0]) = %lu\n", arr, sizeof(&arr));
printf(" arr[1][0] = %p, sizeof(arr[1][0]) = %lu\n", arr + 3, sizeof(arr + 3));
printf(" arr[1][0] = %p, sizeof(arr[2][0]) = %lu\n", &arr + 1, sizeof(arr + 3 + 3 + 1));
}
运行结果
>> &a = 0x7fff350f5c54
p = 0x7fff350f5c54
&p = 0x7fff350f5c58
arr[0][0] = 0x7fff350f5c60, sizeof(arr[0][0]) = 8
arr[1][0] = 0x7fff350f5c84, sizeof(arr[1][0]) = 8
arr[1][0] = 0x7fff350f5c84, sizeof(arr[2][0]) = 8
理解了双重指针以及二维数组之后,那么就可以尝试高维数组的实现形式。
所以在scanf(“%d”, &n);这行代码中,我们传入的是地址,因为我们要改变的是n所代表的这块内存上的值,所以需要找到内存的所在地址然后存入数据。
函数指针
在之前函数的章节,已经涉及到了函数指针,但只是把函数作为传入参数的形式,下面我们将演示函数指针的声明形式
#include<stdio.h>
int add(int a, int b){
return a + b;
}
int main(){
int (*func)(int, int);
func = add;
printf("%d", func(1,1));
}
#include<stdio.h>
int add(int a, int b){
return a + b;
}
int main(){
typedef int (*func)(int, int);
func f = add;
printf("%d", f(1, 1));
}
可以看到我们使用了两种方式定义了函数指针。可以看到以第一种是直接声明的了一个函数指针,而后将指针指向了add()函数,那么第二种的话用到了一个关键字typedef,而后func变成了一个类型的样子,随后定义了f指针指向了add()函数。二者的定义形式的区别就是
声明一个函数指针变量: int (*add)(int, int);
定义一个函数指针类型: typedef int (*add)(int, int);
Main函数
我们知道在C语言中,一个程序运行必须要有main函数,但是我们在使用main函数中,基本都是无参数的形式,但实际上的main函数有三种形式,如下。下面就简单了解一下main函数的其他两种形式
int main();
int main(int argc, char *argv[]);
int main(int argc, char *argv[], char **env);
参数解释:
argc: 命令行参数个数(例如 ls -al al就是参数)
*argv[ ]:命令行内容(注意这是一个二位数组)
**env:环境变量
#include<stdio.h>
void output(int argc, char *argv[], char **env){
printf("argc = %d\n", argc);
for(int i = 0; i < argc; i++){
printf("argv[%d] = %s\n ", i, argv[i]);
}
int main(int argc, char *argv[], char **env){
output (argc, argv,env);
return 0;
}
可以看到示例 ./a.out -a -l -s。其中作为参数全部传入了程序中,值得一提的是./a.out也作为了一个参数传入了,也就是说程序在运行时至少会有一个命令行参数。我们也不难发现空格是参数的分隔符,但是我们可以通过加\转义字符,使其可以把空格传入。
#include<stdio.h>
void output(int argc, char *argv[], char **env){
printf("argc = %d\n", argc);
for(int i = 0; i < argc; i++){
printf("argv[%d] = %s\n ", i, argv[i]);
}
for(int i = 0; env[i]; i++){
printf("env[%d] = %s\n", i, env[i]);
}
}
int main(int argc, char *argv[], char **env){
output (argc, argv,env);
return 0;
}
可以看到env参数包含了很多我们的环境变量,包括用户路径语言等等。这些都是操作系统为我们提供的接口允许我们可以做在操作系统中做一些底层的操作。
main()返回值
在我们初学C语言时就接触了main()函数,我们可以不用返回值,但是又会经常见到return 0。那么main的返回值是返回给谁,又有什么作用呢?从函数上来讲,当然是谁调用就返回给谁,该函数是操作系统调用的,所以就是返回给操作系统的。
我们可以通过echo $?的命令查看上一次运行是不是成功运行,如果返回值为0,代表执行失败。如果是1则是成功执行,所以说可能对于我们来讲程序的功能是成功执行的,但是对于操作系统来讲我们的程序没有执行成功。所以我们应尽量在主函数中加入返回值。