首页 > 其他分享 >【C语言】指针由浅入深全方位详解

【C语言】指针由浅入深全方位详解

时间:2024-07-15 22:56:18浏览次数:16  
标签:由浅入深 arr int C语言 地址 详解 数组 printf 指针

目录

指针定义

指针类型

野指针

指针运算 

指针与数组的联系

二级指针 

指针数组 

字符指针 

数组指针 

数组参数,指针参数 

函数指针 

函数指针数组

回调函数 

练习题 

代码仓库 


指针定义

1. 指针是内存中一个最小单元的编号,也就是地址。

2. 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量。

3. 我们可以通过 &(取地址操作符) 取出变量的内存起始地址,把地址可以存放到一个变量中,这个变量就是指针变量。

4. 指针的大小在32位平台是4个字节,在64位平台是8个字节

int a = 100;
int * pa = &a; 
//*表示pa是指针变量。
//int表示 1.pa指向的类型是int 2.pa解引用的时候访问的对象大小是sizeof(int)。

指针类型

1. 指针类型可以决定指针解引用的时候访问多少个字节(指针的权限)

int* 的指针解引用访问4个字节。

char* 的指针解引用访问1个字节

2. 指针类型决定指针加1减1操作时的步长

整型指针+1跳过4个字节,字符指针+1跳过1个字节


野指针

1. 概念

野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)。

2. 野指针成因

//1.指针未初始化
int *p;//局部变量指针未初始化,默认为随机值
*p = 20;

//2.指针越界访问
 int arr[10] = {0};
 int *p = arr;
 int i = 0;
 for(i=0; i<=11; i++)
 {
     //当指针指向的范围超出数组arr的范围时,p就是野指针
     *(p++) = i;
 }

//3.指针指向的空间释放

3. 如何规避野指针? 

1.明确知道指针应该初始化为谁的地址就直接初始化,不知道的就初始化为NULL。

2.小心指针越界。

3.指针指向的空间释放后,及时置NULL。

4.避免返回局部变量的地址。

5.指针使用前检查有效性。


指针运算 

1. 指针加减整数

int arr[10] = {0};
int* p = &arr[0];
for(int i=0; i<sizeof(arr)/sizeof(arr[0]); i++)
{
    *p = i;
    p = p + 1; //指针加1,加一个sizeof(int)
}

p = arr;
for(int i=0; i<sizeof(arr)/sizeof(arr[0]); i++) { printf("&d ", *(p+i)); }

//*(p+i) == arr[i]
//*(arr+i) == arr[i] == *(i+arr) == i[arr]

2. 指针减指针 

int arr[10] = {0};
printf("%d\n", &arr[9] - &arr[0]); //等于9
//指针减指针的绝对值是指针和指针之间的元素个数
//指针和指针相减的前提是两个指针指向了同一块空间

3. 指针的关系运算 

1.地址是有大小的,指针的关系运算就是比较指针的大小

2.标准规定:允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。


指针与数组的联系

1. 数组是一块连续的空间,可以存放1个或多个类型相同的数据。

2. 数组名是数组首元素地址,数组名==地址==指针。
3. 因为数组是连续存放的,所以通过指针就可以遍历访问整个数组。


二级指针 

1. 指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里?

pp是二级指针变量,用来存放一级指针变量的地址

int a = 10;
int* p = &a; 
int** pp = &p; 

p是指针变量,是变量就有地址

pp存放p的地址


指针数组 

1. 类比

整型数组 - 存放整型的数组
字符数组 - 存放字符的数组
指针数组 - 存放指针(地址)的数组

.

int* arr1[10]; //存放整形指针的数组

char* arr2[4]; //存放字符指针的数组

char** arr3[5];//存放二级字符指针的数组

2. 使用指针数组模拟二维数组

真正的二维数组是连续存放的


字符指针 

1. char* 的两种使用方式

//在指针的类型中我们知道有一种指针类型为字符指针char*
int main()
{
    char ch = 'w';
    char *pc = &ch;
    *pc = 'w';
    return 0;
}

