C语言:指针进阶(下)

发布于:2025-06-12 ⋅ 阅读:(17) ⋅ 点赞:(0)

目录

一、strlen和strlen

sizeof

strlen

区别

二、数组和指针题目

一维数组

字符数组

1.1

1.2

2.1

2.2

3.1

3.2

二维数据

三、指针运算题目

1

2

3

4

5

6

7


一、strlen和strlen

sizeof

  • 类型运算符(编译时求值)

  • 功能:计算变量或类型所占的内存字节数

  • 参数:可以是类型名或变量名

  • 计算时机编译时确定

  • 包含内容:计算整个数组/结构体的大小(包括填充字节)

  • 对指针:返回指针本身的大小(通常4或8字节)

  • 对字符串包括末尾的'\0'空字符

strlen

  • 类型库函数(运行时计算)

  • 功能:计算字符串的长度(不包括结尾的'\0')

  • 参数必须是字符串(以'\0'结尾的字符数组)

  • 计算时机运行时遍历字符串直到遇到'\0'

  • 包含内容:只计算'\0'前的字符数

  • 对指针:如果指向有效字符串则返回字符串长度

  • 对字符串不包括末尾的'\0'

区别

  1. 计算时机:sizeof编译时,strlen运行时

  2. 包含内容:sizeof包含所有内存,strlen只计字符

  3. 参数类型:sizeof接受类型/变量,strlen只接受字符串

  4. 效率:sizeof无运行时开销,strlen需遍历字符串

详见博客:C语言:strlen与sizeof详解_4 *sizeof(int)中的*什么意思-CSDN博客

二、数组和指针题目

注意:1、sizeof(数组名),数组名表示整个数组。计算的是整个数组的大小,单位是字节

           2、&数组名,数组名表示整个数组。取出的是整个数组的地址

           除此之外,所有的数组名都是首元素地址。

一维数组

int main()
{
	int a[] = { 1,2,3,4 };
	printf("%d\n", sizeof(a));
	printf("%d\n", sizeof(a + 0));
	printf("%d\n", sizeof(*a));
	printf("%d\n", sizeof(a + 1));
	printf("%d\n", sizeof(a[1]));
	printf("%d\n", sizeof(&a));
	printf("%d\n", sizeof(*&a));
	printf("%d\n", sizeof(&a + 1));
	printf("%d\n", sizeof(&a[0]));
	printf("%d\n", sizeof(&a[0] + 1));
	return 0;
}

打印结果为(64位): 

1. sizeof(a)

a是数组名,单独出现在sizeof中,代表整个数组,输出16

2. sizeof(a + 0)

a是数组首元素的指针(即int*类型),所以a+0是指针(指向数组的第一个元素),输出8

3. sizeof(*a)

a是数组名,单独出现时代表数组首元素的地址,但是这里,*a表示取首元素的值,类型是int,所以sizeof(*a)就是sizeof(int),输出4

4.  sizeof(a + 1)

a为指针(指向首元素),a+1指向数组的第二个元素(即a[1]的地址),所以a+1是一个指针,输出8

5.  sizeof(a[1])

a[1]是数组的第二个元素,类型为int,输出4

6. sizeof(&a)

&a表示取整个数组的地址,它是一个数组指针(类型为int(*)[4]),输出8

7.sizeof(*&a)

&a是整个数组的地址,类型为int(*)[4]。然后对它解引用(*&a),得到整个数组,输出16

8.  sizeof(&a + 1)

&a是数组的地址,类型是int(*)[4]。&a+1表示跳过了整个数组,指向数组之后的位置(但仍然是一个指针),输出8

9. sizeof(&a[0])

&a[0]是取数组第一个元素的地址,即一个int*类型的指针,输出8

10. sizeof(&a[0] + 1)

&a[0]是第一个元素的地址,加1后是第二个元素的地址(即&a[1]),是int*指针,输出8

字符数组

1.1

