C语言:深入了解指针4(超级详细)

news2025/2/9 4:56:32

看之前必须得掌握有一定指针的知识,不然会看不懂,如果有不懂的可以看我博客 指针1,指针2,指针3 这三个讲了指针全部的基础知识超级详细,这篇只要是讲一些指针练习题也是非常详细

1. sizeof和strlen的对⽐

1. 基本定义和用途

  • sizeofsizeof 是一个运算符,不是函数。它用于计算数据类型或变量所占用的内存字节数。这个计算是在编译时完成的,与程序运行时的数据内容无关。
  • strlenstrlen 是一个标准库函数,定义在 <string.h>(C)或 <cstring>(C++)头文件中。它用于计算以 '\0' 结尾的字符串的实际长度,即字符串中字符的个数(不包括字符串结束符 '\0'),这个计算是在程序运行时进行的。

2. 语法和使用示例

#include <stdio.h>

#include <string.h>

int main() 

      char str[] = "Hello";

      // 使用 sizeof 计算数组占用的内存字节数

      size_t size = sizeof(str); 

     // 使用 strlen 计算字符串的实际长度 

     size_t length = strlen(str); 

     printf("sizeof(str) = %zu\n", size); 

     printf("strlen(str) = %zu\n", length); 

      return 0;

}

输出结果:

sizeof(str) = 6 

strlen(str) = 5

在这个例子中,sizeof(str) 返回 6,因为数组 str 包含 5 个字符 'H''e''l''l''o' 以及一个字符串结束符 '\0',每个字符占 1 字节,所以总共 6 字节。而 strlen(str) 返回 5,因为它只计算字符串中实际的字符个数,不包括 '\0'

3. 对不同数据类型的处理

  • sizeof
    • 对于基本数据类型,如 intchardouble 等,sizeof 返回该数据类型在当前系统中占用的字节数。例如,在 32 位系统中,sizeof(int) 通常返回 4,sizeof(char) 返回 1。
    • 对于数组,sizeof 返回整个数组占用的内存字节数。
    • 对于指针,sizeof 返回指针变量本身占用的内存字节数,而不是指针所指向的对象的大小。例如,在 32 位系统中,所有指针类型的 sizeof 结果通常都是 4 字节,在 64 位系统中通常是 8 字节。

#include <stdio.h>

int main() 

    int arr[10]; 

    int *ptr = arr;

    printf("sizeof(arr) = %zu\n", sizeof(arr));

    printf("sizeof(ptr) = %zu\n", sizeof(ptr)); 

    return 0;

}

  • strlenstrlen 只能用于以 '\0' 结尾的字符串。如果传递给 strlen 的参数不是一个有效的以 '\0' 结尾的字符串,会导致未定义行为,因为 strlen 会一直向后查找 '\0',直到找到为止。

4. 性能差异

  • sizeof:由于 sizeof 是在编译时计算的,不会产生运行时开销,因此性能非常高。
  • strlenstrlen 需要在运行时遍历字符串,直到找到 '\0' 为止,因此其时间复杂度为 ,其中  是字符串的长度。当处理长字符串时,strlen 的性能会受到一定影响。

5. 总结

  • 如果需要知道数据类型或变量占用的内存大小,应该使用 sizeof
  • 如果需要知道以 '\0' 结尾的字符串的实际长度,应该使用 strlen

  2. 数组和指针笔试题解析

     通过上面对strlen和sizeof的了解,结合我之前发的指针1和指针2,指针3的知识,我们来做一些综合性的题目。

2.1 ⼀维数组

认真思考哦

#include<stdio.h>
int main()
{
    int a[] = { 1,2,3,4 };

    // 1. sizeof(a)
    printf("%d\n", sizeof(a));

    // 2. sizeof(a + 0)
    printf("%d\n", sizeof(a + 0));

    // 3. sizeof(*a)
    printf("%d\n", sizeof(*a));

    // 4. sizeof(a + 1)
    printf("%d\n", sizeof(a + 1));

    // 5. sizeof(a[1])
    printf("%d\n", sizeof(a[1]));

    // 6. sizeof(&a)
    printf("%d\n", sizeof(&a));

    // 7. sizeof(*&a)
    printf("%d\n", sizeof(*&a));

    // 8. sizeof(&a + 1)
    printf("%d\n", sizeof(&a + 1));

    // 9. sizeof(&a[0])
    printf("%d\n", sizeof(&a[0]));

    // 10. sizeof(&a[0] + 1)
    printf("%d\n", sizeof(&a[0] + 1));

    return 0;
}

我们接下来来分析一下:

我们来回顾一下我之前提到过的:

数组名是数组首元素的地址
但是有2个例外:
1. sizeof(数组名),数组名单独放在括号里。
2. &数组名  

1. sizeof(a)

  • a 是一个包含 4 个 int 类型元素的数组。sizeof 运算符作用于数组名时,返回整个数组占用的内存字节数。
  • 每个 int 类型占 4 个字节,数组有 4 个元素,所以 sizeof(a) 的结果是 4 * 4 = 16 字节。

2. sizeof(a + 0)

  • 根据数组名的特性,当数组名出现在表达式中(除了作为 sizeof 或 & 运算符的操作数),它会隐式转换为指向数组首元素的指针(int *类型)。
  • a + 0 等价于指向数组首元素的指针,指针在 32 位系统下占 4 个字节,在 64 位系统下占 8 个字节。所以 sizeof(a + 0) 的结果是指针的大小。

3. sizeof(*a)

  • a 隐式转换为指向数组首元素的指针,*a 表示解引用该指针,得到数组的首元素,即 a[0]
  • a[0] 是 int 类型,int 类型占 4 个字节,所以 sizeof(*a) 的结果是 4 字节。

4. sizeof(a + 1)

  • a 隐式转换为指向数组首元素的指针,a + 1 是指向数组第二个元素的指针(int *类型)。
  • 指针的大小在 32 位系统下是 4 字节,在 64 位系统下是 8 字节,所以 sizeof(a + 1) 的结果是指针的大小。

5. sizeof(a[1])

  • a[1] 是数组的第二个元素,类型为 int
  • int 类型占 4 个字节,所以 sizeof(a[1]) 的结果是 4 字节。

6. sizeof(&a)

  • &a 是一个指向整个数组的指针(int (*) [4]类型),虽然它和指向数组首元素的指针在数值上可能相同,但类型不同。
  • 指针的大小在 32 位系统下是 4 字节,在 64 位系统下是 8 字节,所以 sizeof(&a) 的结果是指针的大小。

7. sizeof(*&a)

  • &a 是指向整个数组的指针,*&a 等价于 a,也就是整个数组。
  • 整个数组包含 4 个 int 类型元素,每个 int 占 4 个字节,所以 sizeof(*&a) 的结果是 4 * 4 = 16 字节。

8. sizeof(&a + 1)

  • &a 是指向整个数组的指针(int (*) [4]类型),&a + 1 是指向下一个与 a 同类型数组的指针。
  • 指针的大小在 32 位系统下是 4 字节,在 64 位系统下是 8 字节,所以 sizeof(&a + 1) 的结果是指针的大小。