//还有一种使用方式如下:
int main()
{
    //这里本质是把字符串hello bit.首字符的地址放到了pstr中
    const char* pstr = "hello bit."; 
    printf("%s\n", pstr);
    return 0;
}

2. 面试题

//输出结果?
 #include <stdio.h>
 
int main()
 {
    char str1[] = "hello bit.";
    char str2[] = "hello bit.";
    const char *str3 = "hello bit.";
    const char *str4 = "hello bit.";
 
    if(str1 ==str2)
        printf("str1 and str2 are same\n");
    else
        printf("str1 and str2 are not same\n");
       
    if(str3 ==str4)
        printf("str3 and str4 are same\n");
    else
        printf("str3 and str4 are not same\n");
       
    return 0;
 }

str1和str2不同,原因如下

1. str1和str2是两个不同的数组意味着不同的空间

2. str1, str2是数组名也就是首元素地址,空间不同地址自然不同

.

str3和str4相同,原因如下

1. "hello bit." 是一个常量字符串在内存的常量区,然后首元素地址给str3

2. 因为常量字符串无法改变,所以它可以再次将首元素地址给str4

3. 这里str3和str4指向的是一个同一个常量字符串。

.

C/C++会把常量字符串存储到单独的一个内存区域,当几个指针指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4相同。


数组指针 

1. 类比

整型指针 - 指向整型变量的指针,存放整型变量的地址的指针变量

字符指针 - 指向字符变量的指针,存放字符变量的地址的指针变量

数组指针 - 指向数组的指针,存放数组的地址的指针变量

2. 指针数组与数组指针 

p1, p2分别是什么?

int* p1[10];

int (*p2)[10];

解释:p1是指针数组,p2是数组指针。

p2先和 * 结合,说明p2是一个指针变量,然后指针指向的是一个大小为10个整型的数组。

所以p2是一个指针,指向一个数组,叫数组指针。

这里要注意:[ ]的优先级要高于 * 号的,所以必须加上()来保证p2先和 * 结合。

3. &数组名 与 数组名 

&arr与arr,输出的值是一样的

但是类型不一样

实际上:&arr表示的是整个数组的地址,而不是数组首元素的地址。

本例中&arr 的类型是:int(*)[10],是一种数组指针类型。

.

数组的地址+1,跳过整个数组的大小,所以&arr+1相对于&arr的差值是40。

.

数组地址的存放

3. 数组指针一般用在二维数组传参

void print_arr2(int (*arr)[5], int row, int col)
 {
    int i = 0;
    for(i=0; i<row; i++)
    {
        for(j=0; j<col; j++)
        {
            printf("%d ", arr[i][j]);
        }
        printf("\n");
    }
 }

 int main()
 {
    int arr[3][5] = {1,2,3,4,5,6,7,8,9,10};
    print_arr2(arr, 3, 5);
    return 0;
 }

print_arr2(arr, 3, 5);
1. 数组名arr,表示首元素的地址。
2. 但是二维数组的首元素是二维数组的第一行。
3. 所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址,需要一个数组指针来接收。


数组参数,指针参数 

1. 一维数组传参

例子1

void test(int arr[]){}//这个没问题
void test(int arr[10]){}//这个也没问题,可见对[]内的数字没有要求
void test(int *arr){}//传入数组名是首元素地址,用指针接收也没问题

int main()
{
    int arr[10] = {0}; //一维整型数组
    test(arr);
}

例子2

void test2(int *arr[20]){} //这个没问题
void test2(int **arr){} //传入数组名是首元素地址也就是一个一级指针的地址,所以用二级指针接收

int main()
{
    int *arr2[20] = {0}; //一维指针数组
    test2(arr2);
}

2. 二维数组传参  

例子1

void test(int arr[3][5]){} //正确
void test(int arr[][]){} //错误
void test(int arr[][5]){} //正确
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
 
void test(int *arr){} //错误
void test(int* arr[5]){} //错误
void test(int (*arr)[5]){} //正确
void test(int **arr){} //错误
//传入了一个整型一维数组的地址,需要一个类型为整型一维数组的指针接收

