[C++基础] 数组、指针、内存篇

1、数组

2.1 int a[2][2]= { {1}, {2,3} },则 a[0][1] 的值是多少?

二维数组的初始化通常有两种方式:c++

  • 第一种方式是按行来执行,如int array\[2][3]= { {0,0,1}, {1,0,0} };
  • 而第二种方式是把数值写在一块,如int array\[2][3]= { 0,0,1,1,0,0 };

<span style="color:blue">若只对部分元素进行初始化,数组中未赋值的元素自动为赋值为 0,</span>因此 a[0][1] 的值是0。程序员

<br />编程

2.2 a是数组,(int*)(&a+1) 表示什么意思?

表示 int 类型的数组指针,若 a 为数组 a[5],则(int*)(&a+1)为 a[5]。数组

示例程序以下:安全

#include <stdio.h> 

void main()
{
	int a[5]={1,2,3,4,5};
	int b[ 100];
	int *ptr=(int*)(&a+1); 
	printf("%d %d\n",*(a+1),*(ptr-1));  // 2 5
	printf("sizeof(b)=%d\n",sizeof(b)); // sizeof(b)=400
	printf("sizeof(&b)=%d\n",sizeof(&b)); // sizeof(&b)=4
}

<span style="color:blue">&a 是数组指针,是一个指向 int (*)[5] 的指针,因此 &a+1 的地址是 &a 地址再加 5*sizeof(int),它的运算单位是 int(*)[5]。通过类型转换后,ptr 至关于 int *[5]。</span>数据结构

ptr-1 的单位是 ptr 的类型,所以 ptr-1 的位置恰好是 a[4]。由于 ptr 与 (&a+1) 类型是不同的,因此 ptr-1 只会减去 sizeof(int*)。函数

<span style="color:blue">值得注意的是,a 和 &a 的地址是同样的,但意思不同,a 是数组首地址,也就是 a[0] 的地址;&a 是 对象(数组)首地址;a+1 是数组下一元素的地址,即 a[1];而 &a+1 是下一个对象的地址, 即 a[5]。</span>优化

<br />编码

2.3 不使用流程控制语句,如何打印出1 ~ 1000的整数?

采用构造函数与静态构造变量结合的方法实现。<span style="color:darkOrange">首先在类中定义一个静态成员变量,而后在构造函数里面打印该静态变量的值,并对静态变量进行自增操做,同时在主函数里面定义一个类数组,</span>程序代码示例以下:spa

class print
{
public:
	static int a;

	print()
	{
		printf("%d\n",print::a);
		a++;
	}
};
int print::a = 1;

int main()
{
	print tt[100]; 
	
	return 0;
}

<br />

2.4 int id[sizeof(unsigned long)]; 这个对吗?为何?

答案:正确。<span style="color:darkOrange">这个 sizeof 是编译时运算符,编译时就肯定了 ,能够当作和机器有关的常量。</span>

<br />

扩展: 如下代码可以编译经过吗,为何?

const int size1 = 2;
char str1[size1];

int temp = 0;
const int size2 = temp;
char str2[size2];

<span style="color:blue">str1 能经过编译,而 str2 定义出错,size2 非编译器期间常量,而数组定义要求长度必须为编译期常量。</span>

<br />

2、指针

1 使用指针有哪些好处?

通常而言,使用指针有如下几个方面的好处:

(1)<span style="color:blue">能够动态分配内存;</span>

(2)<span style="color:blue">进行多个类似变量的通常访问;</span>

(3)<span style="color:blue">为动态数据结构,尤为是树和链表,提供支持;</span>

(4)遍历数组,如解析字符串;

(5)高效地按引用 “复制” 数组与结构,特别是做为函数参数的时候,能够按照引用传递函数参数,提升开发效率。

<br />

2 引用和指针的区别?

(1)<span style="color:blue">引用只能在定义时被初始化一次,以后不能被改变,即引用具备“从一而终”的特性。而指针倒是可变的;</span>

(2)<span style="color:blue">引用使用时不须要解引用(*),而指针须要解引用;</span>

(3)<span style="color:blue">引用不能够为空,而指针能够为空;</span>

