目录
2.C 语言的关键字 static 和 C++ 的关键字 static 有什么区别
cout是有缓冲输出,输出过程会首先将输出字符放入缓冲区,然后输出到屏幕。 printf是无缓冲输出,有输出时立即输出。
5. strcpy函数和strncpy函数的区别?哪个函数更安全?
一、主要知识点
- 指针:一级/二级指针,指针数组,数组指针,指针函数,函数指针
- 数组:一维/二维数组 排序等
- 字符串
- 关键字:#define assert const const static volatile extern sizeof typedef等
- 函数:strlen strcmp strstr memcpy sprintf等
- 内存:堆栈,内存分配 malloc/free 大小端
- 变量:变量类型/占用字节数,局部/全局/静态变量,形参/实参
- 结构:结构体,联合体,枚举类
二、指针
1.定义
在 C 语言中,内存单元的地址称为指针,专门用来存放地址的变量,称为指针变量,在不影响理解的情况中,有时对地址、指针和指针变量不区分,通称指针。
一个指针在32位的计算机上,占4个字节;
一个指针在64位的计算机上,占8个字节;
一般形式如下:< 存储类型 > < 数据类型 > * < 指针变量名 > ; 例如, char *pName ;
指针的存储类型是指针变量本身的存储类型。(32位系统为例,地址是32位的,并且地址是正整数,所以指针变量的数据类型是确定的,即unsigned int,无符号整形。因此在指针变量定义时,无需指出指针变量的数据类型。)
指针说明时指定的数据类型不是指针变量本身的数据类型,而是指针目标的数据类型。简称为指针的数据类型。
2.指针的初始化
指针在说明的同时, 也可以被赋予初值,称为指针的初始化。
一般形式是:< 存储类型 > < 数据类型 > *< 指针变量名 > = < 地址量 > ; 例如: int a,*pa=&a;
设 px 为一个指针,则 :
px — 指针变量, 它的内容是地址量
*px — 指针所指向的对象, 它的内容是数据
&px — 指针变量占用的存储区域的地址,是个常量
3.指针的赋值
指针的赋值运算指的是通过赋值运算符向指针变量送一个地址值,向一个指针变量赋值时,送的值必须是地址常量或指针变量,不能是普通的整数 ( 除了赋零以外 )。
指针赋值运算常见的有以下几种形式 :
1.把一个普通变量的地址赋给一个具有相同数据类型的指针 double x=15, *px; px=&x;
2.把一个已有地址值的指针变量赋给具有相同数据类型的另一个指针变量 例如 :
float a, *px, *py;
px = &a;
py = px;
3.把一个数组的地址赋给具有相同数据类型的指针。例如 :
int a[20], *pa;
pa = a; // 等价 pa = &a[0]
4.指针的运算
指针运算是以指针变量所存放的地址量作为运算量而进行的运算,指针运算的实质就是地址的计算,指针运算的种类是有限的,它只能进行赋值运算、算术运算和关系运算。
不同数据类型的两个指针实行加减整数运算是无意义的
px + n 表示的实际位置的地址量是:(px) + sizeof(px 的类型 ) * n
px-py 运算的结果值不是地址量,而是一个整数值,表示两指针之间相隔数据的个数。针相减
5.指针与一维数组
在 C 语言中,数组的指针是指数组在内存中的起始地址,数组元素的地址是指数组元素在内存中的起始地址,一维数组的数组名为一维数组的指针(起始地址)。
x[i] 、 *(px+i) 、 *(x+i) 和 px[i] 具有完全相同的功能:访问数组第 i+1 个数组元素。但指针变量和数组的指针(或叫数组名)在本质上不同,指针变量是地址变量,而数组的指针是地址常量。
p++,p-- ( 对 ) a++,a-- ( 错 ) a+1, *(a+2) ( 对 )
6.指针与二维数组
可把二维数组看作由多个一维数组组成。比如 int a[3][3] ,含有三个元素:a[0]、a[1] 、a[2]元素 a[0] 、a[1] 、a[2] 都是一维数组名。
二维数组名代表数组的起始地址,数组名加1,是移动一行元素。因此,二维数组名常被称为行地址。a+1 等效于 a[1],a+2 等效于 a[2]
存储行地址的指针变量,叫做行指针变量。形式如下:
< 存储类型 > < 数据类型 > (*< 指针变量名>)[ 表达式 ] ; 例如,int a[2][3]; int (*p)[3];
方括号中的常量表达式表示指针加 1 ,移动几个数据。(p+1 移动 3 个元素)
7.字符指针与字符串
C语言通过使用字符数组来处理字符串,通常我们把char数据类型的指针变量称为字符指针变量。
初始化字符指针是把内存中字符串的首地址赋予指针,并不是把该字符串复制到指针中
char str[] = “Hello World”;
char *p = str;
在 C 编程中,当一个字符指针指向一个字符串常量时,不能修改指针指向的对象的值
char * p = “Hello World”;
*p = ‘h’; // 错误, 字符串常量不能修改
8.指针数组
指针数组是指由若干个具有相同存储类型和数据类型的指针变量构成的集合
指针数组的一般说明形式:< 存储类型 > < 数据类型 > *< 指针数组名 >[< 大小 >] ;
指针数组名表示该指针数组的起始地址
声明一个指针数组:double * pa[2] ,a[2][3];
把一维数组 a[0] 和 a[1] 的首地址分别赋予指针变量,数组的数组元数 pa[0] 和 pa[1] :
pa[0]=a[0] ; // 等价 pa[0] = &a[0][0];
pa[1]=a[1]; // 等价 pa[1] = &a[1][0];
9.多级指针
把一个指向指针变量的指针变量,称为多级指针变量,对于指向处理数据的指针变量称为一级指针变量,简称一级指针,而把指向一级指针变量的指针变量称为二级指针变量,简称二级指针
二级指针变量的说明形式如下: < 存储类型 > < 数据类型 > ** < 指针名 >;
多级指针变量加 1 ,是向地址大的方向移动一个目标数据。类似的道理,多级指针运算也是以其目标变量为单位进行偏移。比如, int **p ; p+1 移动一个 int * 变量所占的内存空间。再比如 int ***p , p+1 移动一个 int ** 所占的内存空间。
指针数组也可以用另外一个指针来处理。例如:有一个一维字符指针数组 ps[5] ,char *ps[5]= { "Beijing city" ,......, "London city" } ; char ** pps = ps;
10.void指针
void指针是一种不确定数据类型的指针变量,它可以通过强制类型转换让该变量指向任何数据类型的变量一般形式为: void * < 指针变量名称 > ;
对于 void 指针,在没有强制类型转换之前,不能进行任何指针的算术运算
11.面试题
1.野指针是什么?成因以及解决方法?
“野指针”的成因主要有3种:
① 指针变量没有被初始化。任何指针变量刚被创建时不会自动成为NULL指针,它的缺省值是随机的,它会乱指一气。所以,指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内存。例如
② 指针p被free或者delete之后,没有置为NULL;
③ 指针操作超越了变量的作用范围:不要返回指向栈内存的指针或引用,因为栈内存在函数结束时会被释放。
如何避免野指针:
①将指针初始化为NULL。
②用malloc分配内存 :char * p = (char * )malloc(sizeof(char));
③用已有合法的可访问的内存地址对指针初始化,指针用完后释放内存,将指针赋NULL。
2. 悬空指针和野指针有什么区别?
-
野指针:野指针是不确定其具体指向的指针,一般指那些未初始化的指针。
-
悬空指针:一个指针的指向对象已经被删除,那么就成了悬空指针。
三、数组和字符串
1.一维数组
所谓一维数组是指只有一个下标的数组。它在计算机的内存中是连续存储的。
C 语言中,一维数组的说明一般形式如下:< 存储类型 > < 数据类型 > < 数组名 >[< 表达式 >] ;
注意事项:
- C 语言对数组不作越界检查,使用时要注意 int a[5]; a[5] = 10
- 关于用变量定义数组维数 int i = 15; int a[i];
- 数组必须先定义,后使用只能逐个引用数组元素,不能一次引用整个数组 int a[10] printf("%d", a); (X)
- 数组元素表示形式:数组名 [ 下标 ] 其中:下标可以是常量或整型表达式,
- 初始化方式:在定义数组时,为数组元素赋初值,数组不初始化,其元素值为随机数,对 static 数组元素不赋初值,系统会自动赋以 0 值, 只给部分数组元素赋初值,其余为0。
2.二维数组
定义方式:(声明时列数不能省略,行数可以)
数据类型 数组名 [ 常量表达式 ][ 常量表达式 ] ;
内存是一维的-二维数组:按行序优先
3.字符数组与字符串
字符数组是元素的数据类型为字符类型的数组 char c[10], ch[3][4];
字符数组的初始化
– 逐个字符赋值
– 用字符串常量
C语言中无字符串变量,用字符数组处理字符串字符串结束标志:‘'\0’
4.字符串函数
C 库中实现了很多字符串处理函数
#include <string.h>
几个常见的字符串处理函数
① 求字符串长度的函数 strlen
格式: strlen( 字符数组 )
功能:计算字符串长度
返值:返回字符串实际长度,不包括‘ \0’ 在内
\xhh 表示十六进制数代表的符号
\ddd 表示 8 进制的
– 例:对于以下字符串, strlen(s) 的值为:
char s[10]={‘A’,‘\0’,‘B’,‘C’,‘\0’,‘D’};
char s[ ]=“\t\v\\\0will\n”;
char s[ ]=“\x69\141\n”;
答案: 1 3 3
② 字符串拷贝函数 strcpy
格式: strcpy( 字符数组 1, 字符串 2)
功能:将字符串 2 ,拷贝到字符数组 1 中去
返值:返回字符数组 1 的首地址
说明:字符数组 1 必须足够大,拷贝时‘ \0’ 一同拷贝
③ 字符串连接函数 strcat
格式: strcat( 字符数组 1, 字符数组 2)
功能:把字符数组 2 连到字符数组 1 后面
返值:返回字符数组 1 的首地址
说明:字符数组 1 必须足够大,连接前 , 两串均以‘ \0’ 结束 ; 连接后 , 串1 的 ‘ \0’ 取消 , 新串最后加‘ \0
④ 字符串比较函数 strcmp
格 式: strcmp( 字符串 1, 字符串 2)
功 能:比较两个字符串
比较规则:对两串从左向右逐个字符比较( ASCII 码),直到遇到不同字符或‘ \0’ 为止
返 值:返回 int 型整数
a. 若字符串 1< 字符串 2 , 返回负整数
b. 若字符串 1> 字符串 2 , 返回正整数
c. 若字符串 1== 字符串 2 , 返回零
字符串函数
strncpy(p, p1, n) 复制指定长度字符串
strncat(p, p1, n) 附加指定长度字符串
strcasecmp 忽略大小写比较字符串
strncmp(p, p1, n) 比较指定长度字符串
strchr(p, c) 在字符串中查找指定字符
strstr(p, p1) 查找字符串
字符串函数
isalpha() 检查是否为字母字符
isupper() 检查是否为大写字母字符
islower() 检查是否为小写字母字符
isdigit() 检查是否为数字
5.面试题
1.sizeof关键字与strlen函数的区别
Sizeof:编译器在编译时就计算出了sizeof的结果,而strlen函数必须在运行时才能计算出来。并且sizeof计算的是数据类型占内存的大小,而strlen计算的是字符串实际的长度
strlen只能测量字符串计算字符串 str 的长度,直到空结束字符,但不包括空结束字符
2.strcpy和memcpy区别
1.复制的内容不同。
strcpy只能复制字符串,
memcpy可以复制任意内容,例如字符数组、整型、结构体、类等。
2.复制的方法不同。
strcpy不需要指定长度,遇到结束符"\0"才结束,如果空间不够,就会引起踩内存。
memcpy则是根据其第3个参数决定复制的长度。
3.用途不同。
通常在复制字符串时用strcpy,而需要复制其他类型数据时则一般用memcpy,由于字符串是以“\0”结尾的,所以对于在数据中包含“\0”的数据只能用memcpy。
四、关键字
1.常量
常量是指在程序运行期间其数值不发生变化的数据。
整型常量通常简称为整数。
浮点常量又称为实数,一般含有小数部分。在 C 语言中,实数只有十进制的实数,分为单精度和
双精度。实数有两种表示方法, 即一般形式和指数形式。指数形式的实数一般是由尾数部分、字母 e 或 E 和指数部分组成。 当一个实数的符号为正号时,可以省略不写,其表示的一般形式如下:– 1.176e+10 表示 1.176×1010 - 3.5789e-8 表示 -3.5789×10-8
字符常量是指一个单一字符, 其表示形式是由两个单引号包括的一个字符。‘A’, ‘a’, ‘Q’, ‘0’, ‘9’, ‘+’,
‘:’, ‘?’, ‘$’ 都是字符常量。在 C 语言中, 字符常量具有数值。字符常量的值就是该字符的 ASCII 码值。可以把字符常量看做一个字节的正整数。
所谓字符串常量是指用双引号括起来的一串字符来表示的数据。(字符串以\0结尾)“9” ——'9','0'
1.#define
所谓标识常量是指用标识符代替常量使用的一种常量 ,其名称通常是一个标识符。标识常量也叫符号常量,一般用大写英文字母的标识符。
在使用之前必须预先定义。说明形式为:#define < 标识常量名称 > < 常量 >
一个宏名字之后,可以在其他宏定义中使用 , 例如:
#define ONE 1
#define TWO ONE+ONE
#define THREE ONE+TWO (完全替换,不会计算出来为3,c = THREE * b + a —— c = ONE + TWO * b + a)
如果一个串长于一行,可在行尾用反斜线” \” 续行
2.运算符
3.变量
- char存储大小1字节,值范围-128~127;
- unsigned char存储大小1字节,值范围0~255;
- short存储大小2字节,值范围-32768~32767;
- unsigned short存储大小2字节,值范围0~65535;
- int——
16位系统存储大小2字节,值范围-32768~32767,
32、64位系统存储大小4字节,值范围-2147483648~2147483647;
- unsigned int——
16位系统存储大小2字节,值范围0~65535,
32、64位系统存储大小4字节,值范围0~4294967295;
- long——
16、32位系统存储大小4字节,值范围-2147483648~2147483647,
64位系统存储大小8字节,值范围-9223372036854775808~9223372036854775807;
- unsigned long——
16、32位系统存储大小4字节,值范围0~4294967295,
64位系统存储大小8字节,值范围0~18446744073709551615;
- float存储大小4字节,值范围1.175494351*10^-38~3.402823466*10^38;
- double存储大小8字节,值范围2.2250738585072014*10^-308~1.7976931348623158*10^308;
- long long存储大小8字节,值范围-9223372036854775808~9223372036854775807;
- unsigned long long存储大小8字节,值范围0~18446744073709551615;
- long double——
16位系统存储大小8字节,值范围2.22507*10^-308~1.79769*10^308,
32位系统存储大小12字节(有效位10字节,为了对齐实际分配12字节),值范围3.4*10^-4932 到 1.1*10^4932,
64位系统存储大小16字节(有效位10字节,为了对齐实际分配16字节),值范围3.4*10^-4932 到 1.1*10^4932;
- 指针——
16位系统存储大小2字节,
32位系统存储大小4字节,
64位系统存储大小8字节。
- 变量在程序中用变量名表示。
- 变量名由字母、数字、下划线组成,不能以数字开头,不能和 C 的关键字重名。
- 在程序运行时,变量占据存储空间的大小由其数据类型决定。
- 变量在内存空间中的首地址,称为变量的地址。
- 变量说明的一般形式是:< 存储类型 > < 数据类型 > < 变量名 > ;
< 存储类型 > 是关键词 auto 、 register 、 static 和 extern
1.结构体
1.auto
说明的变量只能在某个程序范围内使用,通常在函数体内或函数中的复合语句里。(默认是随机值)在函数体的某程序段内说明 auto 存储类型的变量时可以省略关键字 auto ,如下:
auto int k ; int j ; double x;
2.register
称为寄存器型,register变量是想将变量放入CPU的寄存器中,这样可以加快程序的运行速度。如申请不到就使用一般内存,同auto ; register变量必须是能被CPU所接受的类型。这通常意味着register变量必须是一个单个的值,并且长度应该小于或者等于整型的长度。不能用“&”来获取register变量的地址。由于寄存器的数量有限,真正起作用的register修饰符的数目和类型都依赖于运行程序的机器。
在某些情况下,把变量保存在寄存器中反而会降低程序的运行速度。因为被占用的寄存器不能再用于其它目的;或者变量被使用的次数不够多,不足以装入和存储变量所带来的额外开销。
3.static
变量称为静态存储类型的变量,既可以在函数体内,也可在函数体外说明 ( 默认是 0 )。局部变量使用 static 修饰 , 有以下特点 :在内存中以固定地址存放的,而不是以堆栈方式存放,只要程序没结束,就不会随着说明它的程序段的结束而消失,它下次再调用该函数,该存储类型的变量不再重新说明,而且还保留上次调用存入的数值。
static 修饰的全部变量,其它文件无法使用。
C++ static修饰类
1️⃣:静态成员为所有类对象所共享,不属于某个具体的实例
2️⃣:静态成员变量必须在类外定义,定义时不添加static关键字
3️⃣:静态成员函数没有隐藏的this指针,不能访问任何非静态成员,不能被virtual修饰
4️⃣:访问静态成员变量的特殊方式
5️⃣:静态成员和类的普通成员一样,也有public、protected、private3种访问级别,也可以具有返回值
4.extern
当变量在一个文件中的函数体外说明,所有其他文件中的函数或程序段都可引用这个变量。extern 称为外部参照引用型,使用 extern 说明的变量是想引用在其它文件中函数体外部说明的变量。
extern修饰变量的声明:
如果文件a.c需要引用b.c中变量int v,就可以在a.c中声明extern int v,然后就可以引用变量v。
extern修饰函数的声明:
如果文件a.c需要引用b.c中的函数,比如在b.c中原型是int fun(int mu),那么就可以在a.c中声明extern int fun(int mu),然后就能使用fun来做任何事情。就像变量的声明一样,extern int fun(int mu)可以放在a.c中任何地方,而不一定非要放在a.c的文件作用域的范围中。
extern修饰符可用于指示C或者C++函数的调用规范:
比如在C++中调用C库函数,就需要在C++程序中用extern “C”声明要引用的函数。这是给链接器用的,告诉链接器在链接的时候用C函数规范来链接。主要原因是C++和C程序编译完成后在目标代码中命名规则不同。
4.常见关键字
1.assert
assert宏的原型定义在<assert.h>中,其作用是如果它的条件返回错误,则终止程序执行,原型定义:
#include <assert.h>
void assert( int expression );
assert的作用是现计算表达式 expression ,如果其值为假(即为0),那么它先向stderr打印一条出错信息,然后通过调用 abort 来终止程序运行。
C 语言库函数 abort 函数的作用是异常终止一个进程,意味着 abort 后面的代码将不再执行。
abort 函数是一个比较严重的函数,当调用它时,会导致程序异常终止,而不会进行一些常规的清除工作,比如释放内存等。
例如参数传入一个指针为NULL时,程序就会奔溃时,我们可以增加assert来防御这种问题。
2.const
阻止一个变量被改变,可以使用const关键字。
1.const变量 (常量化变量)
一般说明形式如下:const < 数据类型 > 变量名 = [< 表达式 >] ;
常量化变量是为了使得变量的值不能修改,变量有 const 修饰时,若想用指针间接访问变量,指针也要有 const 修饰。
2.const修饰指针
(常量化指针目标表达式)
一般说明形式如下:const < 数据类型 > * < 指针变量名称 > = [< 指针运算表达式>] ;
常量化指针目标是限制通过指针改变其目标的数值 ,但 < 指针变量 > 存储的地址值可以修改。
(常量化指针变量)
一般说明形式如下: < 数据类型 > * const < 指针变量名称 > = [< 指针运算表达式>] ;
使得 < 指针变量 > 存储的地址值不能修改。但可以通过 *< 指针变量名称 > 可以修改指针所指向变量的数值。
(常量化指针变量及其目标表达式)
一般说明形式如下:
const < 数据类型 > * const < 指针变量名 > = <指针运算表达式 > ;
常量化指针变量及其目标表达式,使得既不可以修改 <指针变量 > 的地址,也不可以通过 *< 指针变量名称 >修改指针所指向变量的值。
const修饰谁 谁不能修改。
3.const修饰形参
表明它是一个输入参数,在函数内部不能改变其值
在引用或指针传递函数调用中,const 保护了实参所指向的变量。因为在编译阶段编译器对调用函数的选择是根据实参进行的,所以,只有引用传递和指针传递可以用是否加const来重载。
3.volatile
volatile 关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素更改。volatile 提醒编译器它后面所定义的变量随时都有可能改变,因此编译后的程序每次需要存储或读取这个变量的时候,都会直接从变量地址中读取数据。如果没有 volatile 关键字,则编译器可能优化读取和存储,可能暂时使用寄存器中的值,如果这个变量由别的程序更新了的话,将出现不一致的现象。所以遇到这个关键字声明的变量,编译器对访问该变量的代码就不再进行优化,从而可以提供对特殊地址的稳定访问。
通常用于以下情况:
- 多线程访问共享变量:当多个线程同时访问同一个变量时,为了保证数据的正确性,需要使用volatile关键字来禁止编译器对变量进行优化。
- 中断处理函数中的变量:中断处理函数通常需要访问外部设备的寄存器或内存,这些变量的值可能会在任何时候被改变,因此需要使用volatile关键字来确保每次访问都能从内存中读取最新的值。
4.sizeof
sizeof运算符,运算的一般形式:sizeof(<类型或变量名>) 注意∶它只针对数据类型,而不针对变量!
C/C++中,sizeof()只是运算符号,是编译的时候确定大小的。动态分配是运行过程中得到大小的,也就是说C++中new出来的内存,sizeof都无法统计的,退一步说,即使是new出来的空间也有可能失败,所以sizeof无法统计动态分配的内存大小。
5.typedef
C 语言提供了 typedef 关键字,您可以使用它来为类型取一个新的名字。下面的实例为单字节数字定义了一个术语 BYTE:
typedef unsigned char BYTE;
在这个类型定义之后,标识符 BYTE 可作为类型 unsigned char 的缩写,例如:
BYTE b1, b2;
5.面试题
1.extern与static区别
static:
作用于局部变量时:叫静态局部变量,在函数调用时,只有在该函数第一次调用时才对其分配空间和初始化。在函数调用结束时,不对该变量的内存进行释放,值仍然保留。这也是于auto自动变量的区别。
作用于全局变量时: 叫静态全局变量。表示该变量是私有的,只能在该文件使用。不能通过extern关键字对其引用。
作用于函数时: 叫静态函数,表示该函数是私有的,只能在本文件中使用,不能通过extern关键字对其引用
extern:
本文件: 定义在本文件下面的全局变量,想要在上面使用时需要使用extern关键字对其声明
其他文件:定义在其他文件的全局变量想要在本文件使用时,若该变量未被static修饰时可通过extern关键字在本文件对其声明。即可使用
extern修饰符可用于指示C或者C++函数的调用规范:
比如在C++中调用C库函数,就需要在C++程序中用extern “C”声明要引用的函数。这是给链接器用的,告诉链接器在链接的时候用C函数规范来链接。主要原因是C++和C程序编译完成后在目标代码中命名规则不同。
头文件声明时加extern定义时不要加 因为extern可以多次声明,但只有一个定义
2.C 语言的关键字 static 和 C++ 的关键字 static 有什么区别
在C中static用来修饰局部静态变量和外部静态变量、函数。而C++中除了上述功能外,还用来定义类的成员变量和函数。即静态成员和静态成员函数。
注意:编程时static的记忆性,和全局性的特点可以让在不同时期调用的函数进行通信,传递信息,而C++的静态成员则可以在多个对象实例间进行通信,传递信息。
2.const和define的区别
1.编译器处理方式
define – 在预处理阶段进行替换
const – 在编译时确定其值
2.类型检查
define – 无类型,不进行类型安全检查,可能会产生意想不到的错误
const – 有数据类型,编译时会进行类型检查
3.内存空间
define – 不分配内存,给出的是立即数,有多少次使用就进行多少次替换,在内存中会有多个拷贝,消耗内存大
const – 在静态存储区中分配空间,在程序运行过程中内存中只有一个拷贝
4.其他
宏只作替换,不做计算,不做表达式求解。
5.const不能重定义,而define可以通过#undef
取消某个符号的定义,进行重定义;
总结
const比define安全
3.#define和别名typedef的区别
执行时间不同,typedef在编译阶段有效,typedef有类型检查的功能;#define是宏定义,发生在预处理阶段,不进行类型检查;
功能差异,typedef用来定义一种数据类型的别名,定义与平台无关的数据类型,与struct的结合使用等。#define不只是可以为类型取别名,还可以定义常量、变量、编译开关等。
作用域不同,#define没有作用域的限制,只要是之前预定义过的宏,在以后的程序中都可以使用。而typedef有自己的作用域。
4.#include 的顺序以及尖括号和双引号的区别
尖括号表示编译器只在系统默认目录或尖括号内的工作目录下搜索头文件,并不去用户的工作目录下寻找,所以一般尖括号用于包含标准库文件;
双引号表示编译器先在用户的工作目录下搜索头文件,如果搜索不到则到系统默认目录下去寻找,所以双引号一般用于包含用户自己编写的头文件。
5.cout和printf有什么区别?
cout<<是一个函数,cout<<后可以跟不同的类型是因为cout<<已存在针对各种类型数据的重载,所以会自动识别数据的类型。
printf没有类型检查,不安全。cout是通过运算符重载实现的,安全。
printf是函数。cout是ostream对象,和<<配合使用。
cout是有缓冲输出,输出过程会首先将输出字符放入缓冲区,然后输出到屏幕。 printf是无缓冲输出,有输出时立即输出。
5. strcpy函数和strncpy函数的区别?哪个函数更安全?
char* strcpy(char* strDest, const char* strSrc)
char* strncpy(char* strDest, const char* strSrc, int pos)
strcpy函数: 如果参数 dest 所指的内存空间不够大,可能会造成缓冲溢出(buffer Overflow)的错误情况,在编写程序时请特别留意,或者用strncpy()来取代。
strncpy函数:用来复制源字符串的前n个字符,src 和 dest 所指的内存区域不能重叠,且 dest 必须有足够的空间放置n个字符
五、内存
1.内存分区
内存总共分为五大分区:栈区(函数的参数值,局部变量),堆区(new
或malloc
出来的对象),全局静态区(全局、静态变量),常量文本区,程序代码区(存放程序体的二进制代码)。
从生存周期来说:
- 栈区是运行时分配,结束就释放。(类似数据结构中的栈)
- 堆区是程序员主动分配和释放。(类似数据结构中的链表)
- 全局静态区是程序运行前分配,程序结束释放。初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。
- 常量文本区也是程序运行前分配,程序结束释放
从管理角度来说:只有堆区是由程序员自己来管理,其他四个区都是由系统来管理的。
从可能产生的问题来说:
- 栈区可能会造成栈溢出(1、深度递归,栈帧太多 2、只分配不释放,栈耗尽)。
- 堆区可能造成内存泄漏(就是申请空间,然后忘记释放了),野指针(指向了一块已经被释放的空间),内存碎片。
从效率来说:栈区比堆区效率高
从内存大小来说:栈区大小m,堆区大小和内存有关。最大可1G。其他三个区都很小
2.面试题
1. 深拷贝与浅拷贝?
浅拷贝 —仅仅是指向被复制的内存地址,如果原地址中对象被改变了,那么浅复制出来的对象也会相应改变。
深拷贝 —-在计算机中开辟了一块新的内存地址用于存放复制的对象。
浅拷贝在多个对象指向一块空间的时候,释放一个空间会导致其他对象所使用的空间也被释放了,再次释放便会出现错误。
2.. c和c++的区别?
- C是面向过程,C++是面向对象的。
- C通过malloc和free来进行堆内存的分配和释放,而C++除此之外还有new/delete关键字;
- C++的类是C所没有的,但是C中的struct是可以在C++中正常使用的,并且C++对struct进行了进一步的扩展,使struct在C++中可以和class一样当做类使用,而唯一和class不同的地方在于struct的成员默认访问修饰符是public,而class默认的是private;
- C++支持函数重载,而C不支持函数重载,而C++支持重载的依仗就在于C++的名字修饰与C不同,例如在C++中函数int fun(int ,int)经过名字修饰之后变为 _fun_int_int ,而C是 _fun(与参数无关),所以C++才会支持不同的参数调用不同的函数;
- C++中有引用,而C没有;
- 当然还有C++全部变量的默认链接属性是外链接,而C是内连接;
- C 中用const修饰的变量不可以用在定义数组时的大小,但是C++用const修饰的变量可以(如果不进行&,解引用的操作的话,是存放在符号表的,不开辟内存);
- 强制类型转换上也不一样,C的强制类型转换使用()小括号里面加类型进行类型强转的,而C++有四种自己的类型强转方式,分别是const_cast,static_cast,reinterpret_cast和dynamic_cast;
- 由于C++多了一个类,因此和C语言的作用域比起来,就多了一个类作用域,此外,C++还支持namespace名字空间,可以让用户自己定义新的名字空间作用域出来,避免全局的名字冲突问题。
3.堆与栈的区别?
管理方式:对于栈来讲,是由编译器自动管理,无需我们手工控制;对于堆来说,释放工作由程序员控制,容易产生内存泄漏。
空间大小:一般来讲在32位系统下,堆内存可以达到4G的空间,从这个角度来看堆内存几乎是没有什么限制的。但是对于栈来讲,一般都是有一定的空间大小的。
碎片问题:对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题,因为栈是先进后出的队列,不可能有一个内存块从栈中间弹出,在他弹出之前,在他上面的后进的栈内容已经被弹出。
生长方向:对于堆来讲,生长方向是向上的,也就是向着内存地址增加的方向;对于栈来讲,它的生长方向是向下的,是向着内存地址减小的方向增长。
分配方式:堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。静态分配是编译器完成的,比如局部变量的分配。动态分配由alloca()函数进行分配,但是栈的动态分配和堆是不同的,它的动态分配是由编译器进行释放,无需我们手工实现。
分配效率:
栈是机器系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高。
堆则是C/C++函数库提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到足够大小的内存,然后进行返回。显然,堆的效率比栈要低得多。
4. new和malloc的区别?
- new/delete是C++关键字,需要编译器支持。malloc/free是库函数,需要头文件支持;
- 使用new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算。而malloc则需要显式地指出所需内存的大小。
- new操作符内存分配成功时,返回的是对象类型的指针,类型严格与对象匹配,无须进行类型转换,故new是符合类型安全性的操作符。而malloc内存分配成功则是返回void * ,需要通过强制类型转换将void*指针转换成我们需要的类型。
- 在使用new分配内存空间时,内存空间不够时会抛出bac_alloc异常。malloc分配内存失败时返回NULL。
- new会先调用operator new函数,申请足够的内存(通常底层使用malloc实现),然后调用类型的构造函数,初始化成员变量,最后返回自定义类型指针。delete先调用析构函数,然后调用operator delete函数释放内存(通常底层使用free实现)。
- malloc/free是库函数,只能动态的申请和释放内存,无法强制要求其做自定义类型对象构造和析构工作。
5.讲讲大端小端
大端模式:是指数据的高字节保存在内存的低地址中,⽽数据的低字节保存在内存的高地址端。
小端模式,是指数据的高字节保存在内存的高地址中,低位字节保存在在内存的低地址端。