int main()
{
    int arr[3][5] = {0};
    test(arr); //传入数组名也就是首元素地址,二维数组首元素地址是一个一维数组的地址
}

3. 一级指针传参

例子1

void print(int *p, int sz)
{
    int i = 0;
    for(i=0; i<sz; i++) printf("%d\n", *(p+i));
}

int main()
{
    int arr[10] = {1,2,3,4,5,6,7,8,9};
    int *p = arr;
    int sz = sizeof(arr)/sizeof(arr[0]);
    print(p, sz); //一级指针变量p,传给函数
    return 0;
}

思考:当一个函数的参数部分为一级指针的时候,函数能接收什么参数?

4.  二级指针传参

例子1

void test(int** ptr) printf("num = %d\n", **ptr);    

int main()
{
    int n = 10;
    int*p = &n;
    int **pp = &p;

    test(pp); //pp是二级指针变量
    test(&p); //&p是一级指针地址

    return 0;
}

思考: 当函数的参数为二级指针的时候,可以接收什么参数?


函数指针 

1. 类比

数组指针 - 指向数组的指针

函数指针 - 指向函数的指针

2. 函数名 和 &函数名一样,都是函数的地址 

函数地址的存放

int (*pf)(int, int) = &Add; //pf是函数指针变量,前面返回值,后面参数
int (*)(int, int) //去掉名字是函数指针类型

3. 函数指针变量调用函数

int Add(int x, int y){return x+y;}

int main()
{
    //int (*pf)(int, int) = &Add;
    int (*pf)(int, int) = Add; //这两个写法一样

    int a = Add(3, 5);
    int b = (*pf)(3, 5);
    int c = pf(3, 5); //这三个一样,可以不写 *

    return 0;
}

4. 有趣的代码 

//代码1
(*(void(*)())0)();
//1. void(*)()是无返回值无参数的函数指针类型
//2. *(void(*)())0 对0进行强制转换,然后解引用
//3. (*(void(*)())0)() 调用0地址处的这个函数

//代码2
void (*signal(int, void(*)(int)))(int);
//1. signal(int, void(*)(int)) 这是一个函数,参数是整型和函数指针类型
//2. void (*)(int) 把刚刚的函数去掉剩下就是返回类型
//3. 所以这是一个函数声明

代码2可读性不强,我们可以用typedef
typedef void (*pf_t)(int); //将void (*)(int) 重命名为pf_t
void (*signal(int, void(*)(int)))(int); //原代码
pf_t signal(int, pf_t); //重命名后

函数指针数组

1. 类比

int* arr[5] //整型指针数组

char* arr[5] //字符指针数组

函数指针数组:数组的每个元素都是函数指针类型

2. 用函数指针实现计算器 

int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int mul(int a, int b) { return a * b; }
int div(int a, int b) { return a / b; }

void menu() { printf("0.exit, 1.add, 2.sub, 3.mul, 4.div\n"); }

int main()
{
	int (*pf_arr[])(int, int) = { NULL, add, sub, mul, div }; //函数指针数组,转移表
	int input, a, b;
	int sz = sizeof pf_arr / sizeof pf_arr[0];
	while (1)
	{
		menu();
		scanf("%d", &input);
		if (input == 0) break;
		else if (input > 0 && input <= sz)
		{
			scanf("%d %d", &a, &b);
			printf("%d\n", pf_arr[input](a, b));
		}
		else printf("input err\n");
	}

	return 0;
}

3. 指向函数指针数组的指针 

指向函数指针数组的指针是一个指针,这个指针指向一个数组,这个数组的元素都是函数指针。 如何定义?

int (*pf)(int, int); //函数指针
int (*pf_arr[4])(int, int); //函数指针数组
int (*(*p_pf_arr)[4])(int, int) = &pf_arr; //函数指针数组的地址
//p_pf_arr就是指向函数指针数组的指针

回调函数 

1. 什么是回调函数

回调函数就是一个通过函数指针调用的函数。

如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。

回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。

2. 利用回调函数实现计算器 

int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int mul(int a, int b) { return a * b; }
int div(int a, int b) { return a / b; }

void menu() { printf("0.exit, 1.add, 2.sub, 3.mul, 4.div\n"); }

