指针、数组与字符串知识点整理

1.结构体嵌套时的sizeof运算

写出下面各结构体的sizeof计算结构:
struct s1 {
char a[4];
};
struct s2 {
s1 a;
char b;
};
结构体s1所占用的空间为4个字节。
结构体s2的第一个成员a占用4个空间,第二个成员b占用1个空间,而结构体s1类型中占用空间最大的类型为char类型,是1个字节,因此结构体s2的sizeof运算结果只要是1的整数倍即可。最终结构体s2的sizeof运算结果为5.

2.指针与数组的区别。

char str1[15] = “hello world”;
char str2[15] = “hello world”;
尽管数组里面的内容完全一样,但是str1和str2这两个数组名等价于各自数组首元素的地址,所以str1 != str2。
char *str2 = “hello world”;
cha * str3 = “hello world”;
通过指针指向了同一地址,因此str2 == str3表达式的结果为真。

3.指针常量与常量指针

指针常量:本质上是一个常量,指针用来说明常量的类型,表示该常量是一个指针类型的常量。在指针常量中,指针自身的值是一个常量,不可改变,始终指向同一个地址。指针常量的用法如下:

int a = 10, b = 20;
int * const p = &a;
*p = 30;

const是用来修饰p的,因此p是常量,内容是不能改变的。*p是可以改变的,修改后p指向的内容为30.
常量指针:本质上是一个指针,常量表示指针指向的内容,说明指针指向一个“常量”。在常量指针中,指针指向的内容是不可改变的,指针看起来就好像指向了一个常量。常量指针的用法如下:

int a = 10, b = 20;
const int *p = &a;
p = &b;

第二行const与 * p 相邻,说明const是用来修饰 * p 的,因此 * p的值是不可改变的,即p指向的内容不可以通过p来修改,也就是说无法通过给 * p赋值来修改p指向的内容。

4.指针数组与数组指针

指针数组:存放指针的数组。
指针数组本质上是一个数组,指针是数组中的内容,表示数组中的每个元素都是指针,因此指针数组就是存放指针的数组。下面通过一个例子来了解指针数组的用法:

int a = 10, b = 20;
int *p[2];
p[0] = &a;
p[1] = &b;

在指针数组的定义中,根据运算符优先级,[ ]的优先级高于*的优先级,因此变量p与[2]结合,表明p是一个数组,而数组的元素的类型在变量的左侧声明,因此数组p是int * 类型的数组。
数组指针:指向数组的指针。
数组指针本质上是一个指针,数组是指针指向的类型,表示指针指向一个数组,因此数组指针就是指向数组的指针,下面通过一个例子来了解数组指针的用法:

int a[3] = {1,2,3};
int (*p)[3];
p = &a;

上面的代码定义了一个数组指针p,通过将p与* 括起来,使变量p先与*结合起来,表明p是一个指针,然后将(*p)遮住,剩余部分就是指针指向的类型,也就是int[3]类型,综上所述,变量p是一个指针,指针指向一个长度为3的int类型的数组,因此p是一个数组指针。

5.简述指针数组与指向指针的指针的区别

写出指针数组str中四个指针元素的值。