(4)<span style="color:blue">对引用进行 sizeof 操做获得的是所指向的变量(对象)的大小,而对指针进行 sizeof 操做获得的是指针自己(所指向的变量或对象的地址)的大小;</span>

(5)做为参数传递时,二者不一样。引用传递参数是 “引用传递”,会经过一个间接寻址的方式操做到主调函数中的相关变量。指针传递参数本质上是值传递的方式,它所传递的是一个地址值。

<br />

3 指针和数组是否表示同一律念

从原理与定义上看,虽然指针与数组表示的是不一样的概念,但指针却能够方便地访问数组或者模拟数组,二者存在着一种貌似等价的关系,但也存在着诸多不一样之处, 主要表如今如下两个方面:

(1)<span style="color:blue">修改方式不一样。</span>

例如,char a[] = “hello”,能够经过取下标的方式对其元素值进行修改。例如,a[0] = ‘X’是正确的,而对于char *p = “world”,此时 p 指向常量字符串,因此p[0] = ‘X’是不容许的, 编译会报错。

(2)<span style="color:blue">所占字节数不一样。</span>

例如,char *p = “world”,p 为指针,则sizeof(p)获得的是一个指针变量的字节数,而不是 p 所指的内存大小。而 sizeof 数组,获得的是数组所占的内存大小。

<br />

4 复杂声明?

void * ( * (*fp1)(int))[10];

float (*(* fp2)(int,int,int))(int);

int (* (* fp3)())[10]();

分别表示什么意思?

  • fp1 是一个指针,指向一个函数,这个函数的参数为 int 型,函数的返回值是一个指针,这个指针指向一个数组,这个数组有 10 个元素,每一个元素是一个 void* 型指针;
  • fp2 是一个指针,指向一个函数,这个函数的参数为 3 个 int 型,函数的返回值是一个指针,这个指针指向一个函数,这个函数的参数为 int 型,函数的返回值是 float 型;
  • fp3 是一个指针,指向一个函数,这个函数的参数为空,函数的返回值是一个指针,这个指针指向一个数组,这个数组有 10 个元素,每一个元素是一个指针,指向一个函数,这个函数的参数为空,函数的返回值是 int 型。

<br />

5 const 指针?

const char *p1; // 常量指针 — 指向“常量”的指针
char * const p2; // 指针常量 — 指针类型的常量

说明上面两种描述的区别;

(1)p1 本质上是一个指向 const char 的指针,能够改变指向,可是指向的值是不能改变的;

(2)p2 本质上是一个 const char 指针类型的常量,不能够改变指向,可是指向的值能够改变。

<br />

3、内存

1 内存分配方式有几种(并加以描述)?

内存分配方式有三种:

(1)<span style="color:blue">从静态存储区域分配。</span>内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static 变量。

(2)<span style="color:blue">在栈上分配。</span>在执行函数时,函数内局部变量的存储单元均可以在栈上建立,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集。

(3)<span style="color:blue">从堆上分配,亦称动态内存分配。</span>程序在运行的时候用malloc 或new 申请任意多少的内存,程序员本身负责在什么时候用free 或delete 释放内存。动态内存的生存期由程序员决定,使用很是灵活,但问题也最多。

<span style="color:orange">栈是向下增加的,堆是向上增加的。</span>程序示例以下:

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

int global=0; // 静态存储区
char *pl; // 静态存储区

int main()
{
	int a; // 栈
	char s[]="abcdefg"; // 栈
	char *p2; // 栈
	char *p3="123456789"; // p3在栈上,"123456789"在常量区
	static int c=0; // 静态存储区
	pl=(char *)malloc(100); // 分配而来的100B的区域就在堆中 
	strcpy(p1,"123456789") // "123456789"放在常量区,编译器可能会将它与p3所指向的"123456789"优化成一个地方

	return 0;
}

<br />

2 栈空间和堆空间的最大值分别是多少?

在 Windows下,<span style="color:blue">栈是向低地址扩展的数据结构,是一块连续的内存的区域。栈顶的地址和栈的最大容量是系统预先规定好的,在 Windows 下,栈的大小是 2MB;而Linux默认栈空间大小为8MB,</span>能够经过命令 ulimit -s 来设置。

