澳门新葡萄京官网首页iOS 面试– C 语言

指针存储的内存地址,在使用指针的时候,需要保证指针指向地址的有效性。C++程序中内存分配分为静态分配和动态分配,静态分配由编译器在程序运行之前分配,而动态分配实在程序运行的过程中分配。

1.写出下面代码的运行结果

这是我面试过程中常被问到的问题,都是自己整理的希望对你们有帮助。

在C++中,通过关键字new和delete来实现程序的动态内存分配和回收。其中,关键字new实现内存分配,如果需要对分配出的内存进行初始化,则在类型后面加上一个括号,并带有初始值。因此,C++中动态分配内存的一般形式如下:

    int array[5] = {1, 2, 3, 4, 5};int *p = &array[0];

常见基本类型的字节大小

类型标识符*指针名=new类型标识符;

    int max = Max(*p++, 1);

32位操作系统

需要注意的是,为了保存分配出的内存地址,应当使用一个指针指向new的结果。因此,上述形式通过类型的标识符定义了一个指针。例如,下面语句分配一块整型变量的内存,并使指针p指向这块内存。

    printf(“%d ,%d”, max, *p);

char :1个字节(固定)

int*p=newint;

    答案:1,2

*澳门新葡萄京官网首页,(即指针变量): 4个字节(32位机的寻址空间是4个字节。同理64位编译器)(变化*)

上述括号中的10为这块内存提供一个初始值。该内存分配成功之后,会用10去初始化。因此上述语句执行后*p的值为10。此外,用new不但可以为一个变量分配内存。还可以为一个数组分配内存,其方法是在new的类型标识符后面跟一个中括号,其中是数组的长度。例如,下面的语句定义一个长度为10的数组并为其分配空间。

    #define Max(X, Y) ((X) > (Y) ? (X) : (Y))
对于++、–在宏定义当中使用最容易产生副作用

short int : 2个字节(固定)

2.define 定义的宏和const 定义的常量的区别

int: 4个字节(固定)

   
 #define定义的宏,程序在预处理阶段将宏定义内容仅进行了替换,因此程序运行时,常量表中没有用
   
#define所定义的宏,系统并不会为它分配内存,而且在编译时不会检查数据类型,出错的概率要大一些。

unsigned int : 4个字节(固定)

    const
定义的常量,在程序运行的时候是存放在常量表中,系统会为它分配内存,而且在编译时进行数据类型检查。

float: 4个字节(固定)

    #define 定义的表达式时要注意“边缘效应”,例如如下定义:

double: 8个字节(固定)

    #define N 2+3 // 我们预想的 N 值是5,我们这样使用N

long: 4个字节

    int a = N / 2 ; // 我们预想的 a 的值是2.5,可实际上 a 的值是3.5

unsigned long: 4个字节(变化*,其实就是寻址控件的地址长度数值)

3.strcpy , memcpy , sprintf 使用注意事项

long long: 8个字节(固定)

    strcpy 是一个字符串拷贝函数,原型为:strcpy ( char destr, const char
str ) ,结束标志为 ‘’
,由于拷贝的长度不是我们控制的,所以拷贝容易出错。

1.指向字符串常量的指针,指向字符串的常量指针(const)

    Memcpy 是一个内存拷贝函数,函数原型为:memcpy (char destrc , const
char str, unsigned int len ),讲长度为 len 的一段内存,从str 拷贝到
destrc 中去,这个函数的长度可控,但是会有内存读写错误,(比如 len
的长度大于要拷贝的空间或者目的空间)

const char* p = “hello”; // 指向
“字符串常量”
p[0] = ‘X’; // 错误! 想要修改字符串的第一个字符. 但是常量不允许修改
p = p2; // 正确! 让p指向另外一个指针.

    sprintf
是一个格式化函数,将一段数据从通过特定的格式,格式化到一个字符串缓冲区中去。sprintf
格式化的函数的长度不可控制,有可能格式化后的字符串会超出缓冲区的大小,造成溢出。

