赞
踩
char *pa; // 定义一个指向字符型的指针变量
int *pb; // 定义一个指向整型的指针变量
取地址运算符(&)
,也称为间接运算符:char *pa = &a;
int *pb = &f;
取值运算符(*)
:printf("%c, %d\n", *pa, *pb);定义的时候代表定义指针变量,其他代表取值运算符
#include <stdio.h> int main() { char a = 'F'; int f = 123; char *pa = &a; int *pb = &f; //间接访问,取值运算符➕指针变量 printf("a = %c\n", *pa); printf("f = %d\n", *pb); *pa = 'C'; //修改等于修改变量 *pb += 1; printf("now a = %c\n", *pa); printf("now f = %d\n", *pb); //检测指针变量的内存大小,64位编译器中占8个字节 printf("sizeof pa = %lu\n", sizeof(pa)); printf("sizeof pb = %lu\n", sizeof(pa)); //打印指针变量的内存地址 printf("the addr of a is: %p\n", pa); printf("the addr of b is: %p\n", pb); return 0; }
a = F
f = 123
now a = C
now f = 124
sizeof pa = 8
sizeof pb = 8
the addr of a is: 0x7ffee83d9667
the addr of a is: 0x7ffee83d9660
扩展内容,一般人注意即可
int *a;
*a = 123;
- 野指针,也就是指向不可用内存区域的指针。通常对这种指针进行操作的话,将会使程序发生不可预知的错误。
- 野指针与空指针(NULL)不同,空指针在C语言中定义为
define NULL ((void *)0)
- 可见空指针指向0地址,而野指针指向不确定的地方。野指针产生主要有两个原因:
- 1.指针变量未初始化:任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。
- 2.指针释放之后未置空:有时指针在free或delete后未赋值 NULL,便会使人以为是合法的。别看free和delete的名字(尤其是delete),它们只是把指针所指的内存给释放掉,但并没有把指针删除。为什么野指针会产生错误呢?现在有指针int* a和一个变量int b;如果a没有赋值的话,他可能指向任意地方,假设它指向了b的地址,现在又使用了a的指向,如*a=c,那么就在无意间把b的值给改变了。
- 产生野指针以上面第一个原因居多,可以按照以下方法避免:
- 1.定义指针后初始化为NULL;
- 2.使用前制定一个可用地址;
- 3.引用之前判断是否为NULL;
- 4.使用完后赋值为NULL。
函数返回一个指针,该指针指向一个已定义的任一类型的数据。
格式:
类型 *函数名(形参表)
{ 函数体 }
#include <stdio.h> int main() { int a; int *p = &a; //初始化一个指针变量,将a的地址赋给p printf("请输入一个整数:"); scanf("%d", &a); //传递参数到a地址中 printf("a = %d\n", a); //打印a printf("请重新输入一个整数:"); scanf("%d", p); //传递参数到p,就是a的地址中,两者效果一样。注意不能是&p printf("a = %d\n", a); //打印a return 0; }
请输入一个整数:3
a = 3
请重新输入一个整数:5
a = 5
#include <stdio.h>
int main()
{
char str[128]; // 由针对这里的写法,因为scanf函数的不安全,容易产生内存错误,因此规定char数组长度
printf("请输入百度的网址:");
scanf("%s",str); //str前没有&
printf("百度的网站:%s\n", str);
return 0;
}
请输入百度的网址:www.baidu.com
百度的网站:www.baidu.com
数组名是数组第一个元素的地址!
#include <stdio.h> int main() { char str[128]; printf("请输入百度的网址:"); scanf("%s",str); printf("str 的地址是: %p\n", str); printf("str 的地址是: %p\n", &str[0]); //由于字符数组每一个元素都是字符,所以访问字符地址时要加上& return 0; }
请输入百度的网址:www.baidu.com
str 的地址是: 0x7ffee05c15a0
str 的地址是: 0x7ffee05c15a0
#include <stdio.h>
int main()
{
char a[] = "baidu";
int b[5] = {1,2,3,4,5};
float c[5] = {1.1, 2.2, 3.3, 4.4, 5.5};
double d[5] = {1.1, 2.2, 3.3, 4.4, 5.5};
printf("a[0] -> %p, a[1] -> %p, a[2] -> %p\n", &a[0], &a[1], &a[2]);
printf("b[0] -> %p, b[1] -> %p, b[2] -> %p\n", &b[0], &b[1], &b[2]);
printf("c[0] -> %p, c[1] -> %p, c[2] -> %p\n", &c[0], &c[1], &c[2]);
printf("d[0] -> %p, d[1] -> %p, d[2] -> %p\n", &d[0], &d[1], &d[2]);
}
a[0] -> 0x7ffee34df588, a[1] -> 0x7ffee34df589, a[2] -> 0x7ffee34df58a
b[0] -> 0x7ffee34df5e0, b[1] -> 0x7ffee34df5e4, b[2] -> 0x7ffee34df5e8
c[0] -> 0x7ffee34df5c0, c[1] -> 0x7ffee34df5c4, c[2] -> 0x7ffee34df5c8
d[0] -> 0x7ffee34df590, d[1] -> 0x7ffee34df598, d[2] -> 0x7ffee34df5a0
- 字符数组a,元素前后差1字节
- 整型数组b,元素前后差4字节
- 单精度c ,元素前后差4字节
- 双精度d ,元素恰后差8字节
//假设已定义数组a
char *p;
p = a; // 语句1
p = &a[0]; // 语句2
当指针指向数组元素的时候,我们可以对指针进行加减运算
,这样做的意义相当于指向距离指针所在位置向前或向后第n个元素。
p+n的意义是p+n*sizeof(T)
示例
#include <stdio.h>
int main()
{
char a[] = "baidu";
char *p = a;
//注意是*(p+1),指针+1指向下一个地址,而不是*p+1(地址+1)
printf("*p = %c, *(p+1) = %c, *(p+2) = %c\n", *p, *(p+1), *(p+2));
return 0;
*p = b, *(p+1) = a, *(p+2) = i
间接访问的方法叫做指针法
。(即通过指向数组元素的指针找到所需的元素,使用指针占用内存少,运行速度快,目标程序质量高。 )数组名(把数组名看作指针)
用指针法去访问数组元素#include <stdio.h>
int main()
{
int b[5] = {1,2,3,4,5};
//char *p = b; 不定义指针变量
printf("*b = %d, *(b+1) = %d, *(b+2) = %d\n", *b, *(b+1), *(b+2));
return 0;
}
*b = 1, *(b+1) = 2, *(b+2) = 3
用指针定义字符串,用下标法访问每一个元素
#include <stdio.h> #include <string.h> int main() { char *str = "I love FishC.com!"; //给字符指针初始化 int i, length; length = strlen(str); //获取字符串长度 for(i = 0; i < length; i++) { printf("%c", str[i]); } printf("\n"); //换行舒服一点 return 0; }
I love FishC.com!
通常使用两种方法对字符串进行操作:1.字符数组2.字符指针
char *s = “Welcome to C”;
等价于:
char *s;
s = “Welcome to C”;
都能实现字符串的存储和运算。
联系与区别:
存储:
赋值方式:
char *a; a=”I love China!”; √
char str[14]; str[0]=’I’; √
char str[14]; str=”I love China!”; ×
初始化的含义
char *a=”I love China!”; √
char *a; a=”I love China!”; √
char str[14]= ”I love China!”; √
char str[14]; str=”I love China!”; ×
char *a; scanf(“%s”,a); ×,a未初始化
char *a,str[10];
a=str
; scanf (“%s”,a); √
存储单元的内容
指针变量的值是可以改变的;数组名代表数组首地址,是常量,不能改变。
#e.g.改变指针变量的值。
#include <stdio.h>
int main()
{ char *a="I love China!";
a=a+7;
printf(“%s\n”,a);
return 0;
}
> 不能改为
> char a[]=“I love China!”;a=a+7;
char a[]=”House”,*b=”House”(b指向常量);
a[2]=’r’; √
b[2]=’r’; ×
char* str1 = “abcd”;
char str2[] = “abcd”;
char* str1 =”abcd”
含义是先新建一个字符串,内容是abcd 然后str1是一个头指针,指向这个串. 但是这个新建串是作为const存在的,并不是一个可以编辑的变量,因此,一旦你想更改其中的值,程序就会出错。根据定义:指向常量字符串的指针不能更改指针指向的内容,但是可以改变本身的值,既是执行*(str1+2)=’w’;(错误) str1=”bcvcbvv”(正确)
相应的
char *str1 = (char *)malloc(5*sizeof(char));
str1 = “abcd”;
相当于开辟一个5个长度的数组,头指针是str1,但是第二句又把str1指向的位置变了,之后还是不能操作str1的内容.
char str2[] = “abcd”
这个相当与指针常量,就是本身是个常量<因为str2就是数组的一个引用,引用本身就是指针常量>这个的含义是在内存中开辟一个数组,然后向该数组中填充”abcd”, 是一个可操作的变量.所以初始化的时候可以这么写,就能在之后更改其中的内容了.
这里 *(str2+2)=’W’(准确) str2=”mnbmbmb”(错误)
char a[]=“123456!”;
char *p=“12345!”;
都可以用下标法和地址(指针)法引用数组元素
a[5],*(a+5), p[5],*(p+5)
#include <stdio.h> int main() { char str[] = "I love you"; int count = 0; //用于统计 while (*str++ != '\0') { count++; } printf("总共有%d个字符!\n", count); return 0; }
error: cannot increment value of type 'char [11]'
while(*str++ != '\0')
~~~^
1 error generated.
原因:我们以前学过,C语言的术语lvalue指用于识别或定位一个存储位置的标识符,是地址。rlvalue是值,可以是变量或表达式(注意:左值同时还必须是可改变的)
由于数组名是字符串的领头羊,是个地址常量
,无法被随意改变(进行自增等操作),不是lvalue,报错
修改:添加一个中间地址变量(可改变的)
#include <stdio.h> int main() { char str[] = "I love you"; char *target = str; int count = 0; while (*target++ != '\0') //(1) { count++; } printf("总共有%d个字符!\n", count); return 0; }
- 自增运算符++比取值运算符*优先级高,但后缀运算符有个特性,要在下一条语句中才会显示,就这一次虽然自增但是还是用的原来target值
- 其实我们可以在while()内只有*target,把target++留在{}里面
总共有10个字符!
int *p1[5]; //指针数组
int (*p2)[5]; //数组指针
int *p1[5]
下标 | 0 | 1 | 2 | 3 | 4 |
---|---|---|---|---|---|
元素 | int* | int* | int* | int* | int* |
#include <stdio.h> int main() { int a = 1; int b = 2; int c = 3; int d = 4; int e = 5; int *p1[5] = {&a, &b, &c, &d, &e}; int i; for(i = 0; i < 5; i++) { printf("%d\n", *p1[i]); } return 0; }
1
2
3
4
5
#include <stdio.h> int main() { char *p1[] = { //(1) "让c语言改变世界 -- 小c", "让python改变世界 -- 小p", "让Java改变世界 -- 小j", "让go改变世界 -- 小g", "让vb改变世界 -- 小v", }; int i; for (i = 0; i < 5; i++) { printf("%s\n", p1[i]); //(2) } return 0; }
- 如同整型指针数组,数组中每一个元素是一个指针,存储一串对应元素的地址。整型变量就是&a,字符串变量直接用字符串就行,自己(字符串名)就是字符串第一个字符的地址。
- *p1[]或*p1[5] 都行
- 为什么是p1[i]而不是*p1[i]
- p1[i]:例如p1[0],是指针数组的第一个指针指向第一个字符串的首字符,%s输出会自己往后读后面字符,直到’\0’。
- *p1[i]:例如*p1[1],是指针数组第一个指针指向第一个字符串的首字符,则配合%c只会输出每个字符串第一个字符,(只发现有数字和英文可以,中文不行不知道为啥。)
建议先看第四大块指针和二维数组再看数组指针
#include <stdio.h>
int main()
{
int (*p2)[5] = {1, 2, 3, 4, 5};
int i;
for ( i = 0; i < 5; i++)
{
printf("%d\n", *(p2 + i));
}
return 0;
}
3 warnings generated.
1
21
41
61
81
#include <stdio.h> int main() { int temp[5] = {1, 2, 3, 4, 5}; int (*p2)[5] = temp; //给它赋一个地址 int i; for ( i = 0; i < 5; i++) { printf("%d\n", *(p2 + i)); } return 0; }
2 warnings generated.
-523160112
-523160092
-523160072
-523160052
-523160032
warning: incompatible pointer types initializing 'int (*)[5]' with an expression of type 'int [5]'; take the address with & [-Wincompatible-pointer-types]
#include <stdio.h> int main() { int temp[5] = {1, 2, 3, 4, 5}; int (*p2)[5] = &temp; //(1) int i; for ( i = 0; i < 5; i++) { printf("%d\n", *(*p2 + i)); //(2) printf("%p\n", *p2 + i); } return 0; }
数组指针是真正指向数组的指针
,而不是指向变量。把数组当作一个整体,取出他的地址,赋给数组指针才是数组指针想要的。- (*p2)[5]=&temp,这里有&取地址,然后数组本身就是一个地址,所以这里等于是数组temp[5]里头元素的地址的地址 所以下面打印的时候要两次*
0x7ffeec0065c0
1
0x7ffeec0065c4
2
0x7ffeec0065c8
3
0x7ffeec0065cc
4
0x7ffeec0065d0
5
来自弹幕
[1.]最开始讲到*p = a; 指针p指向了a这个数组第一个元素的地址,那么它指向了数组本身了吗?没有!它指向的是数组的第一个元素!!
[2.]而数组指针指向的是这个数组的本身(数组首元素地址)!!这个数组指针指向的内存空间中,含有元素的地址!
[3.]元素的地址在数组地址指向的内存空间里面!
个人感觉学校里讲这块比小甲鱼清晰,
先上学校的ppt。
小甲鱼所说的数组指针,其实在C语言二维数组中就是行指针(二维数组数组名),只不过跨度(+1时)是跨一行的,是指向行的。
而二维数组中指向元素的是列指针,跨度(+1时)是跨一个元素。
一维数组中,数组名是元素指针。
二维数组中,数组名是数组指针(行指针),比如a,a+1。而(a[0],a[1],a[0]+1,*a+1,*(a+1)+1)等是元素指针(列指针)
以上为个人理解和总结,仅供参考。
以下为小甲鱼课的笔记。
#include <stdio.h>
int main()
{
int array[4][5] = {0};
printf("sizeof int: %lu\n", sizeof(int));
printf("array: %p\n", array);
printf("array + 1: %p\n", array + 1);
return 0;
}
sizeof int: 4
array: 0x7ffee0deb5b0
array + 1: 0x7ffee0deb5c4
#include <stdio.h> int main() { int array[4][5] = {0}; int i, j, k = 0; for (i = 0; i < 4; i++) { for (j = 0; j < 5; j++) { array[i][j] = k++; } } printf("*(array + 1): %p\n", *(array + 1)); printf("array[1]: %p\n", array[1]); printf("&array[1][0]: %p\n", &array[1][0]); printf("**(array+1): %d\n", **(array+1)); return 0; } //二维数组里面其实是放了一维数组,这样一维数组,对于二维数组来说就是元素,就像数值对于一维数组来说是一个元素 //一维里面 数组名如同指针变量的功能存放着第一个元素的地址 //二维数组 也可类似这样看 ,形式是 array【i】存放的也是地址 //二维数组就是指针数组里面有四个指向五个元素数组的数组指针(行指针) //array[1]== &array[1][0]== **(array+1)
*(array + 1): 0x7ffeec53b5e4
array[1]: 0x7ffeec53b5e4
&array[1][0]: 0x7ffeec53b5e4
**(array+1): 5
*的作用:如果是最小跨度就取值,比如取出int。如果不是则缩小跨度,比如数组跨度转为int元素跨度。
#include <stdio.h> int main() { int array[4][5] = {0}; int i, j, k = 0; for (i = 0; i < 4; i++) { for (j = 0; j < 5; j++) { array[i][j] = k++; } } printf("*(*(array+1)+3): %d\n", *(*(array+1)+3)); printf("array[1][3]: %d\n", array[1][3]); return 0; }
*(*(array+1)+3): 8
array[1][3]: 8
梅开二度:
小甲鱼所说的数组指针,其实在C语言二维数组中就是行指针(二维数组数组名),只不过跨度(+1时)是跨一行的,是指向行的。
而二维数组中指向元素的是列指针,跨度(+1时)是跨一个元素。
一维数组中,数组名是元素指针。
二维数组中,数组名是数组指针(行指针),比如a,a+1。而(a[0],a[1],a[0]+1,*a+1,*(a+1)+1)等是元素指针(列指针)
以上为个人理解和总结,仅供参考。
初始化二维数组是可以偷懒的
定义一个数组指针是这样的
#include <stdio.h> int main() { int array[2][3] = {{0, 1, 2}, {3, 4 ,5}}; int (*p)[3] = array; printf("**(p+1):%d\n", **(p+1)); printf("**(array+1):%d\n", **(array+1)); printf("array[1][0]:%d\n",array[1][0] ); printf("**((p+1)+2):%d\n", *(*(p+1)+2)); printf("*(*(array+1)+2):%d\n", *(*(array+1)+2)); printf("array[1][2]:%d\n",array[1][2] ); return 0; }
**(p+1):3
**(array+1):3
array[1][0]:3
**((p+1)+2):5
*(*(array+1)+2):5
array[1][2]:5
void本来是无类型,定义变量时void a;
会报错。但是void指针却很厉害
#include <stdio.h> int main() { int num = 1024; int *pi = # char *ps = "FishC"; void *pv; pv = pi; printf("pi:%p, pv:%p\n", pi, pv); pv = ps; printf("ps:%p, pv:%p\n", ps, pv); return 0; }
pi:0x7ffeeb0c162c, pv:0x7ffeeb0c162c
ps:0x104b41f96, pv:0x104b41f96
#include <stdio.h> int main() { int num = 1024; int *pi = # char *ps = "FishC"; void *pv; pv = pi; printf("pi:%p, pv:%p\n", pi, pv); printf("pv:%d\n", *(int *)pv); //强制转换 pv = ps; printf("ps:%p, pv:%p\n", ps, pv); printf("pv:%s\n", (char *)pv); //%s会自己往下读直到'\0' return 0; }
pi:0x7ffee76a7624, pv:0x7ffee76a7624
pv:1024
ps:0x10855bf7a, pv:0x10855bf7a
pv:FishC
空指针
#define NULL((void *)0)
当你还不知道要将指针初始化什么地址时,请将它初始化NULL;在对指针进行解引用时,先检查该指针是否为NULL。这种策略可以为你今后编写大型程序节省大量的调试时间。防止出现野指针
#include <stdio.h>
int main()
{
int *p1;
int *p2 = NULL;
printf("%d\n", *p1);//野指针
printf("%d\n", *p2);//避免了野指针的危害,程序直接报错
return 0;
}
#include <stdio.h> int main() { int num = 520; int *p = # //指针p存放变量num地址 int **pp = &p; //指针的指针pp存放指针p的地址 printf("num:%d\n", num); //打印num的内容 printf("*p:%d\n", *p); //打印p指向的地址里的内容 printf("**pp:%d\n", **pp);//打印**pp的内容 printf("&p; %p, pp: %p\n", &p, pp); //验证pp中存放的是否是指针p的地址,%p就是把值转化成十六进制输出 printf("&num: %p, p: %p, *pp: %p\n", &num, p, *pp); //验证变量num的地址、指针p存放的内容、指针的指针pp所指的是不是同个东西 return 0; }
num:520
*p:520
**pp:520
&p; 0x7ffeeddcf618, pp: 0x7ffeeddcf618
&num: 0x7ffeeddcf624, p: 0x7ffeeddcf624, *pp: 0x7ffeeddcf624
#include <stdio.h> int main() { char *cBooks[] = { "<C程序设计语言>", "<C专家编程>", "<C和指针>", "<C陷阱和缺陷>", "<C Primer Plus>", "<C语言--从入门到入土>"}; char **byWo; //定义为指向字符指针的指针变量 char **woLoves[4]; //定义一个数组,存放着四个指向指针的指针 int i; byWo = &cBooks[5]; //第五个字符串也是一个指针,它(指针)取地址的值就是指向指针的指针存放的值了 woLoves[0] = &cBooks[0]; //两边要同一个级别,下面同理 woLoves[1] = &cBooks[1]; woLoves[2] = &cBooks[2]; woLoves[3] = &cBooks[3]; printf("wo的图书有: %s\n", *byWo);//%s要的是地址,会从第一个字符(*bywo指向字符串第一个字符)读下去 printf("wo喜欢的图书有: \n"); for (i = 0; i < 4; i++) { printf("%s\n", *woLoves[i]); } return 0; }
wo的图书有: <C语言–从入门到入土>
wo喜欢的图书有:
<C程序设计语言>
<C专家编程>
<C和指针>
<C陷阱和缺陷>
#include <stdio.h> int main() { int array[3][4] = { {0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11}}; int **p = array; int i, j; for (i = 0; i < 3; i++) { for (j =0; j< 4; j++) { printf("%2d", *(*(p+i)+j)); } printf("\n"); } return 0; }
系统报错
int **p = array;
1 warning generated.
#include <stdio.h> int main() { int array[3][4] = { {0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11}}; //int **p = array; 注释这部分 int i, j; for (i = 0; i < 3; i++) { for (j =0; j< 4; j++) { printf("%2d", *(*(array+i)+j)); //p改为array } printf("\n"); } return 0; }
0 1 2 3
4 5 6 7
8 91011
printf("p: %p, array: %p\n", p, array);
printf("p+1:%p, array+1:%p\n", p+1, array+1);
p: 0x7ffeeb99f5f0, array: 0x7ffeeb99f5f0
p+1:0x7ffeeb99f5f8, array+1:0x7ffeeb99f600
int **p = array;
,编译器没有把指向的array理解为一个二维数组,只把array理解给指向指针的指针赋的一个地址。#include <stdio.h> int main() { int array[3][4] = { {0, 1, 2, 3}, {4, 5, 6, 7}, {8, 9, 10, 11}}; int (*p)[4] = array; //(1) int i, j; 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 91011
(1)
int (*p)[4] = array
,p指向的仍然是一维数组,但着重考虑的是跨度问题,p的跨度是4*(sizeof int),四列。array是二维数组的首地址赋值给了p,那p+1就是第二行,再进行每一行的索引。
(2)用数组指针来访问二维数组,我们利用的就是p指向的是4个元素的一维数组,所以p的跨度就是4*4=16字节
。而二维数组array+1,就是加上一个元素,也就是加上一行(因为二维数组是一位数组线性扩充)
(3)二者跨度一样所以可以成功
#define PRICE 520
#define A ‘a’
const int price = 520
const char a = ‘a’
#include <stdio.h>
int main()
{
int num = 520;
const int cnum = 880;
const int *pc = &cnum;
printf("cnum: %d, &cnum: %p\n", cnum, &cnum);
printf("*pc: %d, pc: %p\n", *pc, pc);
return 0;
}
cnum: 880, &cnum: 0x7ffeede46634
*pc: 880, pc: 0x7ffeede46634
*pc=1024
这样改变指针指向的内容,因为是常量,会被判定error,自行尝试。#include <stdio.h> int main() { int num = 520; const int cnum = 880; const int *pc = &cnum; // printf("cnum: %d, &cnum: %p\n", cnum, &cnum); // printf("*pc: %d, pc: %p\n", *pc, pc); pc = # printf("num: %d, %num: %p\n", num, &num); printf("*pc: %d, pc: %p\n", *pc, pc); return 0; }
num: 520, num: 0x7ffee7794634
*pc: 520, pc: 0x7ffee7794634
*pc = 1024
会报错。但是num = 1024
在打印*pc不会报错而且可以正常运行(1)指针可以修改为指向不同的常量
(2)指针可以修改为指向不同的变量(1和2说明指针本身可以修改)
(3)可以通过解引用来读取指针指向的数据
(4)不可以通过解引用修改指针指向的数据
#include <stdio.h> int main() { int num = 520; const int cnum = 880; // 指向常量的指针 int * const p = # // 指针常量 *p = 1024; //指针常量, 指向的量可以变,可运行 printf("*p: %d\n", *p); p = &cnum; //指针常量,存储的地址不可以被修改, 不运行 printf("*p: %d\n", *p); return 0; }
累了,爷不学了
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。