9. sizeof(&a[0])

  • &a[0] 是指向数组首元素的指针(int *类型)。
  • 指针的大小在 32 位系统下是 4 字节,在 64 位系统下是 8 字节,所以 sizeof(&a[0]) 的结果是指针的大小。

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

  • &a[0] 是指向数组首元素的指针(int *类型),&a[0] + 1 是指向数组第二个元素的指针(int *类型)。
  • 指针的大小在 32 位系统下是 4 字节,在 64 位系统下是 8 字节,所以 sizeof(&a[0] + 1) 的结果是指针的大小。

2.2 字符数组

接下来我们来几组字符数组来练习练习一下吧

代码1:

#include <stdio.h>

int main()
{
    char arr[] = { 'a','b','c','d','e','f' };

    // 1. sizeof(arr)
    printf("%d\n", sizeof(arr));

    // 2. sizeof(arr + 0)
    printf("%d\n", sizeof(arr + 0));

    // 3. sizeof(*arr)
    printf("%d\n", sizeof(*arr));

    // 4. sizeof(arr[1])
    printf("%d\n", sizeof(arr[1]));

    // 5. sizeof(&arr)
    printf("%d\n", sizeof(&arr));

    // 6. sizeof(&arr + 1)
    printf("%d\n", sizeof(&arr + 1));

    // 7. sizeof(&arr[0] + 1)
    printf("%d\n", sizeof(&arr[0] + 1));

    return 0;
}

接下来我们来详细的分析一下吧:

我们来回顾一下我之前提到过的:

数组名是数组首元素的地址
但是有2个例外:
1. sizeof(数组名),数组名单独放在括号里。
2. &数组名 

1. sizeof(arr)
  • arr 是一个字符数组,当 sizeof 运算符作用于数组名时,它返回整个数组所占用的内存字节数。
  • 该数组包含 6 个 char 类型的元素,每个 char 类型通常占用 1 个字节,所以 sizeof(arr) 的结果是 6 * 1 = 6
2. sizeof(arr + 0)
  • 当数组名 arr 出现在表达式中(除了作为 sizeof 或 & 运算符的操作数)时,它会隐式转换为指向数组首元素的指针(char*类型),即 arr 退化为 char * 类型的指针。
  • arr + 0 仍然是指向数组首元素的指针,sizeof 计算的是指针的大小。在 32 位系统中,指针大小通常为 4 字节;在 64 位系统中,指针大小通常为 8 字节。
3. sizeof(*arr)
  • 由于 arr 退化为指向数组首元素的指针,*arr 表示对该指针进行解引用,得到数组的首元素 arr[0]
  • arr[0] 是 char 类型,char 类型占用 1 个字节,所以 sizeof(*arr) 的结果是 1。
4. sizeof(arr[1])
  • arr[1] 是数组的第二个元素,其类型为 char
  • 因此,sizeof(arr[1]) 的结果同样是 1 字节。
5. sizeof(&arr)
  • &arr 是一个指向整个数组的指针,其类型为 char (*)[6],表示指向包含 6 个 char 元素的数组的指针。
  • 但无论指针指向何种类型的对象,sizeof 计算的都是指针本身的大小。所以在 32 位系统中为 4 字节,在 64 位系统中为 8 字节。
6. sizeof(&arr + 1)
  • &arr 是指向整个数组的指针(char (*)[6]),&arr + 1 表示对该指针进行偏移,使其指向下一个与 arr 同类型的数组的起始位置。
  • 它仍然是一个指针,所以 sizeof(&arr + 1) 的结果和 sizeof(&arr) 一样,在 32 位系统中为 4 字节,在 64 位系统中为 8 字节。
7. sizeof(&arr[0] + 1)
  • &arr[0] 是数组首元素的地址,类型为 char *
  • &arr[0] + 1 是指向数组第二个元素的指针,同样是 char * 类型。
  • 所以 sizeof(&arr[0] + 1) 计算的是指针的大小,在 32 位系统中为 4 字节,在 64 位系统中为 8 字节。

代码2:

#include <stdio.h>
#include <string.h>

int main()
{
    char arr[] = { 'a','b','c','d','e','f' };

    // 1. strlen(arr)
    printf("%d\n", strlen(arr));

    // 2. strlen(arr + 0)
    printf("%d\n", strlen(arr + 0));

    // 3. strlen(*arr)
    printf("%d\n", strlen(*arr));

    // 4. strlen(arr[1])
    printf("%d\n", strlen(arr[1]));

    // 5. strlen(&arr)
    printf("%d\n", strlen(&arr));

    // 6. strlen(&arr + 1)
    printf("%d\n", strlen(&arr + 1));

    // 7. strlen(&arr[0] + 1)
    printf("%d\n", strlen(&arr[0] + 1));

    return 0;
}

 接下来我们来详细的分析一下吧:

我们来回顾一下我之前提到过的:

数组名是数组首元素的地址
但是有2个例外:
1. sizeof(数组名),数组名单独放在括号里。
2. &数组名 

1. strlen(arr)
  • arr 是一个字符数组,但是该数组初始化时没有包含字符串结束符 '\0'
  • strlen 函数会从 arr 指向(char*类型)的地址开始,逐个字符向后查找 '\0',直到找到为止。由于 arr 中没有 '\0'strlen 会继续访问数组后面的内存,这会导致未定义行为,其输出结果是不确定的,可能是一个随机值,甚至可能引发程序崩溃。
2. strlen(arr + 0)
  • arr + 0 等同于 arr,因为数组名 arr 在表达式中(除了作为 sizeof 或 & 运算符的操作数)会隐式转换为指向数组首元素的指针(char*类型),加上 0 后仍然指向数组首元素。
  • 同样,由于数组中没有 '\0',这也会导致未定义行为,输出结果是一个随机值。
3. strlen(*arr)
  • *arr 对 arr 进行解引用,得到数组的首元素 'a',其 ASCII 值为 97。
  • strlen 函数期望的参数是一个指向以 '\0' 结尾的字符串的指针(char * 类型),而这里传递的是一个 char 类型的值(97)。将这个值当作指针来处理是错误的,会导致未定义行为,97作为地址传给给了strlen,可能会引发程序崩溃。
4. strlen(arr[1])
  • arr[1] 是数组的第二个元素 'b',其 ASCII 值为 98。
  • 与 strlen(*arr) 类似,strlen 函数期望的是一个指针,而这里传递的是一个 char 类型的值,会导致未定义行为,98作为地址传给给了strlen,可能会引发程序崩溃。
5. strlen(&arr)
  • &arr 是一个指向整个数组的指针,其类型为 char (*)[6]
  • strlen 函数期望的参数是 char * 类型的指针,这里传递的指针类型不匹配。将 char (*)[6] 类型的指针当作 char * 类型的指针来使用会导致未定义行为,可能会引发程序崩溃,也可能是一个随机值。
6. strlen(&arr + 1)
  • &arr 是指向整个数组的指针char (*)[6]&arr + 1 会跳过整个数组,指向下一个与 arr 同类型的数组的起始位置的地址。
  • 同样,strlen 函数期望的是 char * 类型的指针,这里指针类型不匹配,并且该位置的内存内容是不确定的,会导致未定义行为,可能会引发程序崩溃,也可能是一个随机值。