char* const p = “hello”; // 指向字符串的” 常量的指针”
p[0] = ‘X’; // 正确! 允许修改字符串, 因为该字符串不是常量
p = p2; // 错误! 指针是常量, 不许修改p的指向

4.static 关键字的作用

char const * 和 const char* 是一样的. const 的位置在char左边还是右边都一样.
常量指针的const应当写在 *星号的右边.
指向常量字符串的常量指针的写法是 const char* const p = “xx”;
要2个const

    · 隐藏。编译多个文件时,所有未加 static
前缀的全局变量和函数都全局可见。

2.typedef &#define的问题

    · 保持变量内容的持久。全局变量和 static
变量都存储在静态存储区,程序开始运行就初始化,只初始化一次。static
控制了变量的作用范围。

有下面两种定义pStr数据类型的方法,两者有什么不同?哪一种更好一点?

    · 默认初始化为0。在静态数据区,内存中的所有字节都是0x00,全局变量和
static 变量都是默认初始化为0。

typedef char* pStr;

5.static 关键字的区别

#define pStr char*;

    · 全局变量方面:static
全局变量只初始化一次,防止在其他文件单元中被引用;

分析:通常讲,typedef要比#define要好,特别是在有指针的场合。请看例子:

    · 局部变量方面:static
局部变量只被初始化一次,下一次依据上一次结果的值;

typedef char* pStr1;

    · 函数方面 :static
函数在内存中只有一份,普通函数在每个被调用中维持一份拷贝

#define pStr2 char *

6.关键字 const

pStr1 s1, s2;

    · int const a ;const int a :作用一样,a 是一个常整型数

pStr2 s3, s4;

    · int const * a ;const int * a :a
是一个指向常整型数的指针(整型数不可变,指针可变)

在上述的变量定义中,s1、s2、s3都被定义为char *,而s4则定义成了char,不是我们所预期的指针变量,根本原因就在于#define只是简单的字符串替换而typedef则是为一个类型起新名字。上例中define语句必须写成 pStr2 s3, *s4; 这这样才能正常执行。

    · int * const a :a
是一个指向整型数的常指针(整型数可变,指针不可变)

3.const的问题

    · int const *const a
:是一个指向常整型数的常指针(指针、整型数均不可变)

(1)可以定义const常量,具有不可变性。

7.堆和栈

例如:const int Max=100; int
Array[Max];

    · 管理方式:

(2)便于进行类型检查,使编译器对处理内容有更多了解,消除了一些隐患。

        栈:由编译器自动管理,无需我们手工控制

例如:voidf(const int i) {
………} 编译器就会知道i是一个常量,不允许修改;

        堆:释放工作由程序员控制,容易产生内存泄漏(memory leak)。

(3)可以避免意义模糊的数字出现,同样可以很方便地进行参数的调整和修改。

    · 申请大小:

如(1)中,如果想修改Max的内容,只需要:const int Max=you want;即可!

        栈:在 Windows
下,栈是向低地址扩展的数据结构,是一块连续的内存区域,即栈顶的地址和栈的最大容量是系统预先规定好的,在
Windows 下栈的大小是2M (也有的说是 1 M
),如果申请的空间超过栈的剩余空间时,将提示
overflow。因此能从栈获取的空间比较小。

(4)可以保护被修饰的东西,防止意外的修改,增强程序的健壮性。 还是上面的例子,如果在函数体内修改了i,编译器就会报错;

       
堆:是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表存储空闲的内存地址的,自然不连续,而链表的遍历方式是由低地址向高地址。堆得大小受限于计算机系统中有效的虚拟内存,所以堆获得的空间比较灵活,也比较大。