int Cal(int (*pf)(int, int))
{
	int a, b;
	scanf("%d %d", &a, &b);
	printf("%d\n", pf(a, b));
}

int main()
{
	int input = 1;
	while (input)
	{
		menu();
		scanf("%d", &input);
		switch (input)
		{
		case 0:
			break;
		case 1:
			Cal(add);
			break;
		case 2:
			Cal(sub);
			break;
		case 3:
			Cal(mul);
			break;
		case 4:
			Cal(div);
			break;
		}
	}

	return 0;
}

3. qsort函数

qsort函数特点:

1. 采用快速排序的方法

2. 适合任意类型数据的排序

.

qsort函数参数

void qsort(void* base, //指向需要排序的数组第一个元素

                 size_t num, //排序个数

                 size_t size, //一个元素的大小

                 int (*cmp)(const void*, const void*) //函数指针类型,指向的函数能比较base中元素

                );

.

qsort使用

//void*的指针可以接收任意类型的地址
//但是不能直接解引用和指针运算
//需要强制转换成对应类型
int cmp_int(const void* v1, const void* v2) { return *(int*)v1 - *(int*)v2; }

int main()
{
	int arr[] = { 2, 8, 9, 6, 4, 5, 1 };
	int sz = sizeof arr / sizeof arr[0];
	qsort(arr, sz, sizeof arr[0], cmp_int);

	return 0;
}

4. 模拟实现qsort函数

冒泡排序思想:两两相邻的元素比较,不满足条件就交换

	//冒泡排序
int main()
{
	int arr[] = { 2, 8, 9, 6, 4, 5, 1 };
	int sz = sizeof arr / sizeof arr[0];
	int tmp;
	//需要比较sz-1趟
	for (int i = 0; i < sz - 1; i++)
	{
		//每一趟完成后少一个数 
		for (int j = 0; j<sz-1-i; j++)
		{
			if (arr[j] > arr[j + 1])
			{
				tmp = arr[j];
				arr[j] = arr[j + 1];
				arr[j + 1] = tmp;
			}
		}
	}

	return 0;
}

利用冒泡排序的思想模拟实现qsort函数

问题1:第一个参数如何接收不同类型。

解决1:void*的指针解决并给出个数和每个的大小

问题2:不同类型元素比较方式可能不同

解决2:将两个元素的比较方法作为函数参数传递

问题3:不同的数据类型进行交换时不一样

解决3:利用char*一次只交换一个字节,循环目标字节大小次即可

//比较
int cmp_int(const void* v1, const void* v2) { return *(int*)v1 - *(int*)v2; }
//交换
void swap(char* p1, char* p2, int sz)
{
	char tmp;
	for (int i = 0; i < sz; i++)
	{
		tmp = *p1;
		*p1 = *p2;
		*p2 = tmp;
		p1++;
		p2++;
	}
}
//排序
void bubble_sort(void* base, int num, int sz, int (*cmp)(const void*, const void*))
{
	//冒泡思想
	for (int i = 0; i < num - 1; i++)
	{
		for (int j = 0; j < num - 1 - i; j++)
		{
			//不同类型比较方式不一样所以不能写死
			if (cmp((char*)base + j * sz, (char*)base + (j + 1) * sz) > 0)
			{
				//不同类型交换方式不一样所以不能写死
				swap((char*)base + j * sz, (char*)base + (j + 1) * sz, sz);
			}
		}
	}
}

int main()
{
	int arr[] = { 2, 8, 9, 6, 4, 5, 1 };
	int num = sizeof arr / sizeof arr[0];
	bubble_sort(arr, num, sizeof arr[0], cmp_int);

	return 0;
}

练习题 

数组名表示数组首元素的地址,
但有两个例外:
1. sizeof(数组名),这里的数组名表示整个数组,求整个数组的大小
2. &数组名,这里的数组名表示整个数组

输出结果?(默认地址为4字节)