7. strlen(&arr[0] + 1)
  • &arr[0] 是数组首元素的地址,&arr[0] + 1 是指向数组第二个元素的指针(char*类型)。
  • 由于数组中没有 '\0'strlen 会从第二个元素开始向后查找 '\0',但一直找不到,会继续访问数组后面的内存,这会导致未定义行为,输出结果不确定,也可能是一个随机值。

代码3

#include <stdio.h>

int main()
{
    char arr[] = "abcdef";

    // 1. sizeof(arr)
    printf("%d\n", sizeof(arr));

    // 2. sizeof(arr + 0)
    printf("%d\n", sizeof(arr + 0));

    // 3. sizeof(*arr)
    printf("%d\n", sizeof(*arr));

    // 4. sizeof(arr[1])
    printf("%d\n", sizeof(arr[1]));

    // 5. sizeof(&arr)
    printf("%d\n", sizeof(&arr));

    // 6. sizeof(&arr + 1)
    printf("%d\n", sizeof(&arr + 1));

    // 7. sizeof(&arr[0] + 1)
    printf("%d\n", sizeof(&arr[0] + 1));

    return 0;
}

 接下来我们来详细的分析一下吧:

我们来回顾一下我之前提到过的:

数组名是数组首元素的地址
但是有2个例外:
1. sizeof(数组名),数组名单独放在括号里。
2. &数组名 

1. sizeof(arr)

  • arr 是一个字符数组,使用字符串字面量 "abcdef" 进行初始化。字符串字面量在 C 语言中会自动在末尾添加一个字符串结束符 '\0'
  • 所以数组 arr 实际上包含了 7 个字符:'a''b''c''d''e''f''\0'
  • sizeof 运算符作用于数组名时,返回整个数组所占用的内存字节数。每个 char 类型占 1 个字节,因此 sizeof(arr) 的结果是 7。

2. sizeof(arr + 0)

  • 当数组名 arr 出现在表达式中(除了作为 sizeof 或 & 运算符的操作数)时,它会隐式转换为指向数组首元素的指针,即 arr 退化为 char * 类型的指针。
  • arr + 0 仍然指向数组的首元素,本质上还是一个指针。
  • sizeof 计算的是指针的大小。在 32 位系统中,指针大小通常为 4 字节;在 64 位系统中,指针大小通常为 8 字节。

3. sizeof(*arr)

  • 由于 arr 退化为指向数组首元素的指针,*arr 表示对该指针进行解引用,得到数组的首元素 arr[0],也就是字符 'a'
  • arr[0] 是 char 类型,char 类型占用 1 个字节,所以 sizeof(*arr) 的结果是 1。

4. sizeof(arr[1])

  • arr[1] 是数组的第二个元素,其类型为 char
  • 因此,sizeof(arr[1]) 的结果同样是 1 字节。

5. sizeof(&arr)

  • &arr 是一个指向整个数组的指针,其类型为 char (*)[7],表示指向包含 7 个 char 元素的数组的指针。
  • 但无论指针指向何种类型的对象,sizeof 计算的都是指针本身的大小。所以在 32 位系统中为 4 字节,在 64 位系统中为 8 字节。

6. sizeof(&arr + 1)

  • &arr 是指向整个数组的指针(char (*)[7]),&arr + 1 表示对该指针进行偏移,使其指向下一个与 arr 同类型的数组的起始位置。
  • 它仍然是一个指针,所以 sizeof(&arr + 1) 的结果和 sizeof(&arr) 一样,在 32 位系统中为 4 字节,在 64 位系统中为 8 字节。

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

  • &arr[0] 是数组首元素的地址,类型为 char *
  • &arr[0] + 1 是指向数组第二个元素的指针,同样是 char * 类型。
  • 所以 sizeof(&arr[0] + 1) 计算的是指针的大小,在 32 位系统中为 4 字节,在 64 位系统中为 8 字节。

代码4

#include <stdio.h>
#include <string.h>

int main()
{
    // 定义并初始化字符数组 arr
    char arr[] = "abcdef";

    // 1. strlen(arr)
    printf("%d\n", strlen(arr));

    // 2. strlen(arr + 0)
    printf("%d\n", strlen(arr + 0));

    // 3. strlen(*arr)
    printf("%d\n", strlen(*arr));

    // 4. strlen(arr[1])
    printf("%d\n", strlen(arr[1]));

    // 5. strlen(&arr)
    printf("%d\n", strlen(&arr));

    // 6. strlen(&arr + 1)
    printf("%d\n", strlen(&arr + 1));

    // 7. strlen(&arr[0] + 1)
    printf("%d\n", strlen(&arr[0] + 1));

    return 0;
}

 接下来我们来详细的分析一下吧:

我们来回顾一下我之前提到过的:

数组名是数组首元素的地址
但是有2个例外:
1. sizeof(数组名),数组名单独放在括号里。
2. &数组名

printf(strlen(arr));

  • arr 作为数组名,在 strlen 函数调用时,它会隐式转换为指向数组首元素的指针(这是数组在表达式中的一种退化行为),也就是指向字符 'a' 的指针(char*)。
  • strlen 函数会从该指针指向的位置开始,逐个字符向后查找,直到遇到 '\0' 为止。从 'a' 开始,依次是 'b''c''d''e''f',再到 '\0',总共 6 个有效字符(不包含 '\0')。所以 strlen(arr) 的返回值是 6,程序会输出 6

printf(strlen(arr + 0));

  • arr + 0 本质上和 arr 是一样的,因为指针(char*)加上 0 并不会改变其指向。所以 arr + 0 依然指向数组的首元素 'a'
  • 因此,strlen(arr + 0) 的计算过程和 strlen(arr) 相同,返回值也是 6,程序会输出 6

printf(strlen(*arr));

  • *arr 是对 arr 这个指针进行解引用操作,得到的是数组的首元素 'a''a' 的 ASCII 码值是 97。
  • 但 strlen 函数期望的参数是一个指向以 '\0' 结尾的字符串的指针(char * 类型)。这里把一个 char 类型的值(97)当作指针(地址)传递给 strlen,这会导致未定义行为。程序可能会尝试从内存地址 97 开始查找 '\0',这可能会引发段错误(因为程序可能没有访问该内存地址的权限),程序崩溃或者输出一个无意义的结果。

printf(strlen(arr[1]));

  • arr[1] 表示数组的第二个元素,即字符 'b',其 ASCII 码值为 98。
  • 同样,strlen 函数需要的是指针(char*)类型的指针参数,这里传递的是 char 类型的值,98作为地址传给给了strlen会导致未定义行为,程序可能崩溃或者给出错误的输出。

printf(strlen(&arr));

  • &arr 是一个指向整个数组的指针,其类型为 char (*)[7](因为数组 arr 有 7 个元素)。
  • 而 strlen 函数要求的参数是 char * 类型的指针。将 char (*)[7] 类型的指针当作 char * 类型的指针使用是错误的但也可以算出是6,也会导致未定义行为,程序可能无法正常运行。