例如: void f(const int i) {
i=10;//error! }

    · 碎片问题:

可以节省空间,避免不必要的内存分配。
例如:

       
栈:不存在该问题,因为栈是先进后出的队列,他们是如此一一对应,以至于没有一个内存块从栈中间弹出

#definePI 3.14159 //常量宏

        堆:频繁的 new/delete
势必造成空间的不连续,从而造成大量的碎片,使程序效率降低

const doublePi=3.14159; //此时并未将Pi放入RAM中
……

    · 分配方式:

doublei=Pi; //此时为Pi分配内存,以后不再分配!

       
栈:有2种方式:静态和动态分配。静态分配是由编译器完成的,比如局部变量的分配。动态分配由
alloc
函数进行分配,但栈的动态分配和栈不同,是由编译器进行释放,无需程序员手工实现

double I=PI; //编译期间进行宏替换,分配内存

        堆:只有动态分配

double j=Pi; //没有内存分配

    · 分配效率:

double J=PI; //再进行宏替换,又一次分配内存!

       
栈:是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,所以效率比较高

const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是象#define一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而#define定义的常量在内存中有若干个拷贝。

        堆:是C/C++ 函数库提供的,机制很复杂

(6)提高了效率。

8.引用和指针的区别

编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高。

    · 指针指向一块内存,内容存储所指内存的地址;引用是某块内存的别名。

  1. sizeof与strlen的区别:

    · 指针使用时需(*),引用不需要。

char str[20]=”0123456789″;
int a=strlen; // a=10;strlen 计算字符串的长度,以’为字符串结束标记。
int b=sizeof; // b=20;sizeof 计算的则是分配的数组str[20] 所占的内存空间的大小,不受里面存储的内容影响.

    · 引用只在定义时被初始化,之后不可变;指针可变。

上面是对静态数组处理的结果,如果是对指针,结果就不一样了

    · 引用没有const。

char* ss = “0123456789”;
sizeof结果4===》ss是指向字符串常量的字符指针,sizeof 获得的是一个指针的之所占的空间,应该是长整型的,所以是4,sizeof 结果1===》*ss是第一个字符
其实就是获得了字符串的第一位’0′
所占的内存空间,是char类型的,占了1位strlen= 10如果要获得这个字符串的长度,则一定要使用
strlen

    · 引用不能为空。

Sizeof结构体为结构体中定义的数据类型的总的空间。

    · sizeof 引用得到的是所指向变量(对象)的大小,sizeof
指针是指针本身的大小。

Sizeof对union为union中定义的数据类型的最大数据类型的大小。

    · 引用 ++ 为引用对象自己 ++ ,指针 ++ 是指向对象后面的内存。

5 .auto, register, static分析

    · 程序需要为指针分配内存区域,引用不需要。

auto即C语言中局部变量的默认属性,编译器默认所有的局部变量都是auto的,定义的变量都是在栈中分配内存。

9.用变量 a 给出下面的定义

static关键字指明变量的“静态”属性,同时具有“作用域限定符”的意义,修饰的局部变量存储在程序静态区,static的另一个意义是文件作用域标示符。

    · 一个有10个整型数的数组:

static修饰的全局变量作用域只是声明的文件中,static修饰的函数作用域只是声明的文件中

      int a [10]

register关键字指明将变量存储于寄存器中,register只是请求寄存器变量,但不一定请求成功。register变量的必须是CPU寄存器可以接受的值,不能用&运算符获取register变量的地址,这样使用的好处是处理快。

    · 一个有10个指针的数组,该指针是一个指向一个整型数的:

6.const, volatile同时修饰变量

(1)
“编译器一般不为const变量分配内存,而是将它保存在符号表中,这使得它成为一个编译期间的值,没有了存储与读内存的操作。”

(2)volatile的作用是“告诉编译器,i是随时可能发生变化的,每次使用它的时候必须从内存中取出i的值”。

一,const, volatile含义

(1)const含义是“请做为常量使用”,而并非“放心吧,那肯定是个常量”。
(2)volatile的含义是“请不要做自以为是的优化,这个值可能变掉的”,而并非“你可以修改这个值”。

二,const, volatile的作用以及起作用的阶段

(1)const只在编译期有用,在运行期无用

const在编译期保证在C的“源代码”里面,没有对其修饰的变量进行修改的地方(如有则报错,编译不通过),而运行期该变量的值是否被改变则不受const的限制。

(2)volatile在编译期和运行期都有用

在编译期告诉编译器:请不要做自以为是的优化,这个变量的值可能会变掉;

在运行期:每次用到该变量的值,都从内存中取该变量的值。

补充:编译期 — C编译器将源代码转化为汇编,再转化为机器码的过程;运行期
— 机器码在CPU中执行的过程。

三,const, volatile同时修饰一个变量

(1)合法性

“volatile”的含义并非是“non-const”,volatile和cons不构成反义词,所以可以放一起修饰一个变量。

(2)同时修饰一个变量的含义

表示一个变量在程序编译期不能被修改且不能被优化;在程序运行期,变量值可修改,但每次用到该变量的值都要从内存中读取,以防止意外错误。

7 、栈、堆、静态存储区

栈:主要函数调用的使用

栈是从高地址向低地址方向使用,堆的方向相反。

在一次函数调用中,栈中将被依次压入:参数,返回地址,EBP。如果函数有局部变量,接下来,就在栈中开辟相应的空间以构造变量。

在C语言程序中,参数的压栈顺序是反向的。比如func。在参数入栈的时候,是:先压c,再压b,最后a。在取参数的时候,由于栈的先入后出,先取栈顶的a,再取b,最后取c。

堆:主要内存动态分配

空闲链表法,位图法,对象池法等等 。

Int* p=(int*)malloc(sizeof;

静态存储区:保存全局变量和静态变量

程序静态存储区随着程序的运行而分配空间,直到程序运行结束,在程序的编译期静态存储区的大小就已经确定,程序的静态存储区主要用于保存程序中的全局变量和静态变量与栈和堆不同,静态存储区的信息最终会保存到可执行程序中

知识点:堆栈段在程序运行后才正式存在,是程序运行的基础

1.函数放在代码段:.Test section。.text段存放的是程序中的可执行代码

2.带初始值的全局变量和静态变量在数据段:.data section。
.data段保存的是那些已经初始化了的全局变量和静态变量

3.不带初始值得全局变量和静态变量在.bss。 .bss段存放的是未初始化的全局变量和静态变量
.rodata(read only)段存放程序中的常量值,如字符串常量
同是全局变量和静态变量,为什么初始化和未初始化的变量保存在不同的段中?

答:为了启动代码的简单化,编译链接器会把已初始化的变量放在同一个段:.data,这个段的映像(包含了各个变量的初值)保存在“只读数据段”,这样启动代码就可以简单地复制这个映像到
.data
段,所有的已初始化变量就都初始化了。而未初始化变量也放在同一个段:.bss,启动代码简单地调用
memset 就可以把所有未初始化变量都清0。

void *memset(void *s, int ch,size_tn);

函数解释:将s中当前位置后面的n个字节 (typedef unsigned int size_t )用 ch 替换并返回 s 。

memset:作用是在一段内存块中填充某个给定的值,它是对较大的结构体或数组进行清零操作的一种最快方法

#define Malloc malloc(n*sizeof

8 、野指针

产生原因:

1、局部指针变量没有初始化

2、使用已经释放的指针

3、指针所指向的变量在指针之前被销毁

A.用malloc申请了内存之后,应该立即检查指针值是否为NULL,防止使用为NULL的指针:

B.牢记数组的长度,防止数组越界操作,考虑使用柔性数组

C.动态申请操作必须和释放操作匹配,防止内存泄露和多次释放

D.free指针之后必须立即赋值为NULL

9 、void类型指针

指针有两个属性:指向变量/对象的地址和长度,但是指针只存储地址,长度则取决于指针的类型;编译器根据指针的类型从指针指向的地址向后寻址,指针类型不同则寻址范围也不同,比如:

int*从指定地址向后寻找4字节作为变量的存储单元

double*从指定地址向后寻找8字节作为变量的存储单元

void即“无类型”,void *则为“无类型指针”,可以指向任何数据类型。

void指针可以指向任意类型的数据,即可用任意数据类型的指针对void指针赋值。例如

int *pint;

void *pvoid; //它没有类型,或者说这个类型不能判断出指向对象的长度

pvoid = pint; //只获得变量/对象地址而不获得大小,但是不能 pint =pvoid;

9.2如果要将pvoid赋给其他类型指针,则需要强制类型转换如:

pint = pvoid; //转换类型也就是获得指向变量/对象大小

9.3void指针不能复引用

*pvoid //错误

要想复引用一个指针,或者使用“->”运算符复引用一部分,都要有对于指针指向的内存的解释规则。

例如,int *p;

那么,当你后面复印用p的时候,编译器就会把从p指向的地址开始的四个字节看作一个整数的补码。

因为void指针只知道指向变量/对象的起始地址,而不知道指向变量/对象的大小所以无法正确引用。

在实际的程序设计中,为迎合ANSI标准,并提高程序的可移植性,我们可以这样编写实现同样功能的代码:

void*pvoid;

pvoid++; //ANSI:正确;GNU:正确

pvoid+=1; //ANSI:错误;GNU:正确

      int * a [10]

    · 一个指向10个整数数组的指针:

      int ( * ) a [10]

    · 一个指向函数的指针,该函数有一个整型参数并返回一个整型数:

      int ( * ) a ( int )

    ·
一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整型参数

      int ( * a [10] ) ( int )

10.写出以下代码的输出

    int a [5] = {1, 2, 3, 4, 5} ;

    int *ptr = ( int *) ( &a + 1) ;

    printf (“%d, %d “,*( a + 1) , *(ptr + 1));

    参考答案:2,随机值

    分析:

        a 代表有 5 个元素的数组首地址,a[ 5 ]
的元素分别是1,2,3,4,5。a + 1 表示数据首地址加 1,即 a[ 1
],值为2。但这里是&a + 1,因为 a 代表的是整个数组,它的空间大小为5 *
sizeof ( int ),因此 &a + 1就是 a + 5。a
是个常量指针,指向当前数组的首地址,,指针 + 1就是移动sizeof( int
)个字节。因此 ptr 是指向 int * 类型的指针,而 ptr 指向的就是 a +
5,那么 ptr +1 也相当于 a + 6,所以最后的 *( ptr
+1)就是一个随机值了。而 *( ptr – 1 )就相当于 a + 4,对应值为5。

11.内存分区情况

    · 代码区:存放函数二进制代码

    ·
数据区:系统运行时申请内存并初始化,系统退出时由系统释放,存放全局变量、静态变量、常量

    · 堆区:通过 malloc 等函数或 new
等操作符动态申请得到,需程序员手动申请和释放

    ·
栈区:函数模块申请,函数结束时由系统自动释放,存放局部变量、函数参数

12.用NSLog输出一个浮点类型,结果四舍五入,并保留一位小数

    float money = 1.011;

    NSLog ( @”%.1f”, money ) ;

13.指针和数组的区别

    · 数组可以申请在栈区和数据区;指针可以指向任意类型的内存块。sizeof
作用于数组时,得到是数组所占的内存大小;作用于指针时,得到的都是4个字节的大小

    ·
数组名表示数组首地址,是常量指针,不可修改指向;普通指针的值可以改变

    ·
用字符串初始化字符数组是将字符串的内容拷贝到字符数组中;用字符串初始化字符指针是将字符串的首地址赋给指针,也就是指向了该字符串

发表评论

电子邮件地址不会被公开。 必填项已用*标注