int main()
{
	char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", sizeof(arr));
	printf("%d\n", sizeof(arr + 0));
	printf("%d\n", sizeof(*arr));
	printf("%d\n", sizeof(arr[1]));
	printf("%d\n", sizeof(&arr));
	printf("%d\n", sizeof(&arr + 1));
	printf("%d\n", sizeof(&arr[0] + 1));
	return 0;
}

打印结果为(64位):

1. sizeof(arr)

arr代表整个数组,数组有6个char类型元素,

输出6。

2. sizeof(arr+0)

arr作为数组名,这里会退化为指向首元素的指针(即char*类型),arr+0仍然是一个指向首元素的指针,

输出8。

3. sizeof(*arr)

arr退化为首元素的指针,*arr就是首元素,类型是char,

输出1。

4. sizeof(arr[1])

arr[1]是数组的第二个元素,即'b',类型为char,

输出1。

5. sizeof(&arr)

&arr是取整个数组的地址,类型是char (*)[6](指向长度为6的字符数组的指针),

输出8。

6. sizeof(&arr+1)

&arr是指向数组的指针,加1则跳过整个数组,指向数组之后的位置,类型还是char (*)[6],输出8。

7. sizeof(&arr[0]+1)

&arr[0]是第一个元素的地址,类型是char*,加1指向第二个元素(即arr[1]),所以还是一个char*指针,

输出8。

1.2

int main()
{
	char arr[] = { 'a','b','c','d','e','f' };
	printf("%d\n", strlen(arr));
	printf("%d\n", strlen(arr + 0));
	printf("%d\n", strlen(*arr));
	printf("%d\n", strlen(arr[1]));
	printf("%d\n", strlen(&arr));
	printf("%d\n", strlen(&arr + 1));
	printf("%d\n", strlen(&arr[0] + 1));
	return 0;
}

该代码无法通过编译运行

1.strlen(arr)

找不到'\0',

输出随机值。
2.strlen(arr + 0)

找不到'\0',

输出随机值。
3.strlen(*arr)

*arr是arr[0],值为'a'(类型char,ASCII值97)。
strlen期望指针,但传递了char(整数),类型不匹配。编译器通常报错(警告或错误)。
如果编译通过,运行时会将97解释为内存地址,访问无效地址(如0x61),导致段错误。
输出:编译错误或运行时崩溃(无法输出整数)。


4.strlen(arr[1])

arr[1]是'b'(类型char,ASCII值98)。
strlen期望指针,但传递了char(整数),类型不匹配。编译器通常报错(警告或错误)。
如果编译通过,运行时会将98解释为内存地址,访问无效地址(如0x62),导致段错误。
输出:编译错误或运行时崩溃(无法输出整数)。
5.strlen(&arr)

找不到'\0',

输出随机值。
6.strlen(&arr + 1)

找不到'\0',

输出随机值。
7.strlen(&arr[0] + 1)

找不到'\0',

输出随机值。

同时注意:strlen接收的参数类型为const char *,所以这里会直接报错:

2.1

int main()
{
	char arr[] = "abcdef";
	printf("%d\n", sizeof(arr));
	printf("%d\n", sizeof(arr + 0));
	printf("%d\n", sizeof(*arr));
	printf("%d\n", sizeof(arr[1]));
	printf("%d\n", sizeof(&arr));
	printf("%d\n", sizeof(&arr + 1));
	printf("%d\n", sizeof(&arr[0] + 1));
}

打印结果为(64位):

1.sizeof(arr)

arr是数组名,在sizeof中单独使用,表示整个数组的大小。数组有7个字符(包括'\0'),

输出7

2.sizeof(arr + 0)

这里,arr + 0:数组名arr在表达式中(不是单独在sizeof中)会退化为指向首元素的指针,所以arr+0是一个指针(指向首元素地址),

输出8

3.sizeof(*arr)

arr退化为指针,然后解引用得到第一个元素,即字符a,