printf(strlen(&arr + 1));

  • &arr 指向整个数组(char (*)[7]),&arr + 1 会使指针跳过整个数组 arr,指向下一个和 arr 同类型数组应该存放的起始位置。
  • 这个位置的内存内容是不确定的,而且传递的指针类型与 strlen 函数期望的 char * 类型不匹配,会引发未定义行为,程序可能出现异常。

printf(strlen(&arr[0] + 1));

  • &arr[0] 是数组首元素的地址,&arr[0] + 1 是指向数组第二个元素 'b' 的指针(char*)。
  • strlen 函数从这个指针指向的位置开始查找 '\0'。从 'b' 开始,依次是 'c''d''e''f',再到 '\0',一共有 5 个有效字符。所以 strlen(&arr[0] + 1) 的返回值是 5,程序会输出 5

代码5

#include <stdio.h>

int main()
{
    char* p = "abcdef";

    // 1. sizeof(p)
    printf("%d\n", sizeof(p));

    // 2. sizeof(p + 1)
    printf("%d\n", sizeof(p + 1));

    // 3. sizeof(*p)
    printf("%d\n", sizeof(*p));

    // 4. sizeof(p[0])
    printf("%d\n", sizeof(p[0]));

    // 5. sizeof(&p)
    printf("%d\n", sizeof(&p));

    // 6. sizeof(&p + 1)
    printf("%d\n", sizeof(&p + 1));

    // 7. sizeof(&p[0] + 1)
    printf("%d\n", sizeof(&p[0] + 1));

    return 0;
}

 接下来我们来详细的分析一下吧: 

怕大家搞混这边补充一些指针知识:

1. p 的类型及含义

在代码 char* p = "abcdef"; 中,p 是一个字符指针,其类型为 char *。它指向字符串字面量 "abcdef" 的首字符 'a' 的地址。

2. p[0] 的含义

在 C 语言里,对于指针 pp[i] 等价于 *(p + i)。当 i 为 0 时,p[0] 等价于 *(p + 0),也就是 *p。所以 p[0] 表示的是指针 p 所指向的字符,在这里就是字符 'a',其类型为 char

3. &p[0] 的类型推导

& 是取地址运算符,&p[0] 表示取 p[0] 的地址。由于 p[0] 是 char 类型的字符 'a',那么 &p[0] 就是指向这个 char 类型字符的指针,其类型为 char *

4.&p的含义

& 是取地址运算符,当它作用于指针变量 p 时,&p 得到的是指针变量 p 自身在内存中的存储地址。由于 p 本身是 char * 类型(指向 char 的指针),那么指向 p 的指针就需要能够存储 p 的地址,这个指针指向的对象类型是 char *,所以 &p 的类型是指向 char * 类型的指针,即 char **

 

从本质上来说,&p[0] 和 p 指向的是同一个地址,即字符串 "abcdef" 的首字符地址,只是它们在概念上稍有不同:p 是一个指针变量,而 &p[0] 是通过对数组(这里可以把指针 p 当作指向字符串数组首元素的指针)首元素取地址得到的指针。综上所述,p 是 char * 类型,p[0] 是 char 类型,&p[0] 是 char * 类型,&p 是 char ** 类型。。

sizeof(p)

  • p 是一个(char *类型)指针变量(不是数组名),它存储的是字符串 "abcdef" 的首地址('a')。
  • sizeof 操作符用于计算指针变量本身所占用的内存大小。在 32 位系统中,指针通常占用 4 个字节;在 64 位系统中,指针通常占用 8 个字节。所以 sizeof(p) 的结果是 4(32 位系统)或 8(64 位系统)。

sizeof(p + 1)

  • p + 1 是一个指针运算,它会让指针 p 向后偏移一个 char 类型的大小,从而指向字符串中的第二个字符 'b'
  • 但无论指针指向何处,sizeof 计算的都是指针本身的大小。所以 sizeof(p + 1) 的结果和 sizeof(p) 一样,是 4(32 位系统)或 8(64 位系统)。

sizeof(*p)

  • *p 是对指针 p 进行解引用操作,得到的是指针 p 所指向的字符,也就是字符串的首字符 'a'
  • 'a' 是 char 类型,char 类型在 C 语言中通常占用 1 个字节。所以 sizeof(*p) 的结果是 1。

sizeof(p[0])

  • p[0](char 类型)等价于 *(p + 0),也就是指针 p 所指向的字符,同样是 'a'
  • 因此,sizeof(p[0]) 的结果和 sizeof(*p) 相同,为 1。

sizeof(&p)

  • &p 是取指针变量 p 本身的地址,它是一个指向指针的指针(类型为 char **)。
  • 不管指针指向的是什么类型,sizeof 计算的都是指针本身的大小。所以 sizeof(&p) 的结果是 4(32 位系统)或 8(64 位系统)。

sizeof(&p + 1)

  • &p 是指向指针 p 的指针,&p + 1 会让这个指针向后偏移一个 char ** 类型的大小,指向下一个 char ** 类型的位置。
  • 但 sizeof 计算的依然是指针本身的大小,所以 sizeof(&p + 1) 的结果和 sizeof(&p) 一样,是 4(32 位系统)或 8(64 位系统)。

sizeof(&p[0] + 1)

  • p[0](char 类型) 是字符串的首字符 'a'&p[0] 是首字符 'a' 的地址,也就是指针 p 所指向的地址。
  • &p[0] + 1 会让这个指针向后偏移一个 char 类型的大小,指向字符串中的第二个字符 'b'
  • 它仍然是一个指针(&p[0] char *类型),所以 sizeof(&p[0] + 1) 的结果和 sizeof(p) 相同,是 4(32 位系统)或 8(64 位系统)。

代码6

#include<stdio.h>
#include<string.h>
int main()
{
	char* p = "abcdef";

    //1.strlen(p)
	printf("%d\n", strlen(p));
         
    //2.strlen(p + 1)
	printf("%d\n", strlen(p + 1));

    //3.strlen(*p)
	printf("%d\n", strlen(*p));

    //4.strlen(p[0])
	printf("%d\n", strlen(p[0]));

    //strlen(&p)
	printf("%d\n", strlen(&p));

    //6.strlen(&p + 1)
	printf("%d\n", strlen(&p + 1));

    //7.strlen(&p[0] + 1)
	printf("%d\n", strlen(&p[0] + 1));

	return 0;
}

 接下来我们来详细的分析一下吧:

  1. strlen(p)

    • p(char*类型) 是一个指向字符串常量 "abcdef" 的指针,strlen(p) 会从 p 所指向的字符开始,逐个字符计数,直到遇到字符串结束符 '\0' 为止。
    • 字符串 "abcdef" 的长度是 6,所以输出结果为 6
  2. strlen(p + 1)

    • p + 1 (char*类型)指向字符串 "abcdef" 的第二个字符 'b'
    • strlen(p + 1) 会从 'b' 开始计数,直到遇到字符串结束符 '\0',所以输出结果为 5
  3. strlen(*p)

    • *p (char类型)是解引用操作,它得到的是 p 所指向的第一个字符 'a',其 ASCII 码值为 97。
    • strlen 函数的参数应该是一个指向字符串的指针,而这里传递的是一个字符(实际上是一个整数),这会导致未定义行为,程序可能会崩溃或输出错误的结果。
  4. strlen(p[0])

    • p[0] (char类型)等价于 *p,同样得到的是字符 'a'
    • 传递字符给 strlen 函数会导致未定义行为,程序可能会崩溃或输出错误的结果。
  5. strlen(&p)

    • &p (char**类型)是指针 p 的地址,它指向的并不是一个以 '\0' 结尾的字符串。
    • 传递指针 p 的地址给 strlen 函数会导致未定义行为,程序可能会崩溃或随机值的结果。
  6. strlen(&p + 1)

    • &p + 1 (char**类型)是指针 p 的地址向后偏移一个 char* 类型的大小。
    • 它指向的同样不是一个以 '\0' 结尾的字符串,传递该地址给 strlen 函数会导致未定义行为,程序可能会崩溃或随机值的结果。
  7. strlen(&p[0] + 1)

    • &p[0](char*类型) 等价于 p,指向字符串 "abcdef" 的第一个字符 'a'
    • &p[0] + 1 指向字符串的第二个字符 'b'
    • strlen(&p[0] + 1) 会从 'b' 开始计数,直到遇到字符串结束符 '\0',所以输出结果为 5

 2.3 ⼆维数组

