C/C++面试题的知识点(1)

1、char[] 数组

char * str = "01234";

str存储时需要6个字节,包括末尾的’\0’。

2、strcpy
库函数strcoy拷贝字符串时,从源地址一直向后拷贝,直至遇见末尾的’\0’为止。没有遇到结束符导致越界访问,程序崩溃。

3、实现strcpy函数

//返回值为目的地址,方便链式操作;形参加const表明输入
char * strcpy(char *strDest, const char *strSrc)
{
    //对两个地址加非0断言
    assert((strDest != nullptr)&&(strSrc != nullptr));
    char * address = strDest;
    while((*strDest++ = *strSrc++) != '\0');
    return address
}

4、局部变量和文字静态区
如下代码会有什么问题:

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

存在问题:
(1)函数生成一个局部变量p,存储在栈中,指向字面常量的一个拷贝副本的栈地址。字面常量存在静态存储区,生命周期为整个程序执行期。函数结束,p被回收,副本消失。p返回的地址,指向栈存储区被取消标记,随时被系统修改。因此p指向的内容不确定。
(2)返回p时,会产生一个临时变量,把p的值传递到临时变量,同时p被回收。因此可以修改为

char *p = "hello world";

让p直接指向静态区。这个地址不会变。因此返回值可用。

5、Getmemory
如下代码存在哪些问题?

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

(1)应加申请内存失败判断

if(*p == nullptr || num < 0)
{
    perror("malloc fail!");
    return -1;
}

(2)应释放内存

free(str);
//避免野指针
str = nullptr;

(3)printf(str)可能发生格式化字符串攻击,写全了。

printf("%s", str);

6、局部野指针
如下代码有设么错误?

void swap( int* p1,int* p2 )
{
    int *p;
    *p = *p1;
    *p1 = *p2;
    *p2 = *p;
}

p是一个未初始化的野指针,有可能指向系统区,导致程序崩溃。改为

int *p = nullptr;

也是错误的,有可能指向有意义区域,破坏数据。正确改为:

void swap( int* p1,int* p2 )
{
    int p = 0;
    p = *p1;
    *p1 = *p2;
    *p2 = p;
}

7、BOOL、int、float、指针和“零值”的比较

//BOOL
if(var == flase)

//int,这样写如果将==错写成=,错误提示
if(0 == var)

//float,不能和0直接比
const float Epsilon = 1e-5;
if((var >= -Epsilon) && (var <= Epsilon))

//指针
if(var == nullptr)

8、数组名作为形参的退化
有以下代码计算sizeof的值

void Func ( char str[100] )
{
    sizeof( str ) = ?
}
void *p = malloc( 100 );
sizeof ( p ) = ?

(1)数组名作为函数形参,在函数体内退化为普通指针。可修改,可++/–操作。故sizeof( str ) = 4。
(2)指针长度为4字节,故sizeof ( p ) = 4。
(3)正常情况下如

char str[10];
//sizeof(str)为10。

数组名可转化为指针,指向内存中数组首地址。此时为指针常量,其保存的地址不可被修改。因此不能做自增减操作,不可被修改。

8、关于宏

写一个标准宏MIN, 输入两个参数返回较小的一个。
least = MIN(*p++, b); 会有什么问题?

宏定义可实现类似函数功能,但终归不是函数,宏展开对参数进行老老实实的替换。
(1)老老实实的将宏定义的参数和整个宏,加括号防止问题。

#define MIN(A,B) ((A) <= (B) ? (A) : (B))   

(2)防止宏的副作用。上述表达式原原本本替换后,会导致指针p作自增运算。

((*p++) <= (b) ? (*p++) : (b))   

(3)宏定义后面不能加分号,否则被替换进去。

想起大一时邓凡老师出了道题目,全体阵亡。就是宏的题目,至今记忆尤深。
define A 32+3
a = A * 3
问a的值。大家自然一看简单,不就是35*3=105吗?但结果应该是32+3*3=41。原因:宏是老老实实替换。

9、标准头文件

#ifndef __INCvxWorksh
#define __INCvxWorksh 
#ifdef __cplusplus
extern "C" {
#endif 
/*...*/ 
#ifdef __cplusplus
}
#endif 
#endif /* __INCvxWorksh */

(1)为确保各个文件中类的定义一致,类通常定义在头文件中。谁要用包含下该头文件即可。如果两个都包含,类可能会被重新定义2次。确保头文件被多次包含仍能安全工作的常用技术就是预处理器

(2)常用的#include就是预处理功能,在编译前将制定头文件内容替换。另一个预处理功能是头文件保护符。依赖于预处理变量,一种是已定义,另一种是未定义。#define把一个名字设定为预处理变量。#ifdef当且仅当该变量已定义时为真,#ifndef当且仅当变量未定义时为真。一旦检查结果为真,执行后续操作直至遇到#endif指令为止。预处理变量无视c++作用域的规则。有效防止重复包含。预处理变量必须唯一,通常将头文件的类名构建名字,全部大写。

(3)extern “c”
cpp文件编译器会自己定义__cplusplus宏。extern表示函数或变量,可以在本模块和其他模块使用。
为了在c++代码中使用c代码,此声明可让编译器按照c语言编译规则来编译代码。为什么这样讲?
c++支持重载,编译会将真实函数名+函数参数。c不支持重载使用原函数名。这样就找不见在别的模块中使用c规则编译的此函数的代码,报链接错误。

10、将char数组内容循环右移n个。

void loop(char *p, int n)
{
    //strlen计算到'\0'
    int len = strlen(p);
    if (len == 0 || n < 0)
        return;
    //避免循坏移动
    n = n % len;
    char *tmp = new char[len];
    //后移
    memcpy(tmp+n, p, (len-n));
    //出来的补到前头
    memcpy(tmp, p+len-n, n);
    //全部拷贝给原串
    memcpy(p, tmp, len);
    delete[] tmp;
}

你可能感兴趣的:(C/C++基础点)