点击上方“程序人生”,选择“置顶公众号”
第一时间关注程序猿(媛)身边的故事
1.模拟实现 strstr
[cpp] view plain copy
char* MyStrstr( const char* str1, const char* str2 ){
assert( NULL != str1 && NULL != str2 );
while ('\0' != *str1){
const char* p1 = str1;
const char* p2 = str2;
while (('\0' != *p1) && ('\0' != *p2) && (*p1 == *p2)){
++p1;
++p2;
}
if ('\0' == *p2)
return str1;
++str1;
}
return NULL;
}
2.模拟实现 strcpy
[cpp] view plain copy
char* MyStrcpy(char* dest, const char* src){
assert( NULL != dest && NULL != src );
char* pAddress = dest;
while ('\0' != (*dest++ = *src++))
;
return pAddress;
}
3.写一个“标准”宏MIN,这个宏输入两个参数并返回较小的一个。另外,当你写下面的代码时会发生什么事?
least = MIN(*p++, b);
:
[cpp] view plain copy
#define MIN(A,B) ((A) <= (B) ? (A) : (B))
注意:
[cpp] view plain copy
MIN(*p++, b)会产生宏的副作用
宏定义可以实现类似于函数的功能,但是它终归不是函数,而宏定义中括弧中的“参数”也不是真的参数,在宏展开的时候对“参数”进行的是一对一的替换
程序员对宏定义的使用要非常小心,特别要注意两个问题:
(1)谨慎地将宏定义中的“参数”和整个宏用用括弧括起来。所以,严格地讲,下述解答:
#define MIN(A,B) (A) <= (B) ? (A) : (B)
#define MIN(A,B) (A <= B ? A : B )
都应判0分
(2)防止宏的副作用
宏定义#define MIN(A,B) ((A) <= (B) ? (A) : (B))对MIN(*p++, b)的作用结果是:
((*p++) <= (b) ? (*p++) : (b))
这个表达式会产生副作用,指针p会作2次++自增操作
除此之外,另一个应该判0分的解答是:
#define MIN(A,B) ((A) <= (B) ? (A) : (B));
这个解答在宏定义的后面加“;”,显示编写者对宏的概念模糊不清,只能被无情地判0分并被面试官淘汰
4.为什么标准头文件都有类似以下的结构?
[cpp] view plain copy
#ifndef __INCvxWorksh
#define __INCvxWorksh
#ifdef __cplusplus
extern "C" {
#endif
/*...*/
#ifdef __cplusplus
}
#endif
#endif /* __INCvxWorksh */
头文件中的编译宏
[cpp] view plain copy
#ifndef __INCvxWorksh
#define __INCvxWorksh
#endif
的作用是防止被重复引用。
作为一种面向对象的语言,C++支持函数重载,而过程式语言C则不支持。函数被C++编译后在symbol库中的名字与C语言的不同。例如,假设某个函数的原型为:
void foo(int x, int y);
该函数被C编译器编译后在symbol库中的名字为_foo,而C++编译器则会产生像_foo_int_int之类的名字。_foo_int_int这样的名字包含了函数名和函数参数数量及类型信息,C++就是靠这种机制来实现函数重载的。
为了实现C和C++的混合编程,C++提供了C连接交换指定符号extern "C"来解决名字匹配问题,函数声明前加上extern "C"后,则编译器就会按照C语言的方式将该函数编译为_foo,这样C++语言中就可以调用C语言的函数了。
C++支持函数重载,C语言不支持函数重载。C++为了支持函数重载,按照不同编译器的函数修饰规则,对函数名进行了处理,加入函数的参数类型进行修饰。这里函数是被C编译器编译的,编译后函数名没有进行修饰,所以要加extern"c"告诉编译器使用c的方式进行查找链接。
5.编写类String的构造函数、析构函数和赋值函数,已知类String的原型为:
[cpp] view plain copy
class String{
public:
String( const char* str = NULL ); // 普通构造函数
String(const String &other); // 拷贝构造函数
~ String(void); // 析构函数
String & operator =(const String &other); // 赋值函数
private:
char *m_data; // 用于保存字符串
};
:
[cpp] view plain copy
// 普通构造函数
String::String( const char* str ){
if (NULL==str){
m_data = new char[1]; // 对空字符串自动申请存放结束标志'\0'的空间
*m_data = '\0';
}
else{
int length = strlen(str);
m_data = new char[length+1];
strcpy(m_data, str);
}
}
// String的析构函数
String::~String( void ){
delete [] m_data; // 或delete m_data;
}
// 拷贝构造函数
// 输入参数为const型
String::String( const String& other ){
int length = strlen( other.m_data );
m_data = new char[length+1];
strcpy( m_data, other.m_data );
}
// 赋值函数
// 输入参数为const型
MyString& MyString::operator=( const MyString& str ){
// 检查自赋值
if ( &str != this ){
MyString strTemp( str );
char* pTemp = strTemp.m_pData;
strTemp.m_pData = m_pData;
m_pData = pTemp;
}
// 返回本对象的引用
return *this;
}
当类中包括指针类成员变量时,一定要重载其拷贝构造函数、赋值函数和析构函数
如果出函数作用域后还存在则可以返回引用, 不存在则不能返回引用,要返回临时对象
6.请写一个C函数,若处理器是Big_endian的,则返回0;若是Little_endian的,则返回1(这里的大小端问题重点关注的是如何判断处理器是大端还是小端(笔试)。 大小端问题要完整掌握,还需与另一篇关注为什么有大小端,大小端的区别是什么的面试博客一起学习 )
大端模式,是指数据的高字节保存在内存的低地址中,而数据的低字节保存在内存的高地址中。这样的存储模式有点儿类似于把数据当作字符串顺序处理:地址由小向大增加,而数据从高位往低位放;这和我们的阅读习惯一致。
小端模式,是指数据的高字节保存在内存的高地址中,而数据的低字节保存在内存的低地址中,这种存储模式将地址的高低和数据位权有效地结合起来,高地址部分权值高,低地址部分权值低。
unsigned int value = 0x12345678
Big-Endian: 低地址存放高位,如下:
高地址
---------------
(0x78) -- 低位
(0x56)
(0x34)
(0x12) -- 高位
---------------
低地址
Little-Endian: 低地址存放低位,如下:
高地址
---------------
(0x12) -- 高位
(0x34)
(0x56)
(0x78) -- 低位
--------------
低地址
e684 6c4e
在大端模式下,前32位应该这样读: e6 84 6c 4e(一个字节为8个二进制位,为2个16进制数字,所以这样表示 e6而不是6e)
记忆方法: 地址的增长顺序与值的增长顺序相反
在小端模式下,前32位应该这样读: 4e 6c 84 e6
记忆方法: 地址的增长顺序与值的增长顺序相同
:
[cpp] view plain copy
int CheckCPU( ){
union w{
int a;
char b;
} c;
c.a = 1;
return (1 == c.b);
}
联合体union的存放顺序是所有成员都从低地址开始存放,利用该特性获得了CPU对内存采用Little-endian还是Big-endian模式读写
联合体:各成员共享一段内存空间,一个联合变量的长度等于各成员中最长的长度。这里所谓的共享不是指把多个成员同时装入一个联合变量内,而是指该联合变量可被赋予任一成员值,但每次只能赋一种值,赋入新值则冲去旧值
7.写一个函数返回1+2+3+…+n的值(假定结果不会超过长整型变量的范围)
:
[cpp] view plain copy
long Sum( long n ){
return ( (long)1 + n ) * n / 2;
}
8.宏函数 --> 交换两个数
:
[cpp] view plain copy
#define SWAP( a, b ) \
do { \
(a) = ((a)^(b)); \
(b) = ((a)^(b)); \
(a) = ((a)^(b)); \
}while (0)
1.SWAP和括号间不能有空格,否则会被错误替换
2.续航符后面不能有空格,否则编译器会报错,但是新的一行开头可以有空格
do { } while( 0 )意义:
辅助定义复杂的宏,避免引用的时候出错:
假设你需要定义这样一个宏:
#define DOSOMETHING( ) \
foo1( ); \
foo2( );
这个宏的本意是,当调用DOSOMETHING()时,函数foo1()和foo2()都会被调用。但是如果你在调用的时候这么写:
if (a>0)
DOSOMETHING( );
因为宏在预处理的时候会直接被展开,你实际上写的代码是这个样子的:
if (a>0)
foo1( );
foo2( );
这就出现了问题,因为无论a是否大于0,foo2()都会被执行,导致程序出错。
那么仅仅使用{}将foo1()和foo2()包起来行么?
我们在写代码的时候都习惯在语句右面加上分号,如果在宏中使用{},代码里就相当于这样写了:“{...};”,展开后就是这个样子:
if (a>0)
{
foo1( );
foo2( );
};
这样甚至不会编译通过。所以,很多人才采用了do {...}while (0);
#define DOSOMETHING( ) \
do { \
foo1( ); \
foo2( ); \
}while (0) \
if (a>0)
DOSOMETHING( );
这样,宏被展开后,才会保留初始的语义。
8.模拟实现 strncpy
:
[cpp] view plain copy
char* MyStrncpy( char* dest, const char* src, size_t len ){
assert( NULL != dest && NULL != src );
char* ret = dest;
while (0 != len--){
if (0 != (*dest = *src)){
++dest;
++src;
}
}
if (len > 0)
while (len--)
*dest++ = '\0';
return ret;
}
在安全性方面,显然strncpy要比strcpy安全得多,strcpy无法控制拷贝的长度,不小心就会出现dest的大小无法容纳src的情况,就会出现越界的问题,程序就会崩溃。而strncpy就控制了拷贝的字符数避免了这类问题,但是要注意的是dest依然要注意要有足够的空间存放src,而且src 和 dest 所指的内存区域不能重叠,
[cpp] view plain copy
<span style="font-family:Arial, Helvetica, sans-serif;background-color:rgb(255,255,255);">9.不创建临时变量,交换两个数</span>
:
[cpp] view plain copy
a = a + b;
b = a - b;
a = a - b;
//
a = a * b;
b = a / b;
a = a / b;
// 以上两种方法可能会溢出
a = a ^ b;
b = a ^ b;
a = a ^ b;
// 优化版本( 可读性低, 效率低 )
10.数组除一个数字出现一次外别的数字都出现了两次,求这个数字
:
[cpp] view plain copy
int FindOneTimeNumber( int* arr, size_t len ){
assert( NULL != arr );
int tmp = arr[0];
for (int i = 1; i < len; ++i)
tmp = tmp ^ arr[i];
return tmp;
}
11.数组中有两个数字只出现了一次,其余数字都出现了两次,找出这个数字
:
[cpp] view plain copy
void FindNumbers( int* arr, int len, int* num1, int* num2 ){
assert( NULL != arr && len > 0 );
int tmp = arr[0];
for (int i = 1; i < len; ++i)
tmp = tmp ^ arr[i];
unsigned int flag = 1;
while (1){
if (1 == ( tmp & flag ))
break;
flag <<= 1;
}
*num1 = 0;
*num2 = 0;
for (int i = 0; i < len; ++i){
if (1 == ( arr[i] & flag ))
*num1 = *num1 ^ arr[i];
else
*num2 = *num2 ^ arr[i];
}
}
12.每瓶汽水1元,两个空瓶换一瓶。现有20元,最多喝几瓶
:
[cpp] view plain copy
int DrinkNumbers( int money ){
assert( money >= 0 );
int sumDrinks = 0;
int emptyBottle = 0;
while (1){
if (0 == money && emptyBottle <= 1)
break;
sumDrinks += money;
if (0 == money % 2)
money /= 2;
else{
money /= 2;
++emptyBottle;
}
if (2 == emptyBottle){
++sumDrinks;
emptyBottle = 1;
}
}
return sumDrinks;
}
13.获取一个数二进制序列的奇数位和偶数位
:
[cpp] view plain copy
int a[32];
for (int i = 0; i < 32; ++i){
a[i] = data % 2;
data /= 2;
}
// 偶数
for (int i = 1; i <= 31; i += 2)
cout << arr[i];
// 奇数
for (int i = 0; i <= 20; i += 2)
cout << arr[i];
14.输入一个非负整数,返回组成它的数字之和
:
[cpp] view plain copy
int Sum( int num ){
assert( num >= 0 );
int sum = 0;
int m = 0;
while (0 != num){
m = num % 10;
num /= 10;
sum += m;
}
return sum;
}
15.
:
[cpp] view plain copy
// 1 - 1/2 + 1/3 - 1/4 + ... 1/100
double sum = 0.0;
int flag = 1;
for (int i = 1; i <= 100; ++i){
sum += ( 1 / (i * flag) );
flag *= -1;
}
// 求两个数的平均值,不用 (a+b) / 2
average = (a & b) + ((a ^ b) >> 1);
// 1-100中,9出现次数
int times = 0;
for (int i = 1; i <= 100; ++i){
if (9 == i % 10)
++times;
if (9 == i / 10)
++times;
}
// 将一个二进制数的奇偶位交换(中间的 或 换成 + 也可以)
#define CHANGE(num) ( (((num) & (0X55555555)) << 1) | ((num) & (0XAAAAAAAA) >> 1) )
// 判断一个字符串是否为另外一个字符串旋转之后的字符串
bool IsRotate( const char* str1, const char* str2 ){
assert( NULL != str1 && NULL != str2 );
if (strlen( str1 ) != strlen( str2 ))
return false;
int newLen = strlen( str1 ) * 2 + 1;
char* pNew = (char*)malloc(newLen);
if (NULL == pNew)
return false;
strcpy( pNew, str1 );
strcat( pNew, str1 );
if (NULL == strstr( pNew, str2 ))
return false;
return true;
}
// 实现一个函数,将一个数的二进制翻转并输出翻转后的值
unsigned int ReverseBit( unsigned int val ){
unsigned int a = 0;
unsigned int ret = 0;
for (int i = 0; i < 32; ++i){
a = val & 1;
val >>= 1;
// 注意顺序,先移后或
ret <<= 1;
ret |= a;
}
}
16.请写一个模板类,来求两个数中的最大值
注意:求两个"数"的最大值,若两个"数"不是内置类型,而是自定义类型比较大小。则1.看原自定义类型是否重载operator <( )运算符 2.仿函数
[cpp] view plain copy
template <typename T>
class MaxNumber{
public:
MaxNumber( )
: _a( a )
, _b( b )
{}
T Max( ){
return _a > _b ? _a : _b;
}
private:
T _a;
T _b;
};
int main( ){
int a;
int b;
cin >> a >> b;
MaxNumber<int> m( a, b );
m.Max( );
return 0;
}
17.写一个函数来判断一个字符串是否是对称的
[cpp] view plain copy
bool Fun( const char* str ){
bool ret = true;
if (NULL == str || ('\0' == *str))
return ret;
char* pBegin = str;
char* pEnd = str + strlen(str) - 1;
// 注意此处不能用 pBegin != pEnd 判断. 想想字符串为偶数个字符的情形(不包括'\0'),是否两个指针刚好错过了.
while (pBegin < pEnd){
if (*pBegin == *pEnd){
++pBegin;
--pEnd;
}
else{
ret = false;
break;
}
}
return ret;
}
18.二分查找的算法
[cpp] view plain copy
int BinarySearch( int* arr, int len, int key ){
assert( NULL != arr && len > 0 );
int left = 0;
int right = len - 1;
int mid = 0;
while (left<=right){
mid = left + ( (right-left)>>1 );// 不直接用 (left+right)/2 是 防止越界 和 提高效率
if (arr[mid] < key)
left = mid + 1;
else if (arr[mid] > key)
right = mid - 1;
else
return mid;
}
return -1;
}
right = n - 1 => while (left <= right) (利用只有一个元素时情景去想, 如果是 left(0) < rightr(1 - 1 == 0) 循环根本进不去。 所以是<=) => right = middle - 1(middle索引的元素已经被判断,又因为是 left <= right, 所以 right = mid - 1 如果是 right = mid, 这mid索引这个位置元素又会在下次循环被遍历一次);
right = n => while (left < right)(数组只有一个元素 0 < 1 --> 循环进去一次,<=循环进去两次) => right = middle(middle已经被遍历, 又因为left < right, 若right = middle - 1则middle - 1那个元素被略过, 而right = middle -> left < right --> middle不会被重复遍历,middle - 1的元素也不会被略过);
middle = ((left+right) >> 1); 这样的话 left 与 right 的值比较大的时候,其和可能溢出。 --> middle = left + ( (right - left) >> 1 )
19.快速排序
[cpp] view plain copy
template <typename T>
void QuickSort( vector<T>& a ){
QuickSort( a, 0, a.size( )-1 );
}
template <typename T>
const T& Median3( vector<T>&a, int left, int right ){
int center = left + ((right - left) >> 1);
if (a[center] < a[left])
swap( a[left], a[center] );
if (a[right] < a[left])
swap( a[left], a[right] );
if (a[right] < a[center])
swap( a[center], a[right] );
swap( a[center], a[right - 1] );
return a[right - 1];
}
template <typename T>
void QuickSort( vector<T>& a, int left, int right ){
if (left + 10 <= right){
T pivot = median3( a, left, right );
int i = left;
int j = right - 1;
for ( ; ; ){
while (a[++i] < pivot)
;
while (a[--j] > pivot)
;
if ( i < j )
swap( a[i], a[j] );
else
break;
}
swap( a[i], a[right-1] );
QuickSort( a, left, i - 1 );
QuickSort( a, i + 1, right );
}
else
InsertSort( a, left, right );
}
20.插入排序
插入排序由N-1趟排序组成。对于p=1到N-1趟,插入排序保证从位置0到位置p上的元素为已排序状态。事实:位置0到位置p-1上的元素是已经排过序的。
在第p趟,我们将位置p上的元素向左移动至它在前p+1个元素中的正确位置上。
[cpp] view plain copy
template <typename T>
void InsertSort( vector<T>& a ){
int j;
for (int p = 1; p < a.size( ); ++p){
T tmp = a[p];
for (j = p; j > 0 && tmp < a[j-1]; ++j)
a[j] = a[j-1];
a[j] = tmp;
}
}
21.冒泡、选择
[cpp] view plain copy
void BubbleSort( void ){
int i = 0;
int j = 0;
for (i = 0; i < n - 1; ++i){
for ( j = 0; j < n - i - 1; ++j ){
/* */
}
}
}
void SelectSort( void ){
int maxIndex = 0;
int i = 0;
int j = 0;
for (i = 0; i < n - 1; ++i) {
maxIndex = i;
for ( j = i; j < n - 1; ++j )// j 从 i 开始;下来依次为 j < n - 1; arr[j + 1];//j 从 i + 1 开始,则依次为 j < n; arr[j]
{
if ( arr[j + 1] > arr[maxIndex] )
maxIndex = j + 1;
}
swap ( arr[i], arr[maxIndex] );
}
}
22.堆排序
[cpp] view plain copy
// 根据数组构建大堆
void HeapAdjust( int* arr, int i, int len ){
int child;
int tmp;
for (; 2 * i + 1 < len; i = child){
// 子结点 = 2 * 父结点 + 1
child = 2 * i + 1;
// 得到子结点中数值较大的结点
if (child < len-1 && arr[child+1] > arr[child])
++child;
// 如果较大的子结点值大于父结点的值那么把较大的子结点往上移动,替换它的父结点
if (arr[i] < arr[child]){
tmp = arr[i];
arr[i] = arr[child];
arr[child] = tmp;
}
else
break;
}
}
//堆排序算法
void HeapSort( int* arr, int len ){
int i;
// length/2-1是最后一个非叶节点
// 构建大堆
for (i = len/2-1; i >= 0; --i)
HeapAdjust( arr, i, len );
// 从最后一个元素开始对序列进行调整,不断缩小调整范围直到第一个元素
for (i = len-1; i > 0; --i){
// 把第一个元素和当前的最后一个元素交换
// 保证当前最后一个位置的元素都是现在这个序列之中最大的元素
arr[i] = arr[0] ^ arr[i];
arr[0] = arr[0] ^ arr[i];
arr[i] = arr[0] ^ arr[i];
// 不断缩小调整范围,每一次调整完毕后保证第一个元素是当前序列的最大值
HeapAdjust( arr, 0, i );
}
}
23.希尔排序(插入排序的改进版本),希尔排序的步长现在还没有最好的选择,不过已知的最好步长序列是由Sedgewick提出的(1, 5, 19, 41, 109,...)
操作步骤:
初始时,有一个大小为 10 的无序序列。
(1)在第一趟排序中,我们不妨设 gap1 = N / 2 = 5,即相隔距离为 5 的元素组成一组,可以分为 5 组。
(2)接下来,按照直接插入排序的方法对每个组进行排序。
在第二趟排序中,我们把上次的 gap 缩小一半,即 gap2 = gap1 / 2 = 2 (取整数)。这样每相隔距离为 2 的元素组成一组,可以分为 2 组。
(3)按照直接插入排序的方法对每个组进行排序。
(4)在第三趟排序中,再次把 gap 缩小一半,即gap3 = gap2 / 2 = 1。 这样相隔距离为 1 的元素组成一组,即只有一组。
(5)按照直接插入排序的方法对每个组进行排序。此时,排序已经结束。
时间复杂度:
最好情况:由于希尔排序的好坏和步长gap的选择有很多关系,因此,目前还没有得出最好的步长如何选择(现在有些比较好的选择了,但不确定是否是最好的)。所以,不知道最好的情况下的算法时间复杂度。
最坏情况下:O(N*N)
空间复杂度:
由直接插入排序算法可知,我们在排序过程中,需要一个临时变量存储要插入的值,所以空间复杂度为O(1)。
算法稳定性
希尔排序中相等数据可能会交换位置,所以希尔排序是不稳定的算法。
在使用 增量k的一趟排序后,对于每一个i,我们有 a[i] <= a[i + k]。所有相隔k的元素都被排序。希尔排序一个重要性质:一个k排序的文件保持它的k排序性。如果情况不是这样,该排序就没有意义了,因为后面各趟排序会打乱前面各趟排序的成果。
k排序的一般做法:对于i,i+k,i+2k...中的每一个位置,都把其上的元素放到前面序列中的正确位置上。
一趟k排序作用是对k个独立的子数组进行一次插入排序。
[cpp] view plain copy
template <typename T>
void ShellSort( vector<T>& a ){
for (int gap = a.size( ) / 2; gap > 0; gap /= 2)
for (int i = gap; i < a.size( ); ++i){
T tmp = a[i];
int j = i;
for (; j >= gap && tmp < a[j - gap]; j-= gap)
a[j] = a[j - gap];
a[j] = tmp;
}
}
24.归并排序
合并两个已排序的表时间是线性的,最多进行N-1次比较,N是两个表元素的总数,因为每次比较都把一个元素添加到临时数组中,但最后一次比较除外,它至少添加两个元素。
如果对Merge的每个递归调用均局部声明一个临时数组,那么在任一时刻就可能有 logN个临时数组处在活动期。 由于Merge是MergeSort的最后一行,因此在任一时刻只需要一个临时数组活动,而且这个临时数组可以在MergeSort驱动程序中建立。还可以使用临时数组的任意部分。我们将使用与输入数组a相同的部分。
[cpp] view plain copy
template <typename T>
void MergeSort( vector<T>& a ){
vector<T> tmpArr( a.size( ) );
MergeSort( a, tmpArr, 0, a.size( ) - 1 );
}
template <typename T>
void MergeSort( vector<T>& a, vector<T>& tmpArr, int left, int right ){
if (left < right){
int center = ( left + right ) / 2;
MergeSort( a, tmpArr, left, center );
MergeSort( a, tmpArr, left+1, right );
Merge( a, tmpArr, left, center+1, right );
}
}
template <typename T>
void Merge( vector<T>& a, vector<T>& tmpArr, int leftPos, int rightPos, int rightEnd ){
int leftEnd = rightPos - 1;
int tmpPos = leftPos;
int num = rightPos - leftPos + 1;
while (leftPos <= leftEnd && rightPos <= rightEnd)
if (a[leftPos] <= a[rightPos])
tmpArr[tmpPos++] = a[leftPos++];
else
tmpArr[tmpPos++] = a[rightPos++];
while (leftPos <= leftEnd)
tmpArr[tmpPos++] = a[leftPos++];
while (rightPos <= rightEnd)
tmpArr[tmpPos++] = a[rightEnd++];
for (int i = 0; i < num; i++, rightEnd--)
a[rightEnd] = tmpArr[rightEnd];
}
25. 实现时间复杂度为O(nlogn)的链表排序算法
http://blog.csdn.net/mbuger/article/details/70259940
26.模拟实现strlen
[cpp] view plain copy
int MyStrlen( const char* str ){
assert( NULL != str );
int len = 0;
while ('\0' != *str++)
len++;
return len;
}
不用变量版本:
[cpp] view plain copy
int MyStrlen( const char* src ){
assert( NULL != src );
if ('\0' == *src)
return 0;
else
return ( 1 + MyStrlen( ++str ) );
}
27.模拟实现memcpy、memmove和memset
strcpy和memcpy主要有以下3方面的区别。
1、复制的内容不同。strcpy只能复制字符串,而memcpy可以复制任意内容(内存中的内容),例如字符数组、整型、结构体、类等。
2、复制的方法不同。strcpy不需要指定长度,它遇到被复制字符的串结束符"\0"才结束,所以容易溢出。memcpy则是根据其第3个参数决定复制的长度。
3、用途不同。通常在复制字符串时用strcpy,而需要复制其他类型数据时则一般用memcpy
[cpp] view plain copy
void* MyMemcpy( void* dest, const void* src, size_t n ){
assert( NULL != dest );
assert( NULL != src );
char* pDest = (char*)dest;
const char* pSrc = (const char*)src;
while (n--)
*pDest++ = *pSrc++;
return dest;
}
[cpp] view plain copy
void* MyMemmove( void* dest, const void* src, size_t count ){
assert( NULL != dest );
assert( NULL != src );
char* pDest = (char*)dest;
const char* pSrc = (const char*)src;
if (pDest <= pSrc || pSrc + count <= pDest)// 正常情况下,从前往后拷贝
while (count--)
*pDest++ = *pSrc++;
else
while (count--)
*(pDest + count - 1) = *(pSrc + count - 1);
return dest;
}
[cpp] view plain copy
void* MyMemset( void* dest, int c, size_t count ) {
assert( NULL != dest );
char* tmp = (char*)dest;
while (count--){
*tmp = (char)c;
tmp++;
}
return dest;
}
memcpy
内存拷贝函数
source和destin所指的内存区域可能重叠,但是如果source和destin所指的内存区域重叠,那么这个函数并不能够确保source所在重叠区域在拷贝之前不被覆盖。而使用memmove可以用来处理重叠区域。函数返回指向destin的指针。
memmove
内存移动函数
如果目标区域和源区域有重叠的话,memmove能够保证源串在被覆盖之前将重叠区域的字节拷贝到目标区域中。但复制后src内容会被更改。但是当目标区域与源区域没有重叠则和memcpy函数功能相同。memmove函数能够反向拷贝,不用在意memcpy函数会遇到的问题。
memset
将dest中从当前位置开始的count个字节,用 c 替换并返回 dest 。
在strcpy,strncpy,memcpy和memmove这四个库函数中,安全性是递增的,前三个函数均没有考虑到内存重叠的问题,所以相对来说memmove函数的安全性最高。
28.从键盘输入一个十进制正整数,将其转换成八进制后输出
[cpp] view plain copy
#include <stdlib.h>
#include <stdio.h>
int main( void ){
int number = 123456;
char string[25];
itoa( number, string, 8 );
printf( "string = %s\n", string );
return 0;
}
#include <stdio.h>
#include <math.h>
int dtoe( int a ){
if (a < 8)
return a;
else
return ( dtoe(a / 8) * 10 + a % 8 );
return 0;
}
int main( void ){
int tmp;
scanf( "%d", &tmp );
tmp = dtoe( tmp );
printf( "%d\n", tmp );
return 0;
}
上期回顾:
点击图片get往期内容