#include<stdio.h>

int main()
{
    // 定义一个 3 行 4 列的二维数组 a,并初始化为全 0
    int a[3][4] = { 0 };

    // 1.sizeof(a)
    printf("%d\n", sizeof(a));

    // 2.sizeof(a[0][0])
    printf("%d\n", sizeof(a[0][0]));

    // 3.sizeof(a[0])
    printf("%d\n", sizeof(a[0]));

    // 4.sizeof(a[0] + 1)
    printf("%d\n", sizeof(a[0] + 1));

    // 5.sizeof(*(a[0] + 1))
    printf("%d\n", sizeof(*(a[0] + 1)));

    // 6.sizeof(a + 1)
    printf("%d\n", sizeof(a + 1));

    // 7.sizeof(*(a + 1))
    printf("%d\n", sizeof(*(a + 1)));

    // 8.sizeof(&a[0] + 1)
    printf("%d\n", sizeof(&a[0] + 1));

    // 9.sizeof(*(&a[0] + 1))
    printf("%d\n", sizeof(*(&a[0] + 1)));

    // 10.sizeof(*a)
    printf("%d\n", sizeof(*a));

    // 11.sizeof(a[3])
    printf("%d\n", sizeof(a[3]));

    return 0;
}

 接下来我们来详细的分析一下吧:

  1. sizeof(a)
    • a 是一个 3 行 4 列的二维数组,每个元素是 int 类型。
    • 在大多数系统中,int 类型占 4 个字节,所以整个数组的元素个数为 3 * 4 = 12 个。
    • 因此,sizeof(a) 的结果是 3 * 4 * sizeof(int) = 48 字节。
  2. sizeof(a[0][0])
    • a[0][0] 表示数组 a 的第一个元素,它是 int 类型。
    • 通常 int 类型占 4 个字节,所以 sizeof(a[0][0]) 的结果是 4 字节。
  3. sizeof(a[0])
    • a[0](int *类型) 表示数组 a 的第一行,它可以看作是一个包含 4 个 int 元素的一维数组。
    • 所以 sizeof(a[0]) 的结果是 4 * sizeof(int) = 16 字节。
  4. sizeof(a[0] + 1)
    • a[0] 是第一行数组的首地址,a[0] + 1 是第一行第二个元素的地址。
    • 地址本质上是一个指针,在大多数系统中,指针的大小是固定的,通常为 4 或 8 字节(取决于系统是 32 位还是 64 位)。
    • 所以 sizeof(a[0] + 1) 的结果是指针的大小,一般为 4 或 8 字节。
  5. sizeof(*(a[0] + 1))
    • *(a[0] + 1) 表示对第一行第二个元素的地址进行解引用,得到的是第一行第二个元素。
    • 该元素是 int 类型,所以 sizeof(*(a[0] + 1)) 的结果是 4 字节。
  6. sizeof(a + 1)
    • a (int (*)[4]类型)是二维数组名,a + 1 表示跳过第一行,指向第二行的首地址。
    • 它是一个指针,所以 sizeof(a + 1) 的结果是指针的大小,一般为 4 或 8 字节。
  7. sizeof(*(a + 1))
    • *(a + 1) 表示对指向第二行的指针进行解引用,得到的是第二行数组。
    • 第二行数组包含 4 个 int 元素,所以 sizeof(*(a + 1)) 的结果是 4 * sizeof(int) = 16 字节。
  8. sizeof(&a[0] + 1)
    • &a[0] (int (*)[4]类型) 是第一行数组的地址,&a[0] + 1 表示跳过第一行,指向第二行的地址。
    • 它是一个指针,所以 sizeof(&a[0] + 1) 的结果是指针的大小,一般为 4 或 8 字节。
  9. sizeof(*(&a[0] + 1))
    • *(&a[0] + 1) 表示对指向第二行的地址进行解引用,得到的是第二行数组。
    • 所以 sizeof(*(&a[0] + 1)) 的结果是 4 * sizeof(int) = 16 字节。
  10. sizeof(*a)
    • *a(int *类型)  等价于 a[0],表示第一行数组。
    • 所以 sizeof(*a) 的结果是 4 * sizeof(int) = 16 字节。
  11. sizeof(a[3])
    • 虽然数组 a 只有 3 行(索引从 0 到 2),a[3] 属于越界访问。
    • 但 sizeof 是在编译时计算大小,它不会真正访问数组元素,a[3] 被看作是一个包含 4 个 int 元素的一维数组。
    • 所以 sizeof(a[3]) 的结果是 4 * sizeof(int) = 16 字节。

3. 指针运算笔试题解析

3.1 题⽬1:

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

详细讲解:

  • &a:这里的 &a 表示整个数组 a 的地址,它的类型是 int (*)[5],即指向包含 5 个整型元素的数组的指针。
  • &a + 1:由于 &a 是指向整个数组的指针,&a + 1 会跳过整个数组的内存空间。在这个例子中,数组 a 包含 5 个整型元素,每个整型元素通常占 4 个字节(取决于系统),所以 &a + 1 会跳过 5 * 4 = 20 个字节,指向数组 a 之后的内存地址。
  • (int *):这是一个强制类型转换操作,将 &a + 1 的类型从 int (*)[5] 转换为 int *,这样 ptr 就变成了一个指向整型的指针。

printf 函数输出

  • *(a + 1):数组名 a 代表数组首元素的地址,a + 1 表示数组首元素地址向后偏移一个整型元素的位置,即指向数组的第二个元素。*(a + 1) 是对该地址进行解引用操作,得到该地址存储的值,也就是数组的第二个元素 2。
  • *(ptr - 1)ptr 指向数组 a 之后的内存地址,ptr - 1 表示将指针 ptr 向前偏移一个整型元素的位置,即指向数组 a 的最后一个元素。*(ptr - 1) 是对该地址进行解引用操作,得到该地址存储的值,也就是数组的最后一个元素 5。

3.2 题⽬2

/在X86环境下
//假设结构体的⼤⼩是20个字节
//程序输出的结果是啥?
#include<stdio.h>
struct Test
{
  int Num;
 char *pcName;
 short sDate;
 char cha[2];
 short sBa[4];
}*p = (struct Test*)0x100000;