输出1

4.sizeof(arr[1]))

arr[1]是数组的第二个元素,即字符'b',

输出1

5.sizeof(&arr)

&arr:取整个数组的地址,这是一个指向数组的指针(类型为char(*)[7]),

输出8

6.sizeof(&arr + 1)

&arr是整个数组的地址,加1会跳过整个数组,指向数组末尾之后的位置。但类型仍然是char(*)[7],所以它还是一个指针,

输出8

7.sizeof(&arr[0] + 1)

&arr[0]是第一个元素的地址(char*类型),然后加1得到第二个元素的地址(即指向'b'的指针),

输出8

2.2

int main()
{
	char arr[] = "abcdef";
	printf("%d\n", strlen(arr));
	printf("%d\n", strlen(arr + 0));
	printf("%d\n", strlen(*arr));
	printf("%d\n", strlen(arr[1]));
	printf("%d\n", strlen(&arr));
	printf("%d\n", strlen(&arr + 1));
	printf("%d\n", strlen(&arr[0] + 1));
}

 该代码无法通过编译运行

1.strlen(arr)

arr是数组首元素地址,统计\0之前出现的字符个数,输出6。
2.strlen(arr + 0)

arr+0是数组首元素地址,统计\0之前出现的字符个数,输出6。
3.strlen(*arr)

*arr是arr[0],值为'a'(类型char,ASCII值97)。
strlen期望指针,但传递了char(整数),类型不匹配。编译器通常报错(警告或错误)。
如果编译通过,运行时会将97解释为内存地址,访问无效地址(如0x61),导致段错误。
输出:编译错误或运行时崩溃(无法输出整数)。
4.strlen(arr[1])

arr[1]是'b'(类型char,ASCII值98)。
strlen期望指针,但传递了char(整数),类型不匹配。编译器通常报错(警告或错误)。
如果编译通过,运行时会将98解释为内存地址,访问无效地址(如0x62),导致段错误。
输出:编译错误或运行时崩溃(无法输出整数)。
5.strlen(&arr)

&arr 是整个数组的地址。虽然它的值(即起始地址)与`arr`相同,但类型是`char(*)[7]`(指向长度为7的字符数组的指针)。但是,当传递给strlen时,它会被转换为const char*,因此仍然指向数组的起始位置。

因此,从数组起始位置开始,直到遇到空字符,长度为6

输出:6
6.strlen(&arr + 1)

&arr表示数组地址,+1跳过了整个数组,找不到'\0',

输出随机值。
7.strlen(&arr[0] + 1)

&arr[0] 是指向第一个元素的指针(即字符'a'的地址),然后加1,指向第二个元素(即字符'b'的地址)。

从字符'b'开始,字符串变为"bcdef",长度为5。

输出:5

3.1

int main()
{
	const char* p = "abcdef";
	printf("%d\n", sizeof(p));
	printf("%d\n", sizeof(p + 1));
	printf("%d\n", sizeof(*p));
	printf("%d\n", sizeof(p[0]));
	printf("%d\n", sizeof(&p));
	printf("%d\n", sizeof(&p + 1));
	printf("%d\n", sizeof(&p[0] + 1));
}

 打印结果为(64位):

1. sizeof(p):

p是一个指针,

输出8

2. sizeof(p + 1):

p + 1是指针运算,结果仍然是一个指针(指向字符串的第二个字符,即 'b' 的地址)。

输出8

3. sizeof(*p):

*p是解引用指针,得到的是字符a,类型是const char。

输出1

4. sizeof(p[0]):

p[0]等价于*(p+0),即*p,

输出1

5. sizeof(&p):

&p是取指针p的地址,因此得到的是一个指向指针的指针(即const char**类型)。

输出8

6. sizeof(&p + 1):

&p是指向p的指针,类型为const char**(实际上是指向const char*的指针,所以其类型为 `const char**`,&p+1就是指向下一个位置,但类型不变)。

