main 说到地址,在C语言中一般都与指针挂钩,我们可以利用指定的指针变量来存储特定的内存块的首地址。指针可以可以指向的变量包括整型变量、浮点型变量、结构体变量、指针变量乃至字符串和函数,因为这些事物都是占据内存空间的实体。所以,指针的便利之处在于其能够储存某个变量的首地址,并通过首地址访问或修改在这个内存空间的值。下面举一个例子:
//代码清单1
#include<stdio.h>
#include<string.h>
int main()
{
char str[25]="Be stronger,be helpful!";
printf("%s\n", str);
char *str_ptr = str;
str_ptr[strlen(str) - 1] = '.';
printf("%s", str);
return 0;
}
执行结果是什么呢?如图:
在第5行代码中,我们定义了一个字符串并往它里面添加了一些字符,然后我们在第7行定义了一个字符指针并且用 str[] 数组的首地址 str 为它赋初值,之后在第8行通过 str_ptr 将 str[] 字符数组的最后一个字符由 ‘ ! ’ 替换为 ‘ . ’ ,通过6、9行的输出语句分别输出被替换字符前后储存在 str[] 内的字符串。我们发现,通过这样的操作,我们的确修改了 str[] 内的字符串。
与此类似地,我们可以将字符串修改的这一操作推广到各种变量,在其中,掌握指向指针变量的指针也是很重要的。我们举一个例子:
//代码清单2
#include<stdio.h>
#include<string.h>
int main()
{
char **str_ptr = NULL;
char *str[2] = { "Be stronger,be helpful!","Hold your dream." };
str_ptr = str;
printf("%s\n", str_ptr[0]);
printf("%s", str_ptr[1]);
return 0;
}
在第5行代码中,我定义了一个二重指针 str_ptr ,而在第6行代码中则定义了一个含有两个字符指针作为元素的一个一维数组,第7行中将 str 作为初值赋给了 str_ptr 。为什么能将一个一维数组的首地址赋给一个二重指针呢?原因只有一个,str 此时是一个“二重指针常量”,我找不到更好的词来说明 str 的性质了,但显然,我们将常量值赋给变量,这是很合理的一种想法。我们还可以这样来理解这个“二重指针常量”:str[]中的元素本身就是字符指针,故 str[0]、str[1] 就已是普通的字符指针了,可以理解为“一重指针常量”,这种字符指针指向普通的字符串;而 str[] 又是有着两个元素的数组,str 表示的正是它的首地址,故 str 相对于自身元素而言又是“一重指针常量”,由于其元素本身已是“一重指针常量”,我们可以认为相对于普通的指针,str 可以理解为“二重指针常量”。至于三重指针,也是一样的道理,不过更加复杂了。
另外,指针的应用显然不止这么简单,如果我们在为函数传参数时,想要为想要改变传递的参数的值,我们需要这个参数的地址,举一个例子:
//代码清单3
#include<stdio.h>
typedef struct stu
{
char id[3];
char name[10];
}stu;
int modify(stu *stud)
{
strcpy(stud->id , "7");
strcpy(stud->name, "Log");
return 0;
}
int main()
{
stu student;
stu *stude = &student;
strcpy(student.id, "6");
strcpy(student.name, "Rog");
printf("%s :%s\n", student.id, student.name);
modify(stude);
printf("%s :%s", student.id, student.name);
return 0;
}
结果为:
可以看到, 通过将 modify() 的形参设置为 stu* 类型,我们在第20行将同类型的变量 stude 传递给了 modify() ,然后 student 的值的确在 modify() 函数内被改变了。那就要问了,如果我们不将指针 stude 传给这个函数,我们能直接将一个 stu 类型而非 stu* 类型的变量作为 modify() 的形参从而改变 student 的值吗?我们不妨试试:
//代码清单4
#include<stdio.h>
typedef struct stu
{
char id[3];
char name[10];
}stu;
int modify(stu stude)
{
stu *stud = &stude;
strcpy(stud->id , "7");
strcpy(stud->name, "Log");
return 0;
}
int main()
{
stu student;
strcpy(student.id, "6");
strcpy(student.name, "Rog");
printf("%s :%s\n", student.id, student.name);
modify(student);
printf("%s :%s", student.id, student.name);
return 0;
}
结果为:
student 的值并没有改变,因此这种方法是行不通的。原因?
C语言使用一种被称为“按值”的机制,给函数传递参数。这是因为,当使用变量作为函数参数的时候,C语言并不是把变量的地址传给函数,而是传递变量的值。
————《C语言实用之道》[美] Giulio Zambon 著 潘爱民 译
因此,这串代码中,我们只是将 student 的值拷贝给了 modify() 的形参 stude ,当我们在第20行调用 modify() 的时候,实际上我们只是给它传了 student 的值,然后很快就将这个值给了 stude ,接下来在 modify() 中起作用的实际上是 stude ,而不是 student 。这样看来,student 和 stude 实际上是两个不同的变量,我们可以试验一下,只需将代码清单4的代码改为:
//代码清单5
#include<stdio.h>
typedef struct stu
{
char id[3];
char name[10];
}stu;
int modify(stu stude)
{
stu *stud = &stude;
strcpy(stud->id, "7");
strcpy(stud->name, "Log");
printf("stude's address:%p\n", &stude);
return 0;
}
int main()
{
stu student;
strcpy(student.id, "6");
strcpy(student.name, "Rog");
printf("student's address:%p\n", &student);
printf("%s :%s\n", student.id, student.name);
modify(student);
printf("%s :%s", student.id, student.name);
return 0;
}
结果为:
在我的PC机上可以看到 student 和 stude 的确是被存储在不同的内存单元的,而这正说明了 student 和 stude 是两个不同的变量。
有的时候,我们也可以定义一个字符指针来达到行使字符数组的功能,例如:
//代码清单6
#include<stdio.h>
int main()
{
char *str = "Be stronger,be helpful!";
/*
char *str = NULL;
str = "Be stronger,be helpful!";
*/
//第7、8行的为str赋值的方式也是正确的
/*
char str[25];
str = "Be stronger,be helpful!";
*/
//第13、14行的为str赋值的方式是错误的,
//因为第13行定义的str[]数组被分配了内存,
//str是这块内存的首地址,已经是一个常量了
printf("%s", str);
return 0;
}
结果为:
再说一下我们在每个程序中都必写的 main() 函数,它的完整形式为:
main(int argc,char *argv[])
写一个小程序:
//代码清单7
#include<stdio.h>
int main(int argc, char *argv[])
{
printf("当前文件为:%s", argv[0]);
int iInt = atoi(argv[1]);
printf("接收的第一个参数为: %d\n", iInt);
iInt = atoi(argv[2]);
printf("接收的第二个参数为: %d\n", iInt);
iInt = atoi(argv[3]);
printf("接收的第三个参数为: %d\n", iInt);
printf("总共接收了 %d 个参数", argc - 1);
return 0;
}
当然,它不能在编译器上运行,它是在控制台上运行的,接下来我将教你如何操作这个函数,我的运行环境是Visual Studio,我们先运行一下这个程序:
这是正常的,我们要的正是当前文件的路径,而编译器也只能给我们这个,接着程序便运行不下去了。得到了这个exe文件(可执行文件)路径,我们继续。
具体步骤为:
1、按下 Win+R 键
2、输入cmd
3、进入到要执行的exe文件所在目录
这里对第3个步骤进行详细讲解:
首先,我利用在编译器中执行一次的方法得到了相应源文件的可执行文件,并且在我的PC机中它的路径为:C:\Users\86187\source\repos\博客专用\Debug\博客专用.exe 那么,我需要在弹出的控制台窗口中输入 cd C:\Users\86187\source\repos\博客专用\Debug ,同时按下Enter键,画面为:
接着依次输入exe文件的名称和随便三个数字: 博客专用 12 23 34 ,我们就得到了下图所示的结果:
可以看到,在控制台中我们看到了我们在源文件中写的代码清单7中的代码的执行结果。那么接下来,我讲一下 argv 和 argc 的意义,argc 默认值是1,代表只有数组 argv[] 的元素个数只有一个,即 argv[0] ,而 argv[0] 的意义大家想想自己是如何获取到自己的可执行文件路径的呢?没错,argv[0] 代表的正是由这个源文件生成的可执行文件的路径。说回来,argc 的大小代表着数组 argv[] 的元素个数,我们利用控制台执行了这个文件,输入了3个参数,分别为12、23、34,是没有用到输入函数如scnaf()、gets()的,这一点大家明确。另外,atoi() 函数可以将数字字符串转换成数字,atoi即alphabet to integer,想多了解可以戳这里。
本篇完。
欢迎指正我的上一篇博客:递归实现双向链表
我的下一篇博客:一元多项式的相加和相减操作(链表)