<span style="color:blue">堆是向高地址扩展的数据结构,是不连续的内存区域。 这是因为系统是用链表来存储空闲内存地址的,天然是不连续的,而链表的遍历方向是由低地址向高地址的,</span>而堆的大小受限于计算机系统中有效的虚拟内存。

Linux 虚拟地址空间内核占 1 GB,留给用户进程 3GB,Windows 是各占2GB,用户空间也是用户进程最大的堆申请数量了。但考虑到程序自己大小,动态库等因素,<span style="color:blue">实际的堆申请数量是达不到最大值的,Linux小于3GB,Windows小于2GB。</span>

<br />

3 堆和栈的区别?

堆栈空间分配

栈由操做系统自动分配释放 ,存放函数的参数值,局部变量的值等。<span style="color:blue">其分配方式相似于数据结构中的栈。</span>

堆通常由程序员分配释放, 若程序员不释放,程序结束时可能由操做系统回收。<span style="color:blue">其分配方式相似于数据结构中的链表。</span>

<br />

4 什么是内存泄漏?

<span style="color:orange">所谓内存泄露是指因为疏忽或错误形成程序未能释放已经再也不使用的内存的状况。</span>内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,失去了对该段内存的控制,于是形成了内存的浪费。

<br />

例如,对指针进行从新赋值,程序代码以下:

char *memoryArea = malloc(lO); 
char *newArea = malloc(lO); 
memoryArea = newArea;

对 memoryArea 的赋值会致使 memoryArea 以前指向的内容丢失,最终形成内存泄露。

<br />

再看一个例子:

char *fun()
{
    return malloc(10);
}

void callfun()
{
    fun();
}

上面程序就由于未能对返回值进行处理,最终致使内存泄露。

<br />

怎样解决内存泄露?

在编码过程当中养成良好的编程习惯,<span style="color:orange">用 malloc 或 new 分配的内存都应当在适当的时机用 free 或 delete 释放,在对指针赋值前,要确保没有内存位置会变为孤立的,另外要正确处理使用动态分配的函数返回值。</span>

<br />

5 什么是缓冲区溢出?

缓冲区是程序运行的时候机器内存中的一个连续块,它保存了给定类型的数据,随着动态分配变量会出现问题。<span style="color:blue">缓冲区溢出是指当向缓冲区内填充数据位数超过了缓冲区自身的容量限制时,发生的溢出的数据覆盖在合法数据(数据、下一条指令的指针、函数返回地址等)上的状况。</span>

程序示例以下:

#include <unistd.h> 

void Test()
{
	char buff[4]; 
	printf("Some input:"); 
	gets(buff); 
	puts(buff);
}

该程序的 Test() 函数中使用了标准的 C 语言输入函数 gets(),因为它没有执行边界检查,最终会致使 Test() 函数存在缓冲区溢出安全漏洞。<span style="color:blue">Test() 函数的缓冲区最多只能容纳 3 个字符和一个空字符,因此超过 4 个字符就会形成缓冲区溢出。</span>

<br />

6 有关内存的思考题

思考题(一)

void GetMemory(char *p)
{
	p = (char *)malloc(100);
}

void Test(void)
{
	char *str = NULL;
	GetMemory(str);
	strcpy(str, "hello world");
	printf(str);
}

程序会崩溃。<span style="color:blue">由于 GetMemory 并不能传递动态内存,Test 函数中的 str 一都是 NULL。 </span>

<br />

思考题(二)

char *GetMemory(void)
{
	char p[] = "hello world";
	return p;
}

void Test(void)
{
	char *str = NULL;
	str = GetMemory();
	printf(str);
}

多是乱码。<span style="color:blue">由于 GetMemory 返回的是指 “栈内存" 的指针,该指针的不是 NULL, 其原现的内容被清除,新内容不可知。</span>

<br />

思考题(三)

void GetMemory2(char **p, int num)
{
	*p = (char *)malloc(num);
}

void Test(void)
{
	char *str = NULL;
	GetMemory(&str, 100);
	strcpy(str, "hello");
	printf(str);
}

<span style="color:blue">可以输出 "hello",但会形成内存泄漏,由于没有经过 free 释放内存。</span>

<br />

相关文章
相关标签/搜索