输出8

7. sizeof(&p[0] + 1):

p[0]是字符 'a',所以&p[0]是p本身(即指向 'a' 的指针,类型为const char*)。

&p[0] + 1相当于p + 1,即指向 'b' 的指针,类型仍为const char*。

输出8

3.2

int main()
{
	const char* p = "abcdef";
	printf("%d\n", strlen(p));
	printf("%d\n", strlen(p + 1));
	printf("%d\n", strlen(*p));
	printf("%d\n", strlen(p[0]));
	printf("%d\n", strlen(&p));
	printf("%d\n", strlen(&p + 1));
	printf("%d\n", strlen(&p[0] + 1));
	return 0;
}

 该代码无法通过编译运行 

1. strlen(p)

p是一个指向字符串字面量abcdef的指针。字符串abcdef有6个字符,以\0结尾,

输出6。

2. strlen(p + 1)

p + 1指向p所指向地址的下一个字符。p指向a,所以p + 1指向b。

输出5。

3. strlen(*p)

*p是解引用指针p,所以它是字符'a'。strlen期望一个指针,但这里传递的是一个字符(char类型)。在C语言中,字符会被提升为整数,但这不是一个有效的指针。这会导致未定义行为,因为strlen会尝试将 `'a'` 的ASCII值(97)解释为地址。

输出:编译错误或运行时崩溃(无法输出整数)。

4. strlen(p[0])

同上,

输出:编译错误或运行时崩溃(无法输出整数)。

5. strlen(&p)

&p是取指针p本身的地址。p是一个指向字符的指针,所以&p是一个指向指针的指针(char**类型),&p指向存储指针p的内存地址,它不是一个以`'\0'`结尾的字符串,找不到'\0',

输出随机值

6. strlen(&p + 1)

&p + 1:&p是p的地址,类型是char**。由于p是一个指针变量,&p + 1指向p之后的内存地址(偏移一个指针大小),它不是一个以`'\0'`结尾的字符串,找不到'\0'。

输出随机值

7. strlen(&p[0] + 1)

p[0]是'a',所以&p[0]是取p[0]的地址,即p本身,因为p[0]等同于*(p + 0),所以&p[0]就是p。

因此,&p[0] + 1等同于p + 1,和第二个调用相同。

输出5

二维数据

int main()
{
	int a[3][4] = { 0 };
	printf("%d\n", sizeof(a));
	printf("%d\n", sizeof(a[0][0]));
	printf("%d\n", sizeof(a[0]));
	printf("%d\n", sizeof(a[0] + 1));
	printf("%d\n", sizeof(*(a[0] + 1)));
	printf("%d\n", sizeof(a + 1));
	printf("%d\n", sizeof(*(a + 1)));
	printf("%d\n", sizeof(&a[0] + 1));
	printf("%d\n", sizeof(*(&a[0] + 1)));
	printf("%d\n", sizeof(*a));
	printf("%d\n", sizeof(a[3]));
}

 打印结果为(64位):

1. sizeof(a)

a 是整个二维数组,有3行4列,所以总元素个数为3*4=12,每个元素是int(4字节),所以总大小为12*4=48字节。

输出:48

2. sizeof(a[0][0])

a[0][0] 是数组的第一个元素,类型是int,所以大小为4字节。

输出:4

3. sizeof(a[0])

a[0] 是二维数组的第一行,它是一个一维数组,包含4个int。所以大小是4*4=16字节。

注意:a[0]的类型是int[4]。

输出:16

4. sizeof(a[0] + 1)

a[0] 是第一行的数组名,在这里不会单独作为数组大小使用,而是发生了“退化”(decay)成为指向该行首元素的指针(即int*),所以a[0]+1是指向第一行第二个元素的指针(地址)。在64位系统中,指针的大小是8字节。

注意:这里不是sizeof(整个数组),所以数组名退化为指针。

输出:8

5. sizeof(*(a[0] + 1))