int main()
{
 printf("%p\n", p + 0x1);
 printf("%p\n", (unsigned long)p + 0x1);
 printf("%p\n", (unsigned int*)p + 0x1);
 return 0;
}

详细讲解:

  • 头文件#include <stdio.h> 引入标准输入输出库,以便后续使用 printf 函数进行输出。
  • 结构体定义:定义了 struct Test 结构体,它包含了不同类型的成员变量。
  • 指针初始化:声明了一个指向 struct Test 类型的指针 p,并将其初始化为地址 0x100000。这里通过强制类型转换 (struct Test*) 把 0x100000 转换为 struct Test* 类型。

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

 
  • 指针运算规则:在 C 语言里,当对指针进行加减运算时,偏移量是根据指针所指向的数据类型的大小来计算的。p 是指向 struct Test 类型的指针,已知 struct Test 结构体大小为 20 字节。
  • 具体计算p + 0x1 表示将指针 p 向后偏移 1 个 struct Test 结构体的大小,也就是偏移 20 字节。因为十六进制中,20 字节对应的十六进制数是 0x14(十进制 20 转换为十六进制为 0x14)。所以 p + 0x1 得到的地址是 0x100000 + 0x14 = 0x100014
  • 输出结果:这行代码会输出 0x100014

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

 
  • 类型转换与运算:首先将指针 p 强制转换为 unsigned long 类型,此时 p 就变成了一个无符号长整型数值。在进行 (unsigned long)p + 0x1 运算时,只是简单地对这个无符号长整型数值进行加法操作,即把地址值 0x100000 加上 1(整数加1)。
  • 输出结果:所以这行代码会输出 0x100001

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

 
  • 类型转换与指针运算:先把指针 p 强制转换为 unsigned int* 类型,也就是指向无符号整型的指针。在 X86 环境下,无符号整型 unsigned int 通常占用 4 个字节。当对 unsigned int* 类型的指针进行加法运算时,偏移量是根据无符号整型的大小来计算的。
  • 具体计算(unsigned int*)p + 0x1 表示将指针向后偏移 1 个无符号整型的大小,即偏移 4 个字节。所以得到的地址是 0x100000 + 0x4 = 0x100004
  • 输出结果:这行代码会输出 0x100004

3.3 题⽬3

#include <stdio.h>
int main()
{
 int a[3][2] = { (0, 1), (2, 3), (4, 5) };
 int *p;
 p = a[0];
 printf( "%d", p[0]);
 return 0;
}

详细讲解:

  • 这里定义了一个 3 行 2 列的二维数组 a。但需要注意的是,初始化列表中使用的是逗号表达式 (0, 1)(2, 3) 和 (4, 5)
  • 在 C 语言里,逗号表达式会从左到右依次计算每个表达式的值,并且整个逗号表达式的值是最后一个表达式的值。所以 (0, 1) 的值是 1,(2, 3) 的值是 3,(4, 5) 的值是 5。
  • 因此,数组 a 实际的初始化情况是:

a[0][0] = 1; 

a[0][1] = 3;

a[1][0] = 5; 

a[1][1] = 未初始化的值(默认是随机值);

a[2][0] = 未初始化的值(默认是随机值);

a[2][1] = 未初始化的值(默认是随机值);

  • 定义了一个整型指针 p
  • a[0] 是二维数组 a 第一行的首地址,它的类型是 int *。将 a[0] 赋值给 p 后,p 就指向了数组 a 的第一行的第一个元素。
  • p[0] 等价于 *(p + 0),也就是 p 所指向的元素的值。由于 p 指向数组 a 的第一行的第一个元素,而该元素的值是 1,所以这行代码会输出 1。

3.4 题⽬4

//假设环境是x86环境,程序输出的结果是啥?
#include <stdio.h>
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][2] 的地址
    • p 是指向包含 4 个整型元素的数组的指针,p[4] 表示 p 向后移动 4 行,由于每行有 4 个整型元素,所以 p 向后移动了 4 * 4 = 16 个整型元素的位置。
    • p[4][2] 表示在 p[4] 这一行的基础上再向后移动 2 个整型元素的位置。因此,&p[4][2] 相对于 p 所指向的起始地址偏移了 16 + 2 = 18 个整型元素的位置。
  • 计算 &a[4][2] 的地址
    • a 是一个 5 行 5 列的二维数组,a[4] 表示第 5 行(数组下标从 0 开始)的首地址,相对于 a 的起始地址向后移动了 4 * 5 = 20 个整型元素的位置。
    • a[4][2] 表示在第 5 行的基础上再向后移动 2 个整型元素的位置。所以,&a[4][2] 相对于 a 的起始地址偏移了 20 + 2 = 22 个整型元素的位置。
  • 计算地址差值
    • &p[4][2] - &a[4][2] 表示两个地址之间相差的整型元素个数。这里 &p[4][2] 相对于起始地址偏移了 18 个整型元素,&a[4][2] 相对于起始地址偏移了 22 个整型元素,所以它们的差值为 18 - 22 = -4
  • 输出结果
    • %p 是用于输出指针地址的格式说明符,但在输出指针相减的结果时,它会将差值以十六进制的形式输出。由于差值是 -4,在计算机中以补码形式存储,%p不会对存储的二进制转换,所以存储的是什么就输出什么,对于 32 位系统,-4 的补码是 0xFFFFFFFC(没有转换为原码,所以是一个非常大的数)
    • %d 是用于输出十进制整数的格式说明符,会直接输出 -4。

3.5 题⽬5

#include <stdio.h>
int main()
{
 int aa[2][5] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
 int *ptr1 = (int *)(&aa + 1);
 int *ptr2 = (int *)(*(aa + 1));
 printf( "%d,%d", *(ptr1 - 1), *(ptr2 - 1));
 return 0;
}

详细讲解: 

指针 ptr1 的定义与初始化

  • &aa:这里的 &aa 表示整个二维数组 aa 的地址,其类型是 int (*)[2][5],即指向一个包含 2 行 5 列整型元素数组的指针。
  • &aa + 1:因为 &aa 是指向整个二维数组的指针,&aa + 1 会跳过整个数组 aa 所占用的内存空间。在这个例子中,数组 aa 包含 2 * 5 = 10 个整型元素,每个整型元素通常占 4 个字节(取决于系统),所以 &aa + 1 会指向数组 aa 之后的内存地址。
  • (int *):这是一个强制类型转换操作,将 &aa + 1 的类型从 int (*)[2][5] 转换为 int *,这样 ptr1 就变成了一个指向整型的指针,它指向数组 aa 之后的第一个整型位置。