int a[] = {1,2,3,4};
 printf("%d\n",sizeof(a)); //整个数组
 printf("%d\n",sizeof(a+0)); //首元素地址加0
 printf("%d\n",sizeof(*a)); //首元素地址解引用
 printf("%d\n",sizeof(a+1)); //首元素地址加1
 printf("%d\n",sizeof(a[1])); //第二个元素
 printf("%d\n",sizeof(&a)); //整个数组的地址
 printf("%d\n",sizeof(*&a)); //整个数组的地址解引用
 printf("%d\n",sizeof(&a+1)); //整个数组的地址加1
 printf("%d\n",sizeof(&a[0])); //第一个元素的地址
 printf("%d\n",sizeof(&a[0]+1)); //第一个元素的地址加1

答:16 4 4 4 4 4 16 4 4 4

 输出结果?(默认地址为4字节)

char arr[] = {'a','b','c','d','e','f'};
 printf("%d\n", sizeof(arr)); //整个数组
 printf("%d\n", sizeof(arr+0)); //首元素地址加0
 printf("%d\n", sizeof(*arr)); //首元素地址解引用
 printf("%d\n", sizeof(arr[1])); //第二个元素
 printf("%d\n", sizeof(&arr)); //数组的地址
 printf("%d\n", sizeof(&arr+1)); //数组地址加1
 printf("%d\n", sizeof(&arr[0]+1)); //首元素地址加1

//strlen统计\0之前的字符个数
 printf("%d\n", strlen(arr)); //随机值,无法确定\0
 printf("%d\n", strlen(arr+0)); //和上一个一样
//strlen需要传入一个地址,当'a'传进去会转成ASCII码值进行地址访问
 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)); //随机值

答:6 4 1 1 4 4 4

 输出结果?(地址默认为4)

char arr[] = "abcdef";
 printf("%d\n", sizeof(arr)); //整个数组大小,字符串后面还有'\0'
 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)); //数组地址加1
 printf("%d\n", sizeof(&arr[0]+1)); //第一个元素地址加1

 printf("%d\n", strlen(arr)); //从首元素开始到'\0'之前
 printf("%d\n", strlen(arr+0)); //和上一个一样
 printf("%d\n", strlen(*arr)); //异常访问
 printf("%d\n", strlen(arr[1])); //异常访问
 printf("%d\n", strlen(&arr)); //从首元素开始到'\0'之前
 printf("%d\n", strlen(&arr+1)); //随机值,因为把'\0'跳过了
 printf("%d\n", strlen(&arr[0]+1)); //从第二个元素开始到'\0'之前

答:7 4 1 1 4 4 4

6 6 异常 异常 6 随机 5

 输出结果?(地址默认为4)

 //这里本质是把字符串"abcdef"首字符的地址放到了p中
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)); //p的地址
 printf("%d\n", sizeof(&p+1)); //p的地址加1
 printf("%d\n", sizeof(&p[0]+1)); //首字符地址加1

 printf("%d\n", strlen(p)); //从首字符开始到'\0'
 printf("%d\n", strlen(p+1)); //从第二个字符开始到'\0'
 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)); //从第二个字符开始到'\0'

答:4 4 1 1 4 4 4

6 5 异常 异常 随机 随机 5

 输出结果?(地址默认为4)

int a[3][4] = {0};
 printf("%d\n",sizeof(a)); //整个数组大小
 printf("%d\n",sizeof(a[0][0])); //第一个元素
 printf("%d\n",sizeof(a[0])); //a[0]相当于第一行这个一维数组的数组名
 printf("%d\n",sizeof(a[0]+1)); //第一行这个一维数组的首元素地址加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)); //第一行的地址加1
 printf("%d\n",sizeof(*(&a[0]+1))); //第二行的地址解引用
 printf("%d\n",sizeof(*a)); //二维数组首元素地址解引用
 printf("%d\n",sizeof(a[3])); //越界了,但sizeof只看它类型就得出结果了

答:48 4 16 4 4 4 16 4 16 16 16

解析:数组名先看是否满足两个例外之一,不满足才是首元素地址。

程序的结果是什么?

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

假设p 的值为0x100000。 如下表表达式的值分别为多少?

已知,结构体Test类型的变量大小是20个字节

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;
 }

