第八章-指针(数据类型 *)
指针
- 指针类型占据8个字节,表示内存地址
- 指针变量:用于存放指针类型的数据,即内存地址
- *p:指针运算,拿到指针变量p的内容(一个内存地址),然后到内存中找到这个地址,拿出里面的值(注意:这时会根据指针的类型,拿出多少个字节的数据,int拿4个,char拿一个,因此,指针必须有类型,同理,存储也一样,根据指针类型,分配多少内存存储这个数据)
- &name:取地址运算,拿到name这个变量(name可以是任何类型的变量,也可以是指针变量)的内存地址
- 指针地址图:
*(*x) = 10; <=> a = 10;
/*
析:x = effc5;
*x = p = effc0;
*p = a = 3;
*/
指针与数组
- 数组元素的访问方式
- 数组名[下标] ages[i]
- 指针变量名[下标] p[i]
- *(p + i)
int ages[5]; int *p; p = ages;
- 指针变量+1,地址值究竟加多少,取决于指针的类型
- int * => 4
- char * => 1
- double * => 8
int ages[5] = {10, 9, 8, 67, 56}; int *p; // 指针变量p指向了数组的首元素 //p = &ages[0]; // 数组名就是数组的地址,也是数组首元素的地址 p = ages; /* p ---> &ages[0] p + 1 ---> &ages[1] p + 2 ---> &ages[2] p + i ---> &ages[i] */ printf("%d\n", *(p+2)); //p<=>数组名(就是数组的地址) printf("%d\n", p[2]);
- sizeof区分数组名和指针变量(迷惑点!!!!)
#include<stdio.h>
//注意:int array[]参数会警告:应该用int *array,也就是数这里的array就不是数组名字了,仅仅是个指针变量而已
void test(int array[]){
int s = sizeof(array);
//打印结果为:8 ,因为array不是数组了,而是指针变量
printf("%d\n",s);
//在这里拿到的count永远是1,就不是数组元素的个数了
//int count = sizeof(array)/sizeof(int);
//在这里拿到数组元素的个数,只能多加一个参数了
}
int main(){
int ages[] = {1,2,3,4,5};
int *p = ages;
//这样定义会报警告:将一个数组赋值给了一个指针变量,但是仍然不会报错,因为默认会把数组的第一个元素地址给该指针变量
int *w = {1,2,3,4,5};
//但是尽管字符串是字符数组但是这样写却不会警告!!!
char v[] = "it";
char *sss= "it";
//尽管ages是数组地址,但是结果可不是8!!!,因为ages是数组类型
int s = sizeof(ages);
//p也是数组地址,但结果是8,因为p是指针类型!!!
int x = sizeof(p);
int z = sizeof(w);
//打印结果:20===8===8
printf("%d===%d==%d\n",s,x,z);
/*
总结:
虽然数组名(ages)是数组元素的第一个元素的地址,p理论上是与ages一样,但是实际是不一样的!!!,ages仍然是数组名,p仍然是一个指针变量而已!!
*/
test(ages);
return 0;
}
test2(){
//字符串也一样!!!
//字符指针
char *sss= "it";
字符数组
char vp[] = "it";
int s = sizeof(sss);
int x = sizeof(vp);
//打印结果:8===3
printf("%d===%d\n",s,x);
}
指针与字符串(重要!!!!)
- 内存分5块:常量区/堆/栈/BSS段(静态区)/代码段
-
常量区:存放一些常量字符串
特点:省内存 char *name = "it"; char *name2 = "it"; name与name2指向地址相同!!!,即指向同一块地址 char name3[] = "it"; char *name4 = "it"; name3与name4指向的地址可是不一样哦!!!
- 堆:存放对象
- 栈:存放局部变量 (C语言的数组都是放在栈里面的)
-
- 定义字符串的2种方式
- 利用数组(放在栈)
char name[] = "itcast";
- 特点:字符串里面的字符是可以修改的
- 使用场合:字符串的内容需要经常修改
- 利用指针(放在常量区)
char *name = "itcast";
- 特点:字符串其实是一个常量字符串,里面的字符是不能修改
- 使用场合:字符串的内容不需要修改,而且这个字符串经常使用
// 定义字符串数组 void test2(){ //int ages[5]; // 指针数组(字符串数组)与字符数组区分开 char *names[5] = {"jack", "rose", "jake"}; // 二维字符数组(字符串数组) char names2[2][10] = {"jack", "rose"}; } // 定义字符串 void test() { //根据字符串就是特殊的字符数组来分析: // 字符串变量(!!!) char name[] = "it"; name[0] = 'T'; //Tt printf("%s\n", name); // "it" == 'i' + 't' + '\0' // 指针变量name2指向了字符串的首字符 // 字符串常量(!!!) char *name2 = "it"; //常量不可改的!!! *name2 = 'T'; //第一个字符的地址 printf("%p\n", name2); //打印字符串(error 10 程序崩溃!!!!) printf("%s\n", name2); } tests(){ //也会崩溃!!! //这样定义会报警告:将一个数组赋值给了一个指针变量,但是仍然不会报错,因为默认会把数组的第一个元素地址给该指针变量 int *w = {1,2,3,4,5}; *w = 5; printf("%d",*w); }
- 利用数组(放在栈)
指针数组作为main函数的形参
- 指针数组的一个重要应用就是作为main函数的形参
- 一般情况下main函数是没有参数的,如下:
int main ()
或者int main(void)
- 在某些情况下main函数可以有参数的,例如:
int main(int argc,char *argv[])
- 其中,argc和char* argv[] 就是main函数的形参,他们叫做程序的命令参数.
- argc是argument count的缩写-参数的个数;
- argv是argument vector的缩写-参数向量,他是一个char *指针数组,数组中每一个元素(其值为指针)指向命令中的一个字符串
- 通常main函数和其他函数组成一个文件模块,有一个文件名,对这个文件进行编译/连接,得到可执行文件(.exe),用户执行这个可执行文件,操作系统就回调用main函数,然后由main函数调用其他函数,从而完成程序的功能
- 什么情况下main函数需要参数? main函数的形参是从哪里传递给他的呢?
- main函数是由操作系统调用的,由操作系统给出实参
- 操作系统给出操作命令,调用main函数
- 操作命令通常是以命令行的形式给出的
- 命令行中包含了命令名和需要传给main函数的参数
命令名 参数1 参数2 ...
- 命令名和个参数之间分别用空格分开
- 命令名是可执行文件名(此文件包含main函数,实际上该文件名包含了盘符/路径),为简化这里就不写盘符/路径了
- 比如命令行:
file1 China Beijing
- main函数接收时,命令名也算一个参数,因此argc为3,argv为一个数组,元素为字符指针,分别指向file1,china,Beijing这3个字符串的首地址
-
main函数代码如下:
int main(int argc,char *argv[]){ while(argc>1){ ++ argv; printf("%s",argv); -- argc; } return 0; }
- 在Visual C++环境下,对程序编译连接后,选择工程->设置->调试->程序变量->输入”China Beijing”,在运行程序,则会打印: ` China Beijing`
- 这只是一个简单的举例使用,至于具体使用,比较难,就不在深入了
返回指针的函数
- 顾名思义:就是这个函数返回的是一个指针,而不是基本数据类型
#include <stdio.h>
char *test();
int main(){
char *name = test();
printf("name=%s\n", name);
return 0;
}
char *test(){
return "rose";
}
指向函数的指针
- 顾名思义:函数也有地址,只要指针指向函数的地址就能找到这个函数
- 函数作为程序的一部分,它也有地址
- 函数名就是函数的地址(首地址)
- 定义函数指针变量:
- 格式:
函数返回类型 (*指针变量名) (函数的参数类型,...);
- 格式:
函数:
void test(){
printf("调用了test函数\n");
}
double haha(double d, char *s, int a){
}
/******例1********/
// (*p)是固定写法,代表指针变量p将来肯定是指向函数
// 左边的void:指针变量p指向的函数没有返回值
// 右边的():指针变量p指向的函数没有形参
void (*p)();
// 指针变量p指向了test函数
p = test;
//调用函数的三种方式
p();//因为p=test嘛
(*p)(); // 利用指针变量间接调用函数
test(); // 直接调用函数
/*****例2*******/
// 1.定义指向函数的指针
double (*p)(double, char *, int);
p = haha;
//或者定义时直接赋值
double (*p)(double, char *, int) = haha;
//2.如何间接调用函数
//1>第一种
p(10.7, "jack", 10);
//2>第二种
(*p)(10.7, "jack", 10);
指针变量=NULL
- 指针变量可以有空值,即不指向任何变量,
p = NULL;
- 其中NULL是一个符号常量,代表整数0.在stdio.h中对NULL进行了定义:
#define NULL 0
- 他使p指向地址为0的单元.系统保证使该单元不作它用(不存放有效数据).
- 其中NULL是一个符号常量,代表整数0.在stdio.h中对NULL进行了定义:
- 注意:p的值为NULL跟未对p赋值是两个概念.前者是指向0地址,后者是指向未知数,也叫野指针,很危险
void指针类型
- C99允许使用基类型为void的指针类型,他不指向任何类型的数据.他赋值给另一个指针变量时(或者被赋值时),有系统给他自动转换,使它适合于被赋值的变量类型.
- 代码举例:
int a = 3;
int *p = &a;
char *p2 ;
void *p3;
//可以不需要(void *),系统自动转换
p3 = (void *)p1;
//可以不需要(void *),系统自动转换
p2 = (char *)p3;
p3 = &a;
如何区别:指针** 与 **指针?
- 方法:将前面两个字替换成整型就容易理解了
- 替换结果:整型** 与 整型指针
- 使用举例:
- 指针函数 与 函数指针
- 替换后为: 整型函数(函数返回值为int) 与 整型指针(指针变量指向int)
- 因此可得结论:
指针函数:返回值为指针的函数 --- int *test(); 函数指针: 一个指针变量指向一个函数 --- int (*test)();
- 指针数组 与 数组指针
- 替换后为: 整型数组(数组元素为int) 与 整型指针
- 因此可得结论:
指针数组:一个数组的元素都是指针 --- int *test[10]; 数组指针: 一个指针变量指向一个数组 -- int (*test)[10];
- 指针函数 与 函数指针