a[0]+1是指向第一行第二个元素的指针(即&a[0][1]),解引用后得到a[0][1],类型是int,所以大小是4字节。

输出:4

6. sizeof(a + 1)

a是二维数组名,在表达式中使用(除了sizeof和&)会退化为指向第一行的指针(即int(*)[4])。所以a+1是指向第二行的指针(即&a[1])。它仍然是指针,所以大小为8字节。

输出:8

7. sizeof(*(a + 1))

a+1是指向第二行的指针,解引用后得到第二行的整个数组(即a[1]),类型是int[4],所以大小为4*4=16字节。

输出:16

8. sizeof(&a[0] + 1)

&a[0] 是取第一行的地址(类型为int(*)[4]),加1后指向第二行的地址(即&a[1]),所以这是一个指针,大小为8字节。

输出:8

9. sizeof(*(&a[0] + 1))

*(&a[0] + 1) 等价于a[1],即第二行(一个包含4个int的数组),所以大小为16字节。

输出:16

10. sizeof(*a)

a 是二维数组名,在表达式中退化为指向第一行的指针(即int(*)[4]),解引用后得到第一行(即a[0]),类型为int[4],大小为16字节。

注意:*a 等价于 a[0]。

输出:16

11. sizeof(a[3])

a[3] 看起来是访问第四行(越界),但sizeof是在编译时求值,它不会实际访问这个元素,而是根据类型来确定大小。a[3]的类型是int[4](因为a是3行4列,但类型是确定的),所以大小是16字节。

注意:sizeof不会计算表达式的值,只关心类型。虽然a[3]越界,但类型是明确的。

输出:16

注:

这里s没有被赋值,就是因为sizeof在编译时求值时,不会实际访问这个元素,而运算求值是在链接之后。

三、指针运算题目

1

int main()
{
	int a[5] = { 1, 2, 3, 4, 5 };
	int* ptr = (int*)(&a + 1);
	printf("%d,%d", *(a + 1), *(ptr - 1));
	return 0;
}

打印2,5。

&a+1:跳过整个数组,指向a[5](并不存在,但地址是合法的),随后ptr-1指向a[4],即5。

2

struct Test
{
	int Num;
	char* pcName;
	short sDate;
	char cha[2];
	short sBa[4];
}*p;
int main()
{
	printf("%p\n", p + 0x1);
	printf("%p\n", (unsigned long)p + 0x1);
	printf("%p\n", (unsigned int*)p + 0x1);
	return 0;
}

假设p的值为0x100000,计算结构体大小为32字节(64位),

1. printf("%p\n", p + 0x1);

指针p加上1,实际上是加上1个结构体的大小,即32字节(0x20)。所以结果应该是 p + 0x20(注意,这里0x1代表1个单位,单位是结构体的大小)。但是,输出是%p,所以会以指针形式输出。输出0x100020(因为32的十六进制是0x20)。

2. printf("%p\n", (unsigned long)p + 0x1);

unsigned long是一个整数类型,所以这里就是简单的整数加法。p的值转换后的整数也是0x100000,加1就是0x100001。

3. printf("%p\n", (unsigned int*)p + 0x1);

这里将p转换为unsigned int*指针,然后加1。unsigned int的大小是4字节。所以加1就是增加4个字节。假设p是0x100000,那么转换后指向0x100000,加1后指向0x100004。

3

int main()
{
	int a[4] = { 1,2,3,4 };
	int* ptr1 = (int*)(&a + 1);
	int* ptr2 = (int*)((int)a + 1);
	printf("%x,%x", ptr1[-1], *ptr2);
	return 0;
}

结果为4,未知值(200000)。(%x表示以十六进制形式输出整数)

1. int* ptr1 = (int*)(&a + 1);