答:00100014 00100001 00100004

解析:第二个转成无符号长整形加1就是单纯的加1

 代码结果?

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,2000000

代码结果?

#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;
 }

答:1

解析:二维数组对于行的初始化需要大括号,图中只是逗号表达式

代码结果?

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;
 }

答:fffffffc,-4

解析:%p -- 内存是什么值就打印什么值,用十六进制

代码结果?

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;
 }

 答:10,5

代码结果?

 

int main()
 {
    char *a[] = {"work","at","alibaba"}; //放的是字符串首元素地址
    char**pa = a;
    pa++;
    printf("%s\n", *pa);
    return 0;
 }

答:at

解析:a是首元素地址,所以pa也是首元素地址,pa++,*pa就是第二个元素

代码结果?

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;
 }

答:POINT ER ST EW

答:C

解析:A有点模糊,口头语上指针是指针变量 

答:C

解析:如果指针是4个字节的话,意味着地址是由32位组成,则可以形成2的32次方个地址,就可以管理2的32次方字节的空间

 代码运行结果?

答:0 0 3 4 5

 

答:C

解析:指针比较大小就是地址比较大小

 在小端机器中,代码运行结果?

答:11223300

解析:%x 按十六进制打印

 代码运行结果?

答:6 ,12

 

答:A

解析:D是数组指针

 

答:B

解析:点操作符优先级比星号高

 代码运行结果?

答:wang

  

答:D

解析:stu是通过前面的结构体类型创建的变量

 

答:D

 

答:B

//思路:要换到一直没有空瓶
int main()
{
	//输入金额,1金额等于1瓶
	int n;
	scanf("%d", &n);
	//判断喝多少瓶
	int sum = 0, flag = 0;
	while (n)
	{
		sum += n;
		if (n % 2 == 0) n /= 2; //两空瓶等于1瓶
		else
		{
			flag += n % 2; //不能整除先把余数存起来再除2
			n /= 2;
		}
		if (flag >= 2) //余出来的空瓶超过两瓶就可以换了
		{
			flag -= 2;
			sum++;
		}
	}
	printf("%d\n", sum);

	return 0;
}

 

void print(int* _arr, int _sz) { for(int i=0; i<_sz; i++) printf("%d ", *(_arr+i)); }

int main()
{
	int arr[] = { 1, 2, 3, 4 };
	int sz = sizeof(arr) / sizeof(arr[0]);
	print(arr, sz);
	return 0;
}

 

//思路:分上下两部分
int main()
{
	//输入上部分的行
	int line;
	scanf("%d", &line);
	//打印上部分行
	for(int i=0; i<line; i++)
	{
		//打印空格
		for(int j=0; j<line-1-i; j++) printf(" ");
		//打印星
		for(int j=0; j<i*2+1; j++) printf("*");
		printf("\n");
	}
	//打印下部分行
	for(int i=1; i<line; i++)
	{
		//打印空格
		for(int j=0; j<i; j++) printf(" ");
		//打印星
		for(int k=(line*2-1)-(2*i); k>0; k--) printf("*");
		printf("\n");
	}
	return 0;
}

 

int get(int num, int count)
{
	if (num == 0) return 0;
	int a = num % 10;
	num /= 10;
	return pow(a, count) + get(num, count); //pow求次方
}

int main()
{
	for (int i = 0; i <= 100000; i++)
	{
		//求i的位数
		int cot = 1;
		for (int data = i; data > 9; data /= 10) cot++; //不能在循环内改变i
		//判断
		if(get(i, cot) == i) printf("%d ", i);
	}
	return 0;
}

 

//前面一项乘10加2等于后面一项
int main()
{
	//输入目标数字和项数
	int a, n;
	scanf("%d %d", &a, &n);
	//每项求和
	int sum = 0, k = 0;
	for (int i = 0; i < n; i++)
	{
		k = k * 10 + a; 
		sum += k;
	}
	printf("%d\n", sum);
	return 0;
}

代码仓库 

Pointer/Pointer/main.c · 林宇恒/code_c - 码云 - 开源中国 (gitee.com)

