[C++基础] 数组、指针、内存篇
一、数组
2.1 int a[2][2]= { {1}, {2,3} },则 a[0][1] 的值是多少?
二维数组的初始化一般有两种方式:
- 第一种方式是按行来执行,如
int array\[2][3]= { {0,0,1}, {1,0,0} };
; - 而第二种方式是把数值写在一块,如
int array\[2][3]= { 0,0,1,1,0,0 };
。
若只对部分元素进行初始化,数组中未赋值的元素自动为赋值为 0,所以 a[0][1] 的值是0。
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
}
&a 是数组指针,是一个指向 int (*)[5] 的指针,所以 &a+1 的地址是 &a 地址再加 5*sizeof(int),它的运算单位是 int(*)[5]。经过类型转换后,ptr 相当于 int *[5]。
ptr-1 的单位是 ptr 的类型,因此 ptr-1 的位置刚好是 a[4]。因为 ptr 与 (&a+1) 类型是不一样的,所以 ptr-1 只会减去 sizeof(int*)。
值得注意的是,a 和 &a 的地址是一样的,但意思不一样,a 是数组首地址,也就是 a[0] 的地址;&a 是 对象(数组)首地址;a+1 是数组下一元素的地址,即 a[1];而 &a+1 是下一个对象的地址, 即 a[5]。
2.3 不使用流程控制语句,如何打印出1 ~ 1000的整数?
采用构造函数与静态构造变量结合的方法实现。首先在类中定义一个静态成员变量,然后在构造函数里面打印该静态变量的值,并对静态变量进行自增操作,同时在主函数里面定义一个类数组,程序代码示例如下:
class print
{
public:
static int a;
print()
{
printf("%d\n",print::a);
a++;
}
};
int print::a = 1;
int main()
{
print tt[100];
return 0;
}
2.4 int id[sizeof(unsigned long)]; 这个对吗?为什么?
答案:正确。这个 sizeof 是编译时运算符,编译时就确定了 ,可以看成和机器有关的常量。
扩展: 以下代码能够编译通过吗,为什么?
const int size1 = 2;
char str1[size1];
int temp = 0;
const int size2 = temp;
char str2[size2];
str1 能通过编译,而 str2 定义出错,size2 非编译器期间常量,而数组定义要求长度必须为编译期常量。
二、指针
1 使用指针有哪些好处?
一般而言,使用指针有以下几个方面的好处:
(1)可以动态分配内存;
(2)进行多个相似变量的一般访问;
(3)为动态数据结构,尤其是树和链表,提供支持;
(4)遍历数组,如解析字符串;
(5)高效地按引用 “复制” 数组与结构,特别是作为函数参数的时候,可以按照引用传递函数参数,提高开发效率。
2 引用和指针的区别?
(1)引用只能在定义时被初始化一次,之后不能被改变,即引用具有“从一而终”的特性。而指针却是可变的;
(2)引用使用时不需要解引用(*),而指针需要解引用;
(3)引用不可以为空,而指针可以为空;
(4)对引用进行 sizeof 操作得到的是所指向的变量(对象)的大小,而对指针进行 sizeof 操作得到的是指针本身(所指向的变量或对象的地址)的大小;
(5)作为参数传递时,两者不同。引用传递参数是 “引用传递”,会通过一个间接寻址的方式操作到主调函数中的相关变量。指针传递参数本质上是值传递的方式,它所传递的是一个地址值。
3 指针和数组是否表示同一概念
从原理与定义上看,虽然指针与数组表示的是不同的概念,但指针却可以方便地访问数组或者模拟数组,两者存在着一种貌似等价的关系,但也存在着诸多不同之处, 主要表现在以下两个方面:
(1)修改方式不同。
例如,char a[] = “hello”
,可以通过取下标的方式对其元素值进行修改。例如,a[0] = ‘X’
是正确的,而对于char *p = “world”
,此时 p 指向常量字符串,所以p[0] = ‘X’
是不允许的, 编译会报错。
(2)所占字节数不同。
例如,char *p = “world”
,p 为指针,则sizeof(p)
得到的是一个指针变量的字节数,而不是 p 所指的内存大小。而 sizeof 数组,得到的是数组所占的内存大小。
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 型。
5 const 指针?
const char *p1; // 常量指针 — 指向“常量”的指针
char * const p2; // 指针常量 — 指针类型的常量
说明上面两种描述的区别;
(1)p1 本质上是一个指向 const char 的指针,可以改变指向,但是指向的值是不能改变的;
(2)p2 本质上是一个 const char 指针类型的常量,不可以改变指向,但是指向的值可以改变。
三、内存
1 内存分配方式有几种(并加以描述)?
内存分配方式有三种:
(1)从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static 变量。
(2)在栈上分配。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集。
(3)从堆上分配,亦称动态内存分配。程序在运行的时候用malloc 或new 申请任意多少的内存,程序员自己负责在何时用free 或delete 释放内存。动态内存的生存期由程序员决定,使用非常灵活,但问题也最多。
栈是向下增长的,堆是向上增长的。程序示例如下:
#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;
}
2 栈空间和堆空间的最大值分别是多少?
在 Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。栈顶的地址和栈的最大容量是系统预先规定好的,在 Windows 下,栈的大小是 2MB;而Linux默认栈空间大小为8MB,可以通过命令 ulimit -s 来设置。
堆是向高地址扩展的数据结构,是不连续的内存区域。 这是由于系统是用链表来存储空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址的,而堆的大小受限于计算机系统中有效的虚拟内存。
Linux 虚拟地址空间内核占 1 GB,留给用户进程 3GB,Windows 是各占2GB,用户空间也是用户进程最大的堆申请数量了。但考虑到程序本身大小,动态库等因素,实际的堆申请数量是达不到最大值的,Linux小于3GB,Windows小于2GB。
3 堆和栈的区别?
堆栈空间分配
栈由操作系统自动分配释放 ,存放函数的参数值,局部变量的值等。其分配方式类似于数据结构中的栈。
堆一般由程序员分配释放, 若程序员不释放,程序结束时可能由操作系统回收。其分配方式类似于数据结构中的链表。
4 什么是内存泄漏?
所谓内存泄露是指由于疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
例如,对指针进行重新赋值,程序代码如下:
char *memoryArea = malloc(lO);
char *newArea = malloc(lO);
memoryArea = newArea;
对 memoryArea 的赋值会导致 memoryArea 之前指向的内容丢失,最终造成内存泄露。
再看一个例子:
char *fun()
{
return malloc(10);
}
void callfun()
{
fun();
}
上面程序就因为未能对返回值进行处理,最终导致内存泄露。
怎样解决内存泄露?
在编码过程中养成良好的编程习惯,用 malloc 或 new 分配的内存都应当在适当的时机用 free 或 delete 释放,在对指针赋值前,要确保没有内存位置会变为孤立的,另外要正确处理使用动态分配的函数返回值。
5 什么是缓冲区溢出?
缓冲区是程序运行的时候机器内存中的一个连续块,它保存了给定类型的数据,随着动态分配变量会出现问题。缓冲区溢出是指当向缓冲区内填充数据位数超过了缓冲区自身的容量限制时,发生的溢出的数据覆盖在合法数据(数据、下一条指令的指针、函数返回地址等)上的情况。
程序示例如下:
#include <unistd.h>
void Test()
{
char buff[4];
printf("Some input:");
gets(buff);
puts(buff);
}
该程序的 Test() 函数中使用了标准的 C 语言输入函数 gets(),由于它没有执行边界检查,最终会导致 Test() 函数存在缓冲区溢出安全漏洞。Test() 函数的缓冲区最多只能容纳 3 个字符和一个空字符,所以超过 4 个字符就会造成缓冲区溢出。
6 有关内存的思考题
思考题(一)
void GetMemory(char *p)
{
p = (char *)malloc(100);
}
void Test(void)
{
char *str = NULL;
GetMemory(str);
strcpy(str, "hello world");
printf(str);
}
程序会崩溃。因为 GetMemory 并不能传递动态内存,Test 函数中的 str 一都是 NULL。
思考题(二)
char *GetMemory(void)
{
char p[] = "hello world";
return p;
}
void Test(void)
{
char *str = NULL;
str = GetMemory();
printf(str);
}
可能是乱码。因为 GetMemory 返回的是指 “栈内存" 的指针,该指针的不是 NULL, 其原现的内容被清除,新内容不可知。
思考题(三)
void GetMemory2(char **p, int num)
{
*p = (char *)malloc(num);
}
void Test(void)
{
char *str = NULL;
GetMemory(&str, 100);
strcpy(str, "hello");
printf(str);
}
能够输出 "hello",但会造成内存泄漏,因为没有通过 free 释放内存。
[C++基础] 数组、指针、内存篇的更多相关文章
- 【搬砖】安卓入门(4)- Java开发编程基础--数组
05.01_Java语言基础(数组概述和定义格式说明)(了解) A:为什么要有数组(容器) 为了存储同种数据类型的多个值 B:数组概念 数组是存储同一种数据类型多个元素的集合.也可以看成是一个容器. ...
- day05<Java语言基础--数组>
Java语言基础(数组概述和定义格式说明) Java语言基础(数组的初始化动态初始化) Java语言基础(Java中的内存分配以及栈和堆的区别) Java语言基础(数组的内存图解1一个数组) Java ...
- Java语言基础(数组)
Java语言基础(数组概述和定义格式说明) A:为什么要有数组(容器) 为了存储同种数据类型的多个值 B:数组概念 数组是存储同一种数据类型多个元素的集合.也可以看成是一个容器. 数组既可以存储基本数 ...
- 【C++基础】sizeof 数组 指针 空NULL
笔试遇到很多sizeof的小题,博主基础堪忧,怒总结如下,还是要巩固基础啊啊啊! sizeof操作符 对象所占 栈内存空间的大小,单位是字节 关键词:char 数组 指针 结构体 class [注意 ...
- Delphi 的内存操作函数(2): 给数组指针分配内存
静态数组, 在声明时就分配好内存了, 譬如: var arr1: ..] of Char; arr2: ..] of Integer; begin ShowMessageFmt('数组大小 ...
- c指针与数组,传参问题,指针数组与数组指针的区别,二维数组动态内存分配
一 数组的结构:顺序存储,看谭浩强中的图,牢记 1.数组名指代一种数据结构:数组 现在可以解释为什么第1个程序第6行的输出为10的问题,根据结论1,数组名str的内涵为一种数据结构,即一个长度为10的 ...
- go语言基础之数组指针做函数参数
1.数组指针做函数参数 示例: package main //必须有个main包 import "fmt" //p指向实现数组a,它是指向数组,它是数组指针 //*p代表指针所指向 ...
- C语言数组篇(二)指针数组和数组指针
数组指针 和 指针数组 这两个名词可以说是经常搞混了 数组指针--> 数组的指针 就是前面讲的 指向数组a的指针p; 指针数组--&g ...
- C语言基础 (9) 数组指针
复习 只要把地址拿到就能这么操作.. (这里是合法的地址,不是野指针) 只有定义变量后,此变量的地址才是合法的地址 野指针就是保存没有意义地址的指针变量 操作野指针变量本身不会有任何问题 操作野指针所 ...
随机推荐
- c# asp.net core取当月第一天和最后一天及删除最后一个字符的多种方法
当月第一天0时0分0秒 DateTime.Now.AddDays( - DateTime.Now.Day).Date 当月最后一天23时59分59秒 DateTime.Now.AddDays( - D ...
- Quartz.Net 删除一个Job
Quartz.Net 删除Job 来博客园的第一篇文章先写个简单的,希望能帮助到大家. 步入正题: Quartz.Net有三个重要的概念,分别是 Scheduler .Job .Trigger. S ...
- LoadRunner 11 的兼容问题及权限问题
1.LoadRunner 11 在服务器系统中可能出现 不兼容问题. 要对 安装目录bin\LRLauncherApp.exe 和 bin\wlrun.exe 右键属性兼容性(视系统定). 2.如果填 ...
- 2.Shell脚本中的set指令,比如set -x 和 set -e
set参数介绍 set指令能设置所使用shell的执行方式,可依照不同的需求来做设置 -a 标示已修改的变量,以供输出至环境变量. -b 使被中止的后台程序立刻回报执行状态. -C 转向所产生的文件无 ...
- 微服务架构 ------ DockerCompose从安装到项目部署
DockerCompose的目的:简化Docker的启动和停止流程,以及编排Docker启动服务与服务之间的关系 DockerCompose的安装:curl -L https://get.daoclo ...
- 《JS权威指南学习总结--第7章 数组概念、稀疏数组》
一.数组概念 数组是值的有序结合.每个值叫做一个元素,而每个元素在数组中都有一个位置,用数字表示,称为索引. JS数组是无类型的:数组元素可以是任意对象,并且同一个数组中的不同元素也可能有不同的类型. ...
- 网页百度地图api,支持位置偏移
网页百度地图api,支持位置偏移 需加载 jq <style type="text/css"> #allmap {width:100%; height:100%; bo ...
- 【案例】电子生产中的排程问题如何解决?APS助力智能化排产
共进电子是典型的消费类电子制造企业,以ODM业务为主,立足双O(OEM/ODM),发展自主品牌.其中,生产模式特点包括: 批量制造.多品种小批量.面向订单生产: 产品结构复杂,设计变更频繁:生产计划复 ...
- volume create: k8s-volume: failed: Host 172.31.182.142 is not in 'Peer in Cluster' state
问题描述: 1.gluster peer status查询存在节点 2.创建volume失败提示节点不存在 排查方法: 1.hosts文件是否配置正确 2.检查防火墙是否打开,打开的话放行24007端 ...
- 关于Jackson中JsonNode的取值asText()和textValue()区别
在 比较高版本的Jackson 中, 包名为 com.fasterxml.jackson String jsonText="{\"name\":\"张三\&qu ...