&a 是整个数组的地址,其类型为 int(*)[4]。因此,&a + 1 将会跳过整个数组(即4个int,16字节),所以ptr1指向数组a最后一个元素(4)的下一个位置,即数组a之后的位置。然后将这个地址强制转换为int*类型,赋值给ptr1。那么,ptr1[-1] 相当于 *(ptr1-1)。由于ptr1指向数组a之后的位置,所以ptr1-1指向数组a的最后一个元素,即4。

2. int* ptr2 = (int*)((int)a + 1);

a是数组首元素的地址,类型是int*。但是这里将a直接转换为int,所以a+1就是数值上的+1。然后,再将(int)a + 1 强制类型转化为(int*),此时ptr指向原地址偏移1字节后的位置,*ptr读取从该偏移位置开始的4 字节的数据。由于内存中整数可能按小端序存储(低位字节在前),这会导致读取到跨越两个元素的部分字节,结果通常是一个未知的值(200000)。

4

int main()
{
	int a[3][2] = { (0, 1), (2, 3), (4, 5) };
	int* p;
	p = a[0];
	printf("%d", p[0]);
	return 0;
}

结果为1。

代码中使用了圆括号(0,1),为逗号表达式,实际赋值的是逗号右侧的值。

(0, 1):返回1。

(2, 3):返回3。

(4, 5):返回5。

实际上该数组被初始化为{1,3,5,0,0,0},故a[0]是数组为{1,3}的地址,p[0]就是1。

5

int main() 
{
    int a[5][5];
    int(*p)[4];
    p = a;
    printf( "%p,%d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
    return 0;
}

指针p是数组指针,指向一个包含4个整数的数组,此时p+1跳过4个整型16个字节。a表示首元素地址,类型为int(*)[5],强制赋值后,p就指向了该二维数组的首元素地址。

 -4在内存中为10000000 00000000 00000000 00000100(补码)

原码为11111111 11111111 11111111 11111100,即为0xff ff ff ff ff ff ff fc

 打印结果为(64位):

6

int main()
{
	char* a[] = { "work","at","alibaba" };
	char** pa = a;
	pa++;
	printf("%s\n", *pa);
}

打印结果为at。

创建了一个包含 3 个元素的指针数组a每个元素都是指向字符串常量的指针,其内存布局为:

a[0] → "work\0"

a[1] → "at\0"

a[2] → "alibaba\0"

而pa是一个指向指针的指针,初始指向a[0]的地址,pa++后指向a[1]的地址,所以结果为at

7

int main()
{
	char* c[] = { "ENTER","NEW","POINT","FIRST" };
	char** cp[] = { c + 3,c + 2,c + 1,c };
	char*** cpp = cp;
	printf("%s\n", **++cpp);
	printf("%s\n", *-- * ++cpp + 3);
	printf("%s\n", *cpp[-2] + 3);
	printf("%s\n", cpp[-1][-1] + 1);
	return 0;
}

打印结果为:

本题的关键在于把图画出来:

1.**++cpp

++cpp:cpp指向cp[1]
*cpp:获取cp[1],即c+2
**cpp:获取*(c+2),即"POINT"
输出:POINT

2.*-- * ++cpp + 3

++cpp:cpp此时 指向cp[2]
*cpp:获取cp[2],即c+1
--*cpp:cp[2]修改为c
*--*cpp:获取*c,即"ENTER"
*--*cpp + 3:从"ENTER"偏移 3,得到"ER"
输出:ER
3.*cpp[-2] + 3

cpp[-2]:cp[0],即c+3
*cpp[-2]:*(c+3),即"FIRST"
*cpp[-2] + 3:从"FIRST"偏移 3,得到"ST"
输出:ST
4.cpp[-1][-1] + 1

cpp[-1]:cp[1],即c+2
cpp[-1][-1]:*(c+1),即"NEW"
cpp[-1][-1] + 1:从"NEW"偏移 1,得到"EW"
输出:EW

以上就是一些关于指针进阶的题目,感谢阅读。


网站公告

今日签到

点亮在社区的每一天
去签到