标签:由浅入深,arr,int,C语言,地址,详解,数组,printf,指针
From: https://blog.csdn.net/m0_71164215/article/details/140370108

相关文章

  • C# WinForm框架入门与基本控件使用详解
    一.Winform入门​WinForm是WindowsForm的简称,是基于.NETFramework平台的客户端(PC软件)开发技术,一般使用C#编程。在VS2019中,C#WinForm编程需要创建「Windows窗体应用程序」项目。Windows窗体应用程序是C#语言中的一个重要应用,也是C#语言最常见的应用。使用......
  • 【QT开发】SQL查询QSqlQuery类详解及实战应用
    QSqlQuery是Qt提供的一个功能强大且灵活的SQL查询类,能够方便地与数据库进行交互。通过本篇文章的学习,你应该对QSqlQuery有了全面的理解,能够在自己的项目中正确使用它。QSqlQuery在用户界面中帮助你更好地管理和处理数据库数据,实现高效的数据库操作,有助于创建用户友好和高效......
  • Memcached:高性能内存缓存系统详解及实战
    引言在高并发的Web应用中,数据库往往成为性能瓶颈。为了提高数据读取速度和减少数据库负载,引入缓存机制变得至关重要。Memcached正是这样一款高性能、分布式内存对象缓存系统,它通过在内存中缓存数据来加速动态Web应用,从而极大地改善用户体验。本文将深入探讨Memcached的工作原......
  • 小白初识之C语言二
    重构:不增加代码功能,对代码结构的调整和优化,为了维护和拓展1.流程控制-顺序结构-分支结构(判断\选择)-循环结构2.分支-if-ifelse(三元运算替代)-ifelseifelseifelse(多路分支)-switch-case多路分支,有限,简洁3. 数组-一个标识符,存储多个值(大小是......
  • C语言<<左移运算符
    在C语言中,<<是位左移运算符(BitwiseLeftShiftOperator)。这个运算符用于将一个数的各二进制位全部左移若干位,由运算符右侧的数指定移动的位数,左侧操作数的位将向左移动,移动的位数由右侧操作数决定。移动过程中,左侧操作数左侧超出位数的部分将被丢弃,而在右侧增加的部分将用......
  • MyBatis Plus分页实现详解
    MyBatisPlus分页实现详解在当今的软件开发领域,数据分页是一项非常常见且重要的功能。无论是对于提升用户体验,还是减轻服务器压力,分页都扮演着至关重要的角色。MyBatisPlus作为一款优秀的ORM框架,其内置的分页功能因其高效、易用而广受开发者喜爱。本文将深入探讨MyBatisP......
  • C语言指针
    指针引用与指针引用&指针*必须初始化可以不初始化不能为空可以为空不能更换目标可以更换目标初始化案例int&r;//不合法,没有初始化引用int*p;//合法,但p为野指针,使用需要小心(1)是否需要初始化由于引用不能为空,所以我们在使用引用的时候......
  • C语言的编译和链接
    翻译环境和运⾏环境在ANSIC的任何⼀种实现中,存在两个不同的环境。第1种是翻译环境,在这个环境中源代码被转换为可执⾏的机器指令。第2种是执⾏环境,它⽤于实际执⾏代码。翻译环境那翻译环境是怎么将源代码转换为可执⾏的机器指令的呢?这⾥我们就得展开开讲解⼀下......
  • C语言中关键字static
    前言    在C语言中,static 是一个关键字,它可以用在不同的上下文中,赋予变量或函数不同的意义。static 关键字主要用于控制变量的存储期和可见性,以及函数的链接性。下面是 static 关键字的主要作用原理与用途:局部静态变量    当 static 修饰局部变量时(即......
  • 开发板测试手册——系统启动、文件传送操作步骤详解(1)
    前言本文适用开发环境:Windows开发环境:Windows764bit、Windows1064bitLinux开发环境:Ubuntu14.04.364bit虚拟机:VMware15.1.0U-Boot:U-Boot-2017.01Kernel:Linux-4.9.0、Linux-RT-4.9.0进行本文档操作前,请先按照调试工具安装、Linux开发环境搭建相关文档,安装......