int main() {
	char * str[4] = {"welcome", "to", "new", "Beijing"};
	char ** p = str + 1;

	str[0] = (*p++) + 1;
	str[1] = *(p + 1);
	str[2] = p[1] + 3;
	str[3] = p[0] + (str[2] - str[1]);
char ** p = str +1;

数组名str等价于数组首元素的地址,而str+1表示数组中第二个元素的地址&str[1],因此指针p指向数组str中的第二个元素str[1],而str[1]本身也是指针,指向字符串“to”的首字符t,因此指针p是一个指向指针的指针。*p指向字符串“to”的首字符t。此外 *p++ 中的 *p没有括号并且是后++,因此赋值语句相当于执行了str[0] = *p + 1后再执行p++,而 *p+1指向字符串“to”的第二个字符o,因此str[0]指向了字符o,p++后p指向指针数租str的第三个元素str[2]。

str[1] = * (p + 1);
str[2] = p[1] + 3;

此时,p指向str[2],p + 1指向str[3],也就是p + 1 = &str[3],解引用可得*(p+1)= str[3],因此str[1] = str[3]。由于str[3]指向字符串“Beijing”的首字母B,因此str[1]也指向同样的位置。
在分析str[2]时,首先应该明确p[1]等价于*(p+1),这是访问数组元素的两种方式。根据对str[1]的分析可知,*(p+1)指向字符串“Beijing”的首字符B,也就是p[1]指向字符串“Beijing”的首字符B,p[1]+3就指向字符串“Beijing”的第四个字符j,因此str[2]就指向字符串“Beijing”的第四个字符j。

str[3] = p[0] + (str[2] - str[1]);

表达式str[2]-str[1]表示两个指针之间元素的个数,str[2]指向字符串“Beijing”的第四个字符j,而str[1]指向字符串“Beijing”的首字符B,相差3个字符,因此str[2]-str[1]=3。此外,p[0]等价于*(p+0),也就是*p,而 *p=str[2],因此p[0]同样指向字符串“Beijing”的第4个字符j,而p[0]+3就指向字符串“Beijing”的第7个字符g.

6. 简述指针与引用的区别
  • 指针是变量的地址,引用是变量的别名。
    sizeof运算的运算结果不同,指针进行sizeof运算得到的是指针本身占用的空间,返回结果为4个字节;而引用进行sizeof运算得到的是原变量占用的空间,返回结果取决于原变量的数据类型。
    自增++运算符的意义不同,指针进行自增运算是对指针本身自增,使指针指向下一个地址空间,指针指向的变量没有改变;引用进行自增运算是对原变量的自增,改变原变量的值。
  • 指针可以不初始化,引用必须初始化。
  • 指针本身可以被修改,引用本身不能被修改。
  • 指针可以为NULL,引用不能为NULL。
  • 指针可以定义为二重指针,引用不能定义二重引用。
  • 指针需要解引用,引用直接使用。
7. 程序中的变量存储区

下面程序中的变量定义都涉及哪些存储区?

int a = 0;
int main() {
	char ch = 'a';
	static int c = 0;
	char *p1 = "abc";
	char *p2 = "abc";
	char *p3 = &ch;
	char *p4 = (char *) malloc(10);

程序在main函数外定义并初始化了全局变量a,在main函数内定义并初始化了静态变量c,它们都保存在全局及静态存储区。
字符变量ch以及四个指针变量p1、p2、p3、p4都是普通的局部变量,它们保存在栈存储区。虽然四个指针指向了其他地址空间,这些地址空间位于不同的数据区段,但是指针变量本身保存在栈存储区。
字符串常量“abc”保存在字符串常量区,指针p1和p2指向同一个字符串常量,所以两个指针变量的值是相同的,指向字符串常量区的同一个地址。
程序最后一行通过malloc在堆存储区中动态申请了一块空间,并返回这块空间的首地址,指针p4指向这个地址。
四个指针变量中,p1和p2指向常量存储区中的地址,p3指向栈存储区中的地址,p4指向堆存储区中的地址,当然还可以创建指向全局和静态存储区的指针变量,但是无论指针指向哪个存储区,指针变量本身都保存在栈存储区。

8.简述栈空间和堆空间的区别
  • 栈空间用于存储函数参数和局部变量,所需空间有系统自动分配,回收也由系统管理,无需人工干预;堆空间用于存储动态分配的内存块,分配和释放空间均由程序员控制,有可能产生内存泄漏。
  • 栈空间作为一个严格后进先出的数据结构,可用空间永远都是一块连续的区域;堆空间在不断分配和释放空间的过程中,可用空间链表频繁更新,造成可用空间逐渐碎片化,每块可用空间都很小。
  • 栈空间默认大小只有几M的空间,生长方式是向下的,也就是向着内存地址减小的方向消耗空间;堆空间的理论大小只有几G的空间,生长方式是向上的,也就是随着内存地址增大的方向消耗空间。
  • 栈空间有计算机底层的支持,压栈和出栈都有专门的指令,效率较高;堆空间通过函数动态获取空间,涉及可用空间链表的扫描和调增以及相邻可用空间的合并等操作,效率相对较低。
9.简述递归程序潜在的风险

比如二叉树遍历算法的递归实现:

void preOrder(BiTree *root) {
	if(node != NULL) {
		visit(root);
		preOrder(root->lchild);
		preOrder(root->rchild);
		}
}

递归算法存在一个问题:当递归层数过深时,有可能产生栈溢出。在递归调用过程中,每一次递归调用都会保留现场,把当前的上下文压入函数栈,随着递归调用层数的深入,压入函数栈的内容会越来越多,直到函数栈的空间用尽,而递归程序仍然没有满足返回的条件,继续向更深的一层调用,就会发生栈溢出。
下面是二叉树遍历的循环实现。

void preOrder(BiTree *root) {
	stact <BiTree *> s;
	BiTree *p = root;
	while(p != NULL || !s.empty()) {
		while(p != NULL) {
			visit(p);
			s.push(p);
			p = p -> lchild;
			}
		if(!s.empty()) {
			p = s.top();
			s.pop();
			p = p ->rchild;
		}
	}
}
10.不使用C/C++库函数,编程实现strcmp的功能

完整的算法描述如下:

int mystrcmp(const char *str1, const char *str2) {
	if(str1 == NULL || str2 == NULL) {
		printf("The string is error!\n");
		exit(0);
	}
	while(*str1 != '\0' && *str2 != '\0' && *str1 == *str2) {
		str1++;
		str2++;
	}
	if(*str1 != '\0' && *str2 == '\0') {
		return 1;
	}
	else if(*str1 == '\0' && *str2 != '\0') {
		return -1;
	}
	else if(*str1 > *str2) {
		return 1;
	}
	else if(*str1 < *str2) {
		return 1;
	}
	
11.编程实现strcpy的功能
char *mystrcpy(char *str1, const char *str2) {
	char *p = str1;
	if(p == NULL || str2 == NULL) {
		printf("The string is error!\n");
		exit(0);
	}
	while(*str2 != '\0') {
		*p = *str2;
		p++;
		str2++;
	}
	*p = '\0';
	return str1;
}
12.编程实现函数strstr的功能
char * mystrstr(const char *str1, const char *str2) {
	char *src, *sub;
	if(str1 == NULL || str2 == NULL) {
		printf("The string is error!\n");
		exit(0);
	}
	while(*str1 != '\0') {
		src = str1;
		sub = str2;
		do {
			if(*sub == '\0') {
				return str1;
			}
		}
		while(*src ++ == *sub++);
		str1++;
	}
	return NULL;
}

13.简述memcpy与strcpy的区别
memcpy函数是C语言中的内存拷贝函数,它只提供了一般的内存拷贝功能,而不是针对字符串的。memcpy函数的原型为:

void *memcpy(void * dest, const void *src, size_t count);

strcpy是专为字符串拷贝定义的函数,其函数原型为:

char *strcpy(char *str1, char *str2);

其作用是将指针str2指向的字符串拷贝到指针str1指向的连续内存空间中,在使用strcpy进行字符串拷贝时,不仅拷贝字符串的内容,还会拷贝字符串的结束标志‘\0’。使用strcpy进行字符串拷贝时不需要指定拷贝的长度,函数会自动查找结束符‘\0’。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值