指针 ptr2 的定义与初始化

  • aa:数组名 aa 在大多数表达式中会隐式转换为指向其首元素的指针,对于二维数组 aa,它会转换为指向第一行的指针,类型为 int (*)[5]
  • aa + 1:由于 aa 是指向第一行的指针,aa + 1 会指向数组的第二行,其类型仍然是 int (*)[5]
  • *(aa + 1):对 aa + 1 进行解引用操作,得到第二行数组的首地址,其类型是 int *,也就是指向第二行第一个元素的指针。
  • 这里的强制类型转换 (int *) 实际上是多余的,因为 *(aa + 1) 本身就是 int * 类型。ptr2 最终指向数组 aa 第二行的第一个元素。

 printf 函数输出

  • *(ptr1 - 1)ptr1 指向数组 aa 之后的内存地址,ptr1 - 1 表示将指针 ptr1 向前偏移一个整型元素的位置,即指向数组 aa 的最后一个元素。*(ptr1 - 1) 是对该地址进行解引用操作,得到该地址存储的值,也就是 10
  • *(ptr2 - 1)ptr2 指向数组 aa 第二行的第一个元素,ptr2 - 1 表示将指针 ptr2 向前偏移一个整型元素的位置,即指向数组 aa 第一行的最后一个元素。*(ptr2 - 1) 是对该地址进行解引用操作,得到该地址存储的值,也就是 5

3.6 题⽬6

#include <stdio.h>
int main()
{
 char *a[] = {"work","at","alibaba"};
 char**pa = a;
 pa++;
 printf("%s\n", *pa);
 return 0;
}

详细讲解:

  • 这里定义了一个指针数组 a。指针数组是指数组的每个元素都是指针类型。在这个例子中,数组 a 的元素是 char * 类型,也就是字符指针。
  • 初始化列表中的 "work""at""alibaba" 是字符串常量。在 C 语言里,字符串常量会被存储在只读内存区域,并且在使用时会隐式转换为指向其首字符的指针。所以,a[0] 指向字符串 "work" 的首字符 'w'a[1] 指向字符串 "at" 的首字符 'a'a[2] 指向字符串 "alibaba" 的首字符 'a'
  • 定义了一个二级指针 pa,二级指针就是指向指针的指针。
  • 数组名 a 在大多数表达式中会隐式转换为指向其首元素的指针。由于 a 的元素是 char * 类型,所以 a 会转换为 char ** 类型的指针,指向 a[0]。因此,这里将 a 赋值给 pa 后,pa 就指向了指针数组 a 的第一个元素 a[0]
  • 因为 pa 是 char ** 类型的指针,对其进行 ++ 操作会让它指向下一个 char * 类型的元素。也就是说,pa 原本指向 a[0],执行 pa++ 后,pa 指向了 a[1]
  • *pa 是对 pa 进行解引用操作,由于 pa 指向 a[1],所以 *pa 就相当于 a[1],而 a[1] 是指向字符串 "at" 首字符的指针。
  • %s 是 printf 函数用于输出字符串的格式说明符,它会从给定的指针所指向的字符开始,依次输出字符,直到遇到字符串结束符 '\0'。所以,这里会输出字符串 "at"

 3.7 题⽬7

#include <stdio.h>
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;
}

详细讲解: 

字符指针数组 c 的定义与初始化

  • 定义了一个字符指针数组 c,数组中的每个元素都是一个 char * 类型的指针,分别指向字符串常量 "ENTER""NEW""POINT" 和 "FIRST" 的首字符。

二级字符指针数组 cp 的定义与初始化

  • 定义了一个二级字符指针数组 cp,数组中的元素是 char ** 类型的指针。
  • c+3 指向 c 数组的第 4 个元素(下标从 0 开始),即指向 "FIRST" 的指针;c+2 指向 c 数组的第 3 个元素,即指向 "POINT" 的指针;c+1 指向 c 数组的第 2 个元素,即指向 "NEW" 的指针;c 指向 c 数组的第 1 个元素,即指向 "ENTER" 的指针。

三级字符指针 cpp 的定义与初始化

  • 定义了一个三级字符指针 cpp,并将其初始化为指向二级字符指针数组 cp 的首元素。

第一个 printf 语句

  • ++cpp:先将 cpp 指针向后移动一个位置,使其指向 cp 数组的第 2 个元素(原本指向 cp[0],现在指向 cp[1])。
  • *++cpp:对移动后的 cpp 进行解引用,得到 cp[1],即 c+2,它指向 c 数组中 "POINT" 的指针。
  • **++cpp:再对 *++cpp 进行解引用,得到 "POINT" 字符串的首地址。
  • 最终输出字符串 "POINT"

第二个 printf 语句

  • ++cpp:再次将 cpp 指针向后移动一个位置,使其指向 cp 数组的第 3 个元素(即 cp[2])。
  • *++cpp:对移动后的 cpp 进行解引用,得到 cp[2],即 c+1,它指向 c 数组中 "NEW" 的指针。
  • --*++cpp:将 *++cpp 指向的指针向前移动一个位置,即指向 c 数组的第 1 个元素(c[0]),也就是 "ENTER" 的指针。
  • *--*++cpp:对 --*++cpp 进行解引用,得到 "ENTER" 字符串的首地址。
  • *--*++cpp+3:将 "ENTER" 字符串的首地址向后移动 3 个位置,指向字符 'E' 后面的第 3 个字符 'E'
  • 最终输出从该位置开始的字符串 "ER"

第三个 printf 语句

  • cpp[-2]:等价于 *(cpp - 2),将 cpp 指针向前移动 2 个位置,指向 cp 数组的第 1 个元素(cp[0]),即 c+3,它指向 c 数组中 "FIRST" 的指针。
  • *cpp[-2]:对 cpp[-2] 进行解引用,得到 "FIRST" 字符串的首地址。
  • *cpp[-2]+3:将 "FIRST" 字符串的首地址向后移动 3 个位置,指向字符 'R'
  • 最终输出从该位置开始的字符串 "ST"

第四个 printf 语句

  • cpp[-1]:等价于 *(cpp - 1),由于 cpp 指向 cp 数组的第 3 个元素,所以 cpp - 1 指向 cp 数组的第 2 个元素(即 c + 2)。
  • cpp[-1][-1]:等价于 *(*(cpp - 1) - 1)*(cpp - 1) 得到 c + 2*(cpp - 1) - 1 得到 c + 1,再解引用得到指向 "NEW" 的指针。
  • cpp[-1][-1] + 1:将指向 "NEW" 的指针向后移动 1 个位置,指向字符 'E',从这个位置开始输出字符串,所以输出 "EW"

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2295063.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

CEF132 编译指南 Windows 篇 - 拉取 CEF 源码 (五)

1. 引言 获取 CEF 132 源码是开始编译工作的前提和关键步骤。在完成 depot_tools 的安装和配置后&#xff0c;我们需要通过正确的方式下载和同步 CEF 的源代码。由于 CEF 项目依赖于 Chromium 的大量组件&#xff0c;因此源码的获取过程需要特别注意同步策略和版本管理&#x…

DeepSeek与llama本地部署(含WebUI)

DeepSeek从2025年1月起开始火爆&#xff0c;成为全球最炙手可热的大模型&#xff0c;各大媒体争相报道。我们可以和文心一言一样去官网进行DeepSeek的使用&#xff0c;那如果有读者希望将大模型部署在本地应该怎么做呢&#xff1f;本篇文章将会教你如何在本地傻瓜式的部署我们的…

让万物「听说」:AI 对话式智能硬件方案和发展洞察

