C语言从入门到精通——进阶2 指针进阶

字符指针

int main()
{
	//本质上是把"hello bit"这个字符串的首字符的地址存储在了ps中
	char* ps = "hello bit";
	printf("%c\n", *ps);//h

	char arr[] = "hello bit";//把字符串放入字符数组
	printf("%s\n", ps);//hello bit  %s 给起始位置的地址打印字符串
	printf("%s\n", arr);//hello bit

	//*ps = 'w';//err不能更改 引发了异常: 写入访问权限冲突。
	arr[0] = 'w';//可以更改
	printf("%s\n", arr);//wello bit
	return 0;
}
int main()
{
    char str1[] = "hello bit.";
    char str2[] = "hello bit.";
    char* str3 = "hello bit.";//常量字符串,不能更改
    char* str4 = "hello bit.";
    //*str3 = 'w';err 

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

int main()
{
	//char ch = 'w';
	//char* pc = &ch;//pc是字符指针char* 

	char arr[] = "abcdef";
	char* pc = arr;//字符指针存放数组名
	printf("%s\n", arr);
	printf("%s\n", pc);//pc指向了字符数组

	return 0;
}
int main()
{
	const char* p = "abcdef";//"abcdef"是一个常量字符串//p里面存的a的地址,是把字符串的首字符的地址赋给p
	printf("%s\n", p);//abcdef
	printf("%c\n", *p);//a
	return 0;
}
int main()
{
	char arr1[] = "abcdef";
	char arr2[] = "abcdef";
	const char* p1 = "abcdef";
	const char* p2 = "abcdef";
	if (arr1 == arr2)
	{
		printf("hehe1\n");
	}
	else
	{
		printf("haha1\n");
	}
	//输出haha
	//arr1,arr2是首元素地址
	if (p1==p2)
	{
		printf("hehe2\n");
	}
	else
	{
		printf("haha2\n");
	}
	return 0;
	//输出hehe
	//"abcdef"是常量字符串,在内存里只存了一份,两个指针都指向字符串首地址值
}

 指针数组

是数组,用来存放指针的数组

int main()
{
	int arr[10] = { 0 };//整形数组
	char arr[5] = { 0 };//字符数组
	int* parr[4];//存放整形指针的数组-指针数组
	int* ch[10];//存放字符指针的数组-指针数组
	return 0;
}

int main()
{
	int arr1[] = { 1,2,3,4,5 };
	int arr2[] = { 2,3,4,5,6 };
	int arr3[] = { 3,4,5,6,7 };

	int* parr[] = { arr1,arr2,arr3 };//首元素地址

	int i = 0;
	for (i = 0; i < 3; i++)
	{
		int j = 0;
		for (j = 0; j < 5; j++)
		{
			printf("%d ",*(parr[i] + j));
            //等价于printf("%d",&parr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

数组指针

是指针,指向数组的指针,存放数组的地址

int main()
{

	int* p = NULL;//p是整形指针,指向整形的指针-可以存放整形的地址
	char* pc = NULL;//pc是字符指针-指向字符的指针-可以存放字符的地址
	//数组指针-指向数组的指针-可以存放数组的地址
	int arr[10] = { 0 };
	//arr-首元素地址
	//&arr[0]-首元素的地址
	//&arr-数组的地址
	 
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int(*p)[10] = &arr;//p是数组指针,指向数组,数组的类型是int
	//int* parr[4]指针数组

	return 0;
}

int arr[10];

  • 数组名     arr表示数组首元素的地址
  • &数组名  &arr表示数组的地址

数组指针一般在二维数组以上使用

//参数是数组的形式
void print1(int arr[3][5], int x, int y)
{
	int i = 0;
	int j = 0;
	for (i = 0; i < x; i++)
	{
		for (j = 0; j < y; j++)
		{
			printf("%d ", arr[i][j]);
		}
		printf("\n");
	}
}
//参数是指针的形式
void print2(int(*p)[5], int x, int y)
{
	int i = 0;
	for (i = 0; i < x; i++)
	{
		int j = 0;
		for (j = 0; j < y; j++)
		{
			//printf("%d ", *(*(p + i) + j));
			printf("%d ", (*(p + i))[j]);
		}
		printf("\n");
	}
}
int main()
{
	int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
	print1(arr,3,5);
	printf("\n");
	print2(arr, 3, 5);//arr-数组名-数组名就是首元素地址
                      //二维数组的首元素是第一行

	return 0;
}

// int(* parr[10])[5] 是数组,十个元素,每个元素是数组指针(能够指向一个数组,数组5个元素,每个元素是int类型)

数组传参和指针传参

 //一维数组传参,参数部分可以写成数组,也可以写成指针

int arr[10]={0};
int* arr2[20] = { 0 };

test(arr);
void test(int arr[])
void test(int arr[10])
void test(int *arr)

test2(arr2);
void test2(int* arr2[20])
void test2(int* arr2[])
void test(int **arr)//传的是一级指针的地址

//二维数组的传参

void test(int arr[3][5])
{}
void test(int arr[][5])
{}
void test(int arr[3][])//错的,行可以省略,列不能省略
{}
void test2(int (*arr)[5])
{}

int main()
{
	int arr[3][5] = { 0 };
	//test(arr);
	test2(arr);
	return 0;
}
  •  当一个函数参数部分是一级指针时,函数能接收什么参数?

变量的地址和存放地址的一级指针变量

  • 当一个函数参数部分是二级指针时,函数能接收什么参数?

一级指针变量的地址、二级指针变量本身、存放一级指针的数组的数组名

函数指针

函数指针-指向函数的指针-存放函数地址的指针

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

	//&函数名 和函数名 取出的都是函数的地址
	printf("%p\n", &Add);
	printf("%p\n", Add);
	//int (*pa)(int, int) = &Add;
	int (*pa)(int, int) = Add;//Add等价于pa
	printf("%d", (*pa)(2, 3));
	printf("%d", (*Add)(2, 3));

	printf("%d", Add(2, 3));
	printf("%d", pa(2, 3));

	return 0;
}
void print(char* str)
{
	printf("%s", str);
}
int main()
{
	char str ;
	void(*p)(char*)= print;
	(*p)("hello");
	return 0;
}
(*(void(*)())0)()
//调用0地址处放的函数,该函数无参,返回类型是void
//(void(*)())0  对0进行强制类型转换,被解释为一个函数地址
//* (void(*)())0   对0地址进行解引用
//(*(void(*)())0)() 调用0地址处的函数

函数指针数组

存放同类型的函数指针的数组   取出函数指针数组的地址

指向函数指针数组的指针

指向函数指针数组的指针:是一个指针,指向一个数组,数组的元素都是函数指针

int Add(int x, int y)
{
	return x + y;
}
int main()
{  
	//指针数组
	int* arr[10];
	//数组指针
	int* (*pa)[10] = &arr;
	//函数指针
	int (*pAdd)(int, int) = Add;
	int sum = (*pAdd)(1, 2);
	int sum = pAdd(1, 2);
	         //Add(1,2);
	printf("sum=%d\n", sum);
	//函数指针的数组
	int(*pArr[5])(int, int);
	//指向函数指针数组的指针
	int(*(*ppArr)[5])(int, int) = &pArr;
	return 0;
}

回调函数

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

qsort-库函数-排序


头文件<stdlib.h>
void qsort(void* base, size_t num, size_t size,int (*compar)(const void*, const void*));
第一个参数:待排序数组的首元素地址
第二个参数:待排序数组的元素个数
第三个参数:待排序数组的每个元素的大小-单位是字节
第四个参数:是函数指针,比较两个元素的所用函数的地址-这个函数使用者自己实现 
               函数指针的两个参数是:待比较的两个元素的地址  

void*类型的指针,可以接收任意类型的地址
void*类型的指针,不能进行解引用操作
void*类型的指针,不能进行+-整数的操作 

指针和数组面试题的解析

数组名是首元素的地址
    1.sizeof(数组名)-数组名表示整个数组
    2.&数组名-数组名表示整个数组

 //一维数组

    int a[] = { 1,2,3,4 };
	printf("%d\n", sizeof(a));//sizeof(数组名)-计算的是数组的总大小-单位是字节-4*4=16 
	printf("%d\n", sizeof(a+0));//数组名这里表示首元素地址,a+0还是首元素地址,地址的大小就是4/8字节
	printf("%d\n", sizeof(*a));//数组名表示首元素地址,*a就是首元素
	printf("%d\n", sizeof(a+1));//数组名这里表示首元素地址,a+1是第二个元素地址,地址的大小就是4/8字节
	printf("%d\n", sizeof(a[1]));//第二个元素的大小
	printf("%d\n", sizeof(&a));//&a取出的是数组的地址,但是数组的地址也是地址,地址的大小就是4/8个字节
	printf("%d\n", sizeof(*&a));//数组的地址解引用是访问的是数组,计算的数组大小,单位是字节
	printf("%d\n", sizeof(&a+1));//&a是数组的地址,&a+1虽然地址跳过整个数组,但还是地址,地址的大小
	printf("%d\n", sizeof(&a[0]));//第一个元素的地址
	printf("%d\n", sizeof(&a[0]+1));//第二个元素的地址

//字符数组

    char arr[] = { 'a','b','c','d','e','f' };
    printf("%d\n", sizeof(arr));//sizeof计算的是数组的大小,6*1=6字节
    printf("%d\n", sizeof(arr+0));//数组名arr这里表示首元素地址,a+0还是首元素地址,地址的大小就是4/8字节
    printf("%d\n", sizeof(*arr));//arr是首元素地址,*arr是首元素,char1个字节
    printf("%d\n", sizeof(arr[1]));//第二个元素的大小
    printf("%d\n", sizeof(&arr));//&arr取出的是数组的地址,但是数组的地址也是地址,地址的大小就是4/8个字节
    printf("%d\n", sizeof(&arr+1)); //&arr是数组的地址,& arr + 1虽然地址跳过整个数组,但还是地址,地址的大小
    printf("%d\n", sizeof(&arr[0]+1));//第二个元素的地址

    printf("%d\n", strlen(arr));//随机值,找\0
    printf("%d\n", strlen(arr+0));//随机值
    printf("%d\n", strlen(*arr));//err——strlen要的是地址,*arr相当于是首元素,崩溃了
    printf("%d\n", strlen(arr[1]));//err——strlen要的是地址,第二个元素,崩溃了
    printf("%d\n", strlen(&arr));//随机值
    printf("%d\n", strlen(&arr+1));//随机值
    printf("%d\n", strlen(&arr[0]+1));//随机值
char arr[] = "abcdef";//数组里面放的是a,b,c,d,e,f,\0

    printf("%d\n", sizeof(arr));//7,sizeof(arr)计算的数组的大小,单位是字节
    printf("%d\n", sizeof(arr + 0));//4,计算的是首元素地址的大小,arr+0是首元素的地址
    printf("%d\n", sizeof(*arr));//1,*arr是首元素,计算首元素的大小
    printf("%d\n", sizeof(arr[1]));//1,第二个元素
    printf("%d\n", sizeof(&arr));//4,数组的地址
    printf("%d\n", sizeof(&arr + 1));// 4,跳过整个数组后的地址
    printf("%d\n", sizeof(&arr[0] + 1));//4,第二个元素的地址

    printf("%d\n", strlen(arr));//6,首元素地址
    printf("%d\n", strlen(arr + 0));//6,首元素地址
    //printf("%d\n", strlen(*arr));//err——strlen要的是地址,*arr相当于是首元素,崩溃了
    //printf("%d\n", strlen(arr[1]));//err——strlen要的是地址,第二个元素,崩溃了
    //printf("%d\n", strlen(&arr));//err——&arr类型为数组的地址——数组指针char (*)[7],strlen参数类型const char *
    //printf("%d\n", strlen(&arr+1));//err——下个数组的地址
    printf("%d\n", strlen(&arr[0] + 1));//5,第二个元素开始向后数
    const char* p = "abcdef";//把常量字符串a的地址放在p中

    printf("%d\n", sizeof(p)); // 4,p是首元素的地址,是指针变量-计算指针变量p的大小
    printf("%d\n", sizeof(p + 1)); // 4,p+1是第二个元素字符b的地址,是指针
    printf("%d\n", sizeof(*p)); // 1,指针解引用等于字符串的第一个字符
    printf("%d\n", sizeof(p[0]));//1,p[0]=a 
    printf("%d\n", sizeof(&p)); // 4,二级指针,是地址
    printf("%d\n", sizeof(&p + 1));// 4,跳过p,还是地址
    printf("%d\n", sizeof(&p[0] + 1));//4,第二个元素b的地址

    printf("%d\n", strlen(p));//6,从a往后数
    printf("%d\n", strlen(p + 1));// 5,从b往后数
    //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));// 5,从第二个元素往后数

//二维数组

    int a[3][4] = { 0 }; //注意:二维数组,也可以看作一维数组,只不过数组里面放的元素是数组
    printf("%d\n", sizeof(a)); // 48  数组总大小12*4=48
    printf("%d\n", sizeof(a[0][0]));// 4 a[0][0]一行一列的元素,数组中首位元素
    printf("%d\n", sizeof(a[0]));// 16 a[0]相当于第一行作为一维数组的数组名,把数组名单独放在sizeof()内,计算的是第一行的大小 4*4
    printf("%d\n", sizeof(a[0] + 1));// 4 a[0]是第一行的数组名,数组名此时是(首元素)第一行第一个元素的地址,+1 代表第一行第二个元素地址
    printf("%d\n", sizeof(*(a[0] + 1)));//4 同上 ,只不过对上面进行解引用,是第一行第二个元素 大小为4
    printf("%d\n", sizeof(a + 1)); //4 a是二维数组的数组名,没有sizeof(数组名),也没有&(数组名),所以a是首元素地址
    //二维数组的首元素是第一行,a就是第一行(首元素)的地址,a+1代表数组中第二行一维数组的地址,大小为4
    printf("%d\n", sizeof(*(a + 1)));//16 *(a+1)) 解引用代表数组中第二个“元素”,计算第二行的大小,4*4
    printf("%d\n", sizeof(&a[0] + 1));//4  第二行的地址,大小为4
    printf("%d\n", sizeof(*(&a[0] + 1)));//16 第二行元素4*4
    printf("%d\n", sizeof(*a)); //16 a是二维数组的首元素地址,*a是第一行 4*4
    printf("%d\n", sizeof(a[3])); //16 越界访问,但能算出长度为16(sizeof内部表达式不参与计算) 

char** cpp; 

cpp[-2][-1]==>*(*(cpp-2)-1)

 

指针作业讲解

 //杨氏矩阵:有一个二维数组. 数组的每行从左到右是递增的,每列从上到下是递增的. 在这样的数组中查找一个数字是否存在。 时间复杂度小于O(n)

int FindNum(int arr[3][3], int k, int row, int col)
{
	int x = 0;
	int y = col - 1;

	while (x <= row - 1 && y >= 0)
	{
		if (arr[x][y] > k)
		{
			y--;//当前数组右上角元素大于要找元素时,一定不在第一行,删掉一行
		}
		else if (arr[x][y] < k)
		{
			x++; //当前数组右上角元素小于要找元素时,一定不在最右边一列,删掉一列
		}
		else
		{
			return 1;
		}
	}
	return 0;//找不到
}

 //实现一个函数,可以左旋字符串的k个字符

暴力求解法

#include <string.h>
#include<assert.h>

void left_move(char* arr,int k)
{
	assert(arr);
	int i = 0;
	int len = strlen(arr);
	for (i = 0; i < k; i++)
	{
		//左旋转一个字符
		char tmp = *arr;
		int j = 0;
		for (j = 0; j < len - 1; j++)
		{
			*(arr + j) = *(arr + j + 1);
		}
		*(arr + len - 1) = tmp;
	}
}

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

	printf("%s\n", arr);
	return 0;
}

三部翻转法

void reverse(char* left, char* right)
{
	assert(left != NULL);
	assert(right != NULL);
	while (left < right)
	{
		char tmp = *left;
		*left = *right;
		*right = tmp;
		left++;
		right--;
	}

}

void left_move(char* arr, int k)
{
	assert(arr);
	int len = strlen(arr);
	assert(k <= len);
	reverse(arr,arr+k-1);//逆序左边
	reverse(arr+k,arr+len-1);//逆序右边
	reverse(arr,arr+len-1);//逆序整体
}

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

	printf("%s\n", arr);
}

//写一个函数,判断一个字符串是否为另外一个字符串旋转之后的字符串

int is_left_move(char* s1, char* s2)
{
	int len = strlen(s1);
	int i = 0;
	for (i = 0; i < len; i++)
	{
		left_move(s1, i);
		int ret=strcmp(s1, s2);
		if (ret == 0)
			return 1;
	}
	return 0;
}

也可以在str1后面追加str1,然后判断str2是否为子串

int is_left_move(char* str1, char* str2)
{
	int len1 = strlen(str1);
	int len2 = strlen(str2);
	if (len1 != len2)//两串长度不一样时,提前返回0
		return 0;

	strncat(str1, str1, 6);//在str1字符串中追加一个str1字符串
	char* ret=strstr(str1, str2);//找子串
	if (ret == NULL)
	{
		return 0;
	}
	else
	{
		return 1;
	}
}

之前作业题目

 //指针函数逆序字符串

#include<string.h>
#include<assert.h>

void reverse(char* str)
{
	assert(str);
	int len = strlen(str);
	char* left = str;
	char* right = str + len - 1;

	while (left < right)
	{
		char tmp = *left;
		*left = *right;
		*right = tmp;

		left++;
		right--;
	}
}

int main()
{
	char arr[256] = { 0 };
	//scanf("%s", arr);
	gets_s(arr);//录入一行
	reverse(arr);//逆序

	printf("%s\n", arr);
	return 0;
}

//求Sn=a+aa+aaa+aaaa+aaaaa的前n项之和,其中a是一个数字

int Sum(int a,int n)
{
	int sum = 0;
	int i = 0;
	int ret=0;
	for (i = 0; i <n; i++)
	{
		ret= a + ret * 10;
		sum += ret;
	}
	return sum;
}

int main()
{
	int a = 0;
	int n = 0;
	scanf("%d%d", &a, &n);

	printf("%d", Sum(a, n));

	return 0;
}

//求自幂数:如果在一个固定的进制中,一个n位自然数等于自身各个数位上数字的n次幂之和,则称此数为自幂数。(n=3时,为水仙花数)

#include <math.h>
int main()
{
	int i = 0;
	for (i = 0; i <= 1000000; i++)
	{
		//1.计算i的位数
		int n = 1;
		int tmp = i;
		int sum = 0;
		while (tmp/=10)
		{
			n++;
		}
		//2.计算i的每一位的n次方之和sum
		tmp = i;
		while (tmp)
		{
			sum += pow(tmp % 10, n);
			tmp /= 10;
		}
		//3.比较i和sum
		if (i == sum)
		{
			printf("%d ", i);
		}
	}
	return 0;
}

//打印n行菱形

      *

     ***

    *****

   *******

    *****

     ***

      *

int main()
{
	int line = 0;
	scanf("%d", &line);//只有总行数为奇数时可以打印,输入上半部分行数
	//打印上半部分
	int i = 0;
	for (i = 0; i < line; i++)
	{
		//打印空格
		int j = 0;
		for (j = 0; j<line-1-i; j++)
		{
			printf(" ");
		}
		//打印*
		for (j = 0; j < 2*i+1; j++)
		{
			printf("*");
		}
		printf("\n");
	}
	//打印下半部分
	for (i = 0; i < line-1; i++)
	{
		//打印空格
		int j = 0;
		for (j = 0; j < i+1; j++)
		{
			printf(" ");
		}
		//打印*
		for (j = 0; j < 2 * (line-i-1) - 1; j++)
		{
			printf("*");
		}
		printf("\n");
	}
	return 0;
}

//喝汽水,1瓶汽水1元,2个空瓶可以换一瓶汽水,给20元,可以喝多少汽水

int main()
{
	int money = 0;
	int total = 0;
	int empty = 0;
	scanf("%d", &money);
	//买回来的汽水喝掉
	total = money;
	empty = money;
	//换回来的汽水
	while (empty >= 2)
	{
		total += empty / 2;
		empty=empty / 2 + empty % 2;
	}
	printf("%d", total);
}

 //调整数组使奇数全部都位于偶数前面
//从左边开始找偶数,从右边开始找奇数,把他们交换

void move(int arr[], int sz)
{
	int left = 0;
	int right = sz - 1;
	while (left < right)
	{
		while ((left < right)&&(arr[left] % 2 == 1))
		{
			left++;
		}
		while ((left < right)&&(arr[right] % 2 == 0))
		{
			right--;
		}
		if (left < right)
		{
			int tmp = arr[left];
			arr[left] = arr[right];
			arr[right] = tmp;
		}
	}
}

 //打印杨辉三角

int main()
{
	int arr[10][10] = { 0 };
	int i = 0;
	int j = 0;
	for (int i = 0; i < 10; i++)
	{
		for (int j = 0; j < 10; j++)
		{
			if (j == 0)
			{
				arr[i][j] = 1;
			}
			if (i==j)
			{
				arr[i][j] = 1;
			}
			if((i>=2)&&(j>=1)&&(j!=i))
			arr[i][j] = arr[i-1][j-1] + arr[i-1][j];
		}
	}
	for (int i = 0; i < 10; i++)
	{
		for (int j = 0; j <=i; j++)
		{
			printf("%3d ", arr[i][j]);
		}
		printf("\n");
	}
	return 0;
}

//逻辑推理:判断凶手是谁问题

int main()
{
	int killer = 0;
	for (killer = 'a'; killer <= 'd'; killer++)
	{
		if ((killer != 'a') + (killer == 'c') + (killer == 'd') + (killer != 'd') == 3)
		{
			printf("killer=%c\n", killer);
		}
	}
	return 0;
}

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值