本文整理自声网 SDK 新业务探索组技术负责人&#xff0c;IoT 行业专家 吴方方 1 月 18 日在 RTE 开发者社区「Voice Agent 硬件分享会」上的分享。本次主要介绍了 AI 对话式智能硬件的发展历程&#xff0c;新一波 AI 浪潮所带来的创新机遇、技术挑战以及未来的展望。 在语音交…

Day38-【13003】短文,二叉树,完全二叉树,二叉树的顺序存储,和链式存储

文章目录 第二节 二叉树二叉树的定义及重要性质n个结点&#xff0c;能组合成多少个不同的二叉树满二叉树、完全二叉树完全二叉树的性质二叉树的性质二叉树的结点数完全二叉树的高度 二叉树的存储顺序存储方式链式存储方式二叉链表的程序实现二叉链表空指针域计算 第二节 二叉树…

【AI】在Ubuntu中使用docker对DeepSeek的部署与使用

这篇文章前言是我基于部署好的deepseek-r1:8b模型跑出来的 关于部署DeepSeek的前言与介绍 在当今快速发展的技术环境中&#xff0c;有效地利用机器学习工具来解决问题变得越来越重要。今天&#xff0c;我将引入一个名为DeepSeek 的工具&#xff0c;它作为一种强大的搜索引擎&a…

unity视频在场景中的使用

&#xff08;一&#xff09;软件操作在平面上显示视频播放 1.创建渲染器纹理 2.创建平面 3.在平面上添加Video player 4.视频拖拽到Video player 5.渲染模式选择渲染器纹理 6.把纹理拖到目标纹理上 7.把纹理拖到平面上就可以了 然后运行项目 8.结果 &#xff08;二&#…

vue3+vite+eslint|prettier+elementplus+国际化+axios封装+pinia

文章目录 vue3 vite 创建项目如果创建项目选了 eslint prettier从零教你使用 eslint prettier第一步&#xff0c;下载eslint第二步&#xff0c;创建eslint配置文件&#xff0c;并下载好其他插件第三步&#xff1a;安装 prettier安装后配置 eslint (2025/2/7 补充) 第四步&am…

【Android开发AI实战】基于CNN混合YOLOV实现多车牌颜色区分且针对车牌进行矫正识别(含源码)

文章目录 引言单层卷积神经网络&#xff08;Single-layer CNN&#xff09;&#x1f4cc; 单层 CNN 的基本结构&#x1f4cc; 单层 CNN 计算流程图像 透视变换矫正车牌c实现&#x1fa84;关键代码实现&#xff1a;&#x1fa84;crnn结构图 使用jni实现高级Android开发&#x1f3…

多光谱成像技术在华为Mate70系列的应用

华为Mate70系列搭载了光谱技术的产物——红枫原色摄像头&#xff0c;这是一款150万像素的多光谱摄像头。 相较于普通摄像头&#xff0c;它具有以下优势&#xff1a; 色彩还原度高&#xff1a;色彩还原准确度提升约 120%&#xff0c;能捕捉更多光谱信息&#xff0c;使拍摄照片色…

数字人|通过语音和图片来创建高质量的视频

简介 arXiv上的计算机视觉领域论文&#xff1a; AniPortrait: Audio-Driven Synthesis of Photorealistic Portrait Animation AniPortrait&#xff1a;照片级真实感肖像动画的音频驱动合成 核心内容围绕一种新的人像动画合成框架展开。 研究内容 提出 AniPortrait 框架&a…

LLMs瞬间获得视觉与听觉感知,无需专门训练:Meta的创新——在图像、音频和视频任务上实现最优性能。

引言&#xff1a; 问题&#xff1a; 当前的多模态任务&#xff08;如图像、视频、音频描述生成、编辑、生成等&#xff09;通常需要针对特定任务训练专门的模型&#xff0c;而现有的方法在跨模态泛化方面存在局限性&#xff0c;难以适应新任务。此外&#xff0c;多模态嵌入反演…

ZZNUOJ(C/C++)基础练习1081——1090(详解版)

目录 1081 : n个数求和 &#xff08;多实例测试&#xff09; C C 1082 : 敲7&#xff08;多实例测试&#xff09; C C 1083 : 数值统计(多实例测试) C C 1084 : 计算两点间的距离&#xff08;多实例测试&#xff09; C C 1085 : 求奇数的乘积&#xff08;多实例测试…

【DeepSeek】私有化本地部署图文(Win+Mac)

目录 一、DeepSeek本地部署【Windows】 1、安装Ollama 2、配置环境变量 3、下载模型 4、使用示例 a、直接访问 b、chatbox网页访问 二、DeepSeek本地部署【Mac】 1、安装Ollama 2、配置环境变量 3、下载模型 4、使用示例 5、删除已下载的模型 三、DeepSeek其他 …

深度学习里面的而优化函数 Adam,SGD,动量法,AdaGrad 等 | PyTorch 深度学习实战

前一篇文章&#xff0c;使用线性回归模型逼近目标模型 | PyTorch 深度学习实战 本系列文章 GitHub Repo: https://github.com/hailiang-wang/pytorch-get-started 本篇文章内容来自于 强化学习必修课&#xff1a;引领人工智能新时代【梗直哥瞿炜】 深度学习里面的而优化函数 …

基于Spring Boot的图书个性化推荐系统的设计与实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…

【论文阅读】Comment on the Security of “VOSA“

Comment on the Security of Verifiable and Oblivious Secure Aggregation for Privacy-Preserving Federated Learning -- 关于隐私保护联邦中可验证与遗忘的安全聚合的安全性 论文来源摘要Introduction回顾 VOSA 方案对VOSA不可伪造性的攻击对于类型 I 的攻击对于类型 II 的…

3.攻防世界 Confusion1(服务器模板注入SSTI)

题目描述如下 进入题目页面如下 图片是蟒蛇、大象&#xff1f;python、php&#xff1f; 猜测需要代码审计 点击 F12查看源码&#xff0c;有所提示flag 但是也没有其他信息了 猜测本题存在SSTI&#xff08;服务器模板注入&#xff09;漏洞&#xff0c;为验证&#xff0c;构造…

保姆级教程 !SQL Server数据库的备份和还原

使用 SQL Server Management Studio (SSMS) 备份和还原数据库 1、数据库备份 Step 1 打开 SSMS 输入server name 以及用户名和密码连接到你的 SQL Server 实例 Step 2 展开Database,选中你要备份的数据库 Step 3 右击选中的数据库&#xff0c;点击Tasks --> Back …

AlwaysOn 可用性组副本所在服务器以及该副本上数据库的各项状态信息

目录标题 AlwaysOn语句代码解释&#xff1a;1. sys.dm_hadr_database_replica_states 视图字段详细解释及官网链接官网链接字段解释 2. sys.availability_replicas 视图字段详细解释及官网链接官网链接字段解释 查看视图的创建语句方法一&#xff1a;使用 SQL Server Managemen…

ip地址是手机号地址还是手机地址

在数字化生活的浪潮中&#xff0c;IP地址、手机号和手机地址这三个概念如影随形&#xff0c;它们各自承载着网络世界的独特功能&#xff0c;却又因名称和功能的相似性而时常被混淆。尤其是“IP地址”这一术语&#xff0c;经常被错误地与手机号地址或手机地址划上等号。本文旨在…