面试-;代码
Posted ssopp24
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了面试-;代码相关的知识,希望对你有一定的参考价值。
一.剑指offer
代码鲁棒性(输入参数检查等)贯穿所有代码当中,不会特别强调.
1.赋值运算符函数
注意:
1.返回值类型为该类型引用(链式赋值),返回*this
2.传入参数常量引用
3.释放自身已有空间
4.判断是不是自身赋值
例:
MyString& MyString::operator=( const MyString& str )
if ( this == &str )
return *this;
delete[] m_pData;
m_pData = NULL:
m_pData = new char[strlen(str.m_pData) + 1];
strcpy( m_pData, str.m_pData );
return *this;
不足:若内存不足,new申请内存时抛出异常,则m_pData已经被delete,此时其为一个空指针,非常容易导致程序奔溃。 在赋值运算符函数内部抛异常,CMyString实例不再保持有效的状态,违背异常安全性原则.
改进:1.先new再delete 2.创建临时实例,交换临时实例和原来的实例
:
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;
3.二维数组中的查找:二维数组,每一行,从左到右递增。每一列,从上到下递增。 输入一个二维数组和一个整数,判断数组中是否含有该整数
思路:选取数组右上角数字,等于->结束。 大于->剔除这一列。 小于->剔除这一行。 这样每比较一次剔除一列或一行,缩小范围,直到找到要查找的数字or查找范围为空
例:
bool Find( int* matrix, int rows, int cols, int number )
bool found = false;
if ( NULL != matrix && rows > 0 && cols > 0 )
int row = 0;
int col = cols - 1;
while ( row < rows && col >= 0 )
if ( number == matrix[row * cols + col] )
found = true;
return found;
else if ( matrix[row * cols + col] > number )
--col;
else
++row;
return found;
二维数组在内存中占据连续的空间。在内存中从上到下存储各行元素,每行从左到右顺序存储
4.替换空格
1.在原串上操作 --> 保证原串后面有足够的空间 --> 从后往前替换
2.开辟新空间 --> 直接替换
两种思路都需遍历原串一次获取空格个数 --> O(N)
相关题目:两个排序数组A1,A2。A1后有足够空间容纳A2,将A2中数字插入A1中使所有数字依旧有序
思路:遍历两个数组各一次得到两数组长度,三个指针,一个指针指向lengthA1+lengthA2-1,其余两个分别指向A1与A2末尾。 依次比较,将合适数字放到第一个指针所指位置
合并两个数组(字符串)时,如果从前往后赋值每个数字or字符需要重复移动数字or字符多次 --> 从后往前复制,减少移动次数,提高效率
5.从尾到头打印链表
1.栈
2.递归
7.用两个栈实现队列
思路:栈S1和S2,入“队列”入栈S1。出“队列”,若S2有元素S2.pop( ), 否则,将S1中元素压入栈S2
相关题目:两个队列实现栈
8.旋转数组中最小的数字 --> 把数组最开始的若干个元素搬到数组的末尾(旋转数组),输入一个递增数组的旋转,输出最小数字
思路:数组旋转后可将其视为两个排序的子数组,并且前面子数组元素都大于等于后面子数组元素,最小的元素是第二个子数组的第一个元素(我们要找最小的元素) --> 二分(O(logn))(每移动一次指针,查找范围缩小为原来的一半) --> p1指向数组第一个元素,p2指向数组最后一个元素。 如果中间元素大于p1指向数据,即中间元素在第一个子数组中,将p1指向这。 否则如果 中间元素小于p2指向元素,将p2指向中间元素。 两个指针慢慢靠近 直到 第一个指针指向第一个子数组最后一个元素,第二个指针指向第二个子数组第一个元素, 两指针相邻,此时第二个指针指向数组中最小的元素。 循环结束
特殊情况:
1.如果移动元素个数为0,此时第一个元素就是最小的元素,代码中我们将 indexMid初始化为index1,如果第一个数字小于最后一个数字,直接返回。
2. 1, 0, 1, 1, 1 与 1, 1, 1, 0, 1 这种 两个指针指向的数字及它们中间数字三者相同的时候,无法判断中间数字位于哪个子数组中,也就无法移动指针来缩小查找的范围,此时 --> 顺序查找
:
int Min( int* arr, int len )
if (NULL == arr || len <= 0)
throw new std::exception( "Invalid parameters" );
int index1 = 0;
int index2 = len - 1;
int indexMid = index1;
while (arr[index1] >= arr[index2])
if (1 == index2 - index1)
return arr[index2];
indexMid = (index1 + index2) / 2;
// 顺序查找
if (arr[index1] == arr[index2] && arr[index1] == arr[indexMid])
return MinInOrder( arr, index1, index2 );
if (arr[indexMid] >= arr[index1])
index1 = indexMid;
else if (arr[indexMid] <= arr[index2])
index2 = indexMid;
int MinInOrder( int* arr, int index1, int index2 )
int result = arr[index1];
for (int i = index1 + 1; i <= index2; ++i)
if (arr[i] < result)
result = arr[i];
return result;
9.斐波那契数列:
n f(n)
0 0
1 1
>1 f(n-1)+fr(n-2)
1.递归
2.循环:
long long Fibonacci( unsigned n )
int result[2] = 0, 1;
if (n < 2)
return result[n];
long long fibOne = 0;
long long fibTwo = 1;
long long fibN = 0;
// 注意这里是 i<=n, 而不是i<n
for (unsigned int i = 2; i <= n; ++i)
fibN = fibOne + fibTwo;
fibOne = fibTwo;
fibTwo = fibN;
return fibN;
相关题目:
1.一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法?
n = 1, 1种方法(一次1级台阶)
n = 2, 2种方法( 两次1级, 或者一次2级 )
前两种为基准情况
n = 3, 3( 三次1级, 或一次1级一次2级(顺序不同,两个方法) )
我们通过 台阶数为3来分析。 青蛙一次只能跳一级或者两级
也就是说: 青蛙跳上三级台阶最后一跳只有两种情况,跳一级或者跳两级,所以青蛙跳三级台阶总的方法数位: 青蛙跳至只剩一级台阶和只剩两级台阶的方法数之和
即 F(n) = F(n-1) + F(n-2)
或者,我们多写几种情况,也可以发现规律,方法数为 前一次方法数 + 后一次方法数
:
int jumpFloor( int number )
if (number <= 0)
return 0;
else if (1 == number)
return 1;
else if (2 == number)
return 2;
return jumpFloor( number-2 ) + jumpFloor( number-1 );
2.青蛙一次可以跳上1级台阶,也可以跳上2级……跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
思路: 假设台阶为n级,则青蛙可以 跳一次或者多次。 一次: 直接跳n级,这是一种方法。 多次: 青蛙跳到 1到n-1级 任一级(不管怎样跳,跳几次)后再跳一次到n级。
或者这样分析,青蛙最后一跳,有可能是从起点直接跳到终点,或者从起点跳了若干步后(到达 1到n-1级中间任一级 )再跳到n级。
所以总的方法数为:青蛙 跳到 1级到n-1级 每级可能的方法数(再跳到n级) + 1(直接跳到n级)
F(n) = F(n-1) + F(n-2) + ... + F(1) + 1;
可得: F(n-1) = F(n-2) + F(n-3) +..._F(1) + 1;
则F(n) = F(n-1) + F(n-1)
即F(n) = 2F(n-1)
:
int jumpFloorII( int number )
if (number <= 0)
return 0;
if (1 == number)
return 1;
return 2 * jumpFloorII( number-1 );
3.我们可以用2*1的小矩形横着或者竖着去覆盖更大的矩形。请问用n个2*1的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?
思路: 对于n>=3的情况, 不管前面矩形是怎样覆盖的。我们只考虑最后一次怎么覆盖。
最后一次只有两种覆盖方式:1.用一个小矩形竖着覆盖。 2.用两个小矩形横着覆盖。
所以总的方法数无外乎 --> 你用各种方法覆盖到只剩1个再竖着覆盖或者你用各种方法覆盖到只剩两个再横着覆盖
即:总的方法数F(n) = n-1次的方法数F(n-1)(接着用一个小矩形竖着覆盖) + n-2次的方法数F(n-2)(接着用两个小矩形横着覆盖)
:
int rectCover( int number )
if ( number <= 0 )
return 0;
if ( 1 == number )
return 1;
if ( 2 == number )
return 2;
return rectCover( number-1 ) + rectCover( number-2 );
10.二进制中1的个数 输入一个整数,输出该整数二进制表示中1的个数
思路:一个整数减去1再和原整数做与行算,会把该整数二进制形式最右边一个1变为0. 那么二进制表示有多少个1就可以进行多少次这样的操作。
:
int NumberOf1( int n )
int count = 0;
while (0 != n)
++count;
n = (n - 1) & n;
return count;
常规思路:将1依次左移判断数字每一位是否是1(注意不能将数字右移 与 1进行&运算,因为负数右移补符号位,数字最终变为0XFFFFFFFF,死循环)
:
int NumberOf1( int n )
int count = 0;
unsigned int flag = 1;
while (0 != flag)
if (0 != (flag & n))
++count;
flag = flag << 1;
return count;
相关题目:
1.用一条语句判断一个整数是不是2的整数次方
思路:如果一个整数是2的整数次方,则它二进制表示中只有一位为1, 把这个整数减去1和它自己做与运算,这个整数中唯一的1就会变为0
2。输入两个整数m,n。 计算需要改变m的二进制表示中多少位才能得到n
思路:第一步 将m与n异或, 第二部求异或结果中1的位数
13.在O(1)时间删除链表结点
思路:1.如果链表只有一个结点(头节点),直接删除 2.如果要删除结点为尾结点,则用常规方法(O(N)) 3.删除多个节点的非尾结点(将下一个节点 value值赋给被删结点, 删除下个节点)
14.调整数组顺序使奇数位于偶数前面
两个指针p1,p2, 一个从前往后遍历,一个从后往前遍历
:
void Reorder( int* arr, unsigned int len, bool (*func)(int) )
if (NULL == arr || len == 0)
return;
int* pBegin = arr;
int* pEnd = arr + len - 1;
while (pBegin < pEnd)
while (pBegin < pEnd && !func(*pBegin))
++pBegin;
while (pBegin < pEnd && func(*pEnd))
--pEnd;
if (pBegin < pEnd)
int temp = *pBegin;
*pBegin = *pEnd;
*pEnd = temp;
bool IsEven( int n )
return 0 == ( n & 1 );
void ReorderOddEven( int* arr, unsigned int len )
Reorder( arr, len, IsEven );
15.链表倒数第k个结点
想象有一把长度为k的尺子放在链表上,两个指针p1(尺子头),p2(尺子尾)。 当p2到达链表尾部时p1正好指向倒数第k个结点
注意这个题重点考虑鲁棒性
相关题目:
1.求链表中间结点
思路:两个指针,p1,p2。 p1一次走一步,p2一次走两步。 当p2指向链表最后一个结点时, p1正好指向链表中间节点
2.判断链表是否带环
思路:快慢指针,相遇->带环。 快指针到链表尾->不带环
当我们用一个指针遍历链表不能解决问题时,可以尝试两个指针来遍历链表。 可以让其中一个指针走的快些(如一次走两步),或者让它先在链表上走若干步
16.反转链表
:
ListNode* ReverseList( ListNode* pHead )
// 链表为空和链表只有一个结点在这个语句进行处理
if (NULL == pHead || NULL == pHead->next)
return pHead;
ListNode* pPrev = NULL;
ListNode* pCur = pHead;
ListNode* pReverseHead = NULL;
while (NULL != pCur)
ListNode* pNext = pCur->next;
if (NULL == pNext)
pReverseHead = pCur;
pCur->next = pPrev;
pPrev = pCur;
pCur = pNext;
return pReverseHead;
17.合并两个排序的链表
:
ListNode* Merge( ListNode* pHead1, ListNoe* pHead2 )
if (NULL == pHead1)
return pHead2;
else if (NULL == pHead2)
return pHead1;
ListNode* pMergedHead = NULL;
if (pHead1->val < pHead2->val)
pMergeHead = pHead1;
pMergeHead->next = Merge( pHead1->next, pHead2 );
else
pMergeHead = pHead2;
pMergeHead->next = Merge( pHead1, pHead2->next );
return pMergedHead;
21.包含min函数的栈 定义一个可以使min, push, pop都为O(1)的栈
思路:借用辅助栈,每次数据栈元素入栈时,将所有入栈元素中最小值入辅助栈(保证辅助栈和数据栈->同入同出)
:
void Push( const int& data )
dataS.push( data );
if (0 == minS.size( ) || data < minS.top( ))
minS.push( data );
else
minS.push( minS.top( ) );
void Pop( )
assert( dataS.size( ) > 0 && minS.size( ) > 0 );
dataS.pop( );
minS.pop( );
const T& Min( ) const
assert( dataS.size( ) > 0 && minS.size( ) > 0 );
return minS.top( );
23.从上往下打印二叉树(层序遍历二叉树)
借助队列
:
void LevelPrint( Node* pRoot )
if (NULL == pRtoot)
return;
queue<Node*> queueNode;
queueNode.push( pRoot );
while (0 != queueNode.size( ))
Node* pNode = queueNode.front( );
queueNode.pop( );
Visit( pNode );
if (NULL != pNode->pLeft)
queueNode.push(pNode->pLeft);
if (NULL != pNode->pRight)
queueNode.push(pNode->pRight);
29.数组中出现次数超过一半的数字
思路:如果有一个数字出现次数超过数组元素总个数的一半,则它出现次数比其他数字出现的次数总和还要多。
:
bool g_inputInvalid = false;
bool CheckInvalidArr( int* arr, int len )
g_inputInvalid = false;
if (NULL == arr || len <= 0)
g_inputInvalid = true;
return g_inputInvalid;
bool CheckMoreThanHalf( int* arr, int len, int number )
int times = 0;
for (int i = 0; i < len; ++i)
if (number == arr[i])
++times;
bool isMoreThanHalf = true;
if (times * 2 <= len)
g_inputInvalid = true;
isMoreThanHalf = false;
return isMoreThanHalf;
int MoreThanHalfNum( int* arr, int len )
// 如果只返回0存在二义性,是无效参数返回0还是0出现次数超过一半呢?
// 定义一个函数 和 一个全局变量,即可区分这两种情况
if (CheckInvalidArray( arr, len ))
return 0;
int result = arr[0];
int times = 1;
for (int i = 1; i < len; ++i)
if (0 == times)
result = arr[i];
times = 1;
else if (result == arr[i])
++times;
else
--times;
// 此时result中保存的数字并不一定是数组中出现次数超过数组长度一半的数字,需要验证
// 11234
if (!CheckMoreThanHalf( arr, len, result ))
result = 0;
return result;
37.两个链表的第一个公共结点
思路:
1.借助栈,两个链表都从后往前遍历,直到找到最后一个相同结点
2.遍历两个链表一次,得到链表长度,len1,len2。假设len1 为5, len2 为4。 则在链表1上先走(len1 - len2)步,接着同时开始走,第一个相同节点
38.数字在排序数组中出现的次数
思路:
因为是排序数组,找到第一个k和最后一个k即可知道k出现的次数 O(log n)二分
:
int GetFirstK( int* arr, int len, int k, int start, int end )
// 这段区间内没找到k, 返回-1
if (start > end)
return -1;
int midIndex = (start + end) / 2;
int midData = arr[midIndex];
if (k == midData)
if ((midIndex > 0 && k != arr[midIndex - 1])
|| 0 == midIndex)
return midIndex;
else
end = midIndex - 1;
else if (midData > k)
end = midIndex - 1;
else
start = midIndex + 1;
return GetFirstK( arr, len, k, start, end );
int GetLastK( int* arr, int len, int k, int start, int end )
if (start > end)
return -1;
int midIndex = (start + end) / 2;
int midData = arr[midIndex];
if (k == midData)
if ((midIndex < len - 1 && k != arr[midIndex + 1])
|| len - 1 == midIndex)
return midIndex;
else
start = midIndex + 1;
else if (midData > k)
end = midIndex - 1;
else
start = midIndex + 1;
return GetLastK( arr, len, k, start, end );
int GetTimesOfK( int* arr, int len, int k )
int times = 0;
if (NULL != arr && len > 0 )
int first = GetFirstK( arr, len, k, 0, len - 1 );
int last = GetLastK( arr, len, k, 0, len - 1 );
if (first > -1 && last > -1)
times = last - first + 1;
return times;
39.二叉树的深度
:
int TreeDepth( BinaryTreeNode* pRoot )
if (NULL == pRoot)
return 0;
int depthLeft = TreeDepth( pRoot->left );
int depthRight = TreeDepth( pRoot->right );
return (depthleft > depthRight) ? (depthLeft + 1) : (depthRight + 1);
相关题目:判断一棵树是不是平衡二叉树。 左右子树的深度相差不超过1.
1.需要重复遍历多次的解法
:
bool IsBalanced( BinaryTreeNode* pRoot )
if (NULL == pRoot)
return true;
int depthLeft = TreeDepth( pRoot->left );
int depthRight = TreeDepth( pRoot->right );
int diff = left - right;
if (diff > 1 || diff < -1)
return false;
return IsBalanced( pRoot->left ) && IsBalanced( pRoot->right );
后序遍历,每个节点只遍历一次
:
bool IsBalanced( BinaryTreeNode* pRoot, int* pDepth )
if (NULL == pRoot)
*pDepth = 0;
return true;
int left;
int right;
if (IsBalanced( pRoot->left, &left ) && IsBalanced( pRoot->tight, &right ))
int diff = left - right;
if (diff >= -1 && diff <= 1)
*pDepth = left > right ? (left + 1) : (right + 1);
return true;
return false;
bool IsBlanced( BinaryTreeNode* pRoot )
int depth = 0;
return IsBlanced( pRoot, &depth );
42.翻转单词顺序 vs 左旋转字符串
输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。 标点符号和普通字母一样处理。 --> "I am a student." 变为 "student. a am I"
思路:
1.翻转句子中所有的字符。此时不但翻转了句子中单词的顺序,连单词内的字符的顺序也翻转了
2.翻转每个单词中字符的顺序
:
void Reverse( char* pBegin, char* pEnd )
if (NULL == pBegin || NULL == pEnd)
return;
while (pBegin < pEnd)
char temp = *pBegin;
*pBegin = *pEnd;
*pEnd = temp;
++pBegin;
--pEnd;
char* ReverseSentence( char* pData )
if (NULL == pData)
return NULL;
char* pBegin = pData;
char* pEnd = pData;
while ('\\0' != *pEnd)
++pEnd;
--pEnd;
// 翻转整个句子
Reverse( pBegin, pEnd );
// 翻转句子中每个单词
pBegin = pEnd = pData;
while ('\\0' != *pBegin)
if (' ' == *pBegin)
++pBegin;
++pEnd;
else if (' ' == *pEnd || '\\0' == *pEnd)
Reverse( pBegin, --pEnd );
pBegin = ++pEnd;
else
++pEnd;
return pData;
相关题目:字符串左旋/右旋是把字符串前面的若干个字符转移到字符串的尾部。 “abcdefg” 和 数字2 --> "cdefgab"
思路:将要左旋的字符串和剩余字符串分别翻转,再翻转整个字符串
:
char* LeftRotateString( char* pStr, int n )
if (NULL != pStr)
int nLength = strlen( pStr );
if (nLength > 0 && n > 0 && n < nLength)
char* pFirstStart = pStr;
char* pFirstEnd = pStr + n - 1;
char* pSecondStart = pStr + n;
char* pSecondEnd = pStr + nLength - 1;
Reverse( pFirstStart, pFirstEnd );
Reverse( pSecondStart, pSecondEnd );
Reverse( pFirstStart, pSecondEnd );
50.树中两个结点的最低公共祖先
1.二叉搜索树
:
BSTNode* GetLastCommonNode( BSTNode* pRoot, BSTNode* pNode1, BSTNode* pNode2 )
if (NULL == pRoot)
return NULL;
else if (NULL == pNode1)
return pNode2;
else if (NULL == pNode2)
return pNode1;
else if (pNode1 == pNode2)
return pNode1;
int val = pRoot->val;
int val1 = pNode1->val;
int val2 = pNode2->val;
if (val1 < val && val2 < val)
// 注意,递归时,有返回值,调用函数一定要加 return。 无返回值则可以只递归调用,做你想做的事情, 不用加return
return GetLastCommonNode( pRoot->left, pNode1, pNode2 );
else if (val1 > val && val2 > val)
return GetLastCommonNode( pRoot->right, pNode1, pNode2 );
else
if (val1 == val)
return pNode1;
else if (val2 == va1)
return pNode2;
else
return pRoot;
2.普通树,含指向父节点指针
:
BTreeNode* FindFirstCommonNode( TreeNode* pNode1, TreeNode* pNode2 )
if (NULL == pNode1)
return pNode2;
else if (NULL == pNode2)
return pNode1;
stack<TreeNode*> s1;
stack<TreeNode*> s2;
// 注意:入栈第一个结点分别是这两个结点,而不是它们的父结点
while (NULL != pNode1)
s1.push( pNode1 );
pNode1 = pNode1->parent;
while (NULL != pNode2)
s2.push( pNode2 );
pNode2 = pNode2->parent;
TreeNode* firstCommonNode = NULL;
// 如果两个结点没在同一颗二叉树中, 返回NULL
if (s1.top( ) != s2.top( ))
return NULL;
while (0 != s1.size( ) && 0 != s2.size( ))
if (s1.top( ) == s2.top( ))
firstCommonNode = s1.top( );
s1.pop( );
s2.pop( );
else
break;
return firstCommonNode;
56.链表中环的入口结点
思路:定义指针P1,P2。 P1在链表上走环大小步后P2开始以相同速度遍历链表,两个指针相遇点即为链表中环的入口结点
:
// 首先判断单链表是否带环,若带环(此时快慢指针已经相遇),从此刻起计步,直到下次两个指针再相遇,快指针步数减去慢指针步数,即为环的长度
ListNode* EntryNodeOfLoop( ListNode* pHead )
if (NULL == pHead)
return NULL;
// 定义快慢指针判断是否有环
ListNode* pFast = pHead;
ListNode* pSlow = pHead;
// 如果有环,通过这两个变量求得环长度
int fastLength = 0;
int slowLength = 0;
int loopLength = 0;
// 求出环长度后通过这两个结点求出环入口节点
ListNode* p1 = pHead;
ListNode* p2 = pHead;
// 设置个死循环,如果链表不带环,我们在循环里直接退出即可
while ( 1 )
// 在两个指针走之前必须判断pFast和pFast的next是否为空,两个条件都必须判断而且判断顺序不能出错
// 因为快指针走得快,我们只需要判断快指针而不需要判断慢指针。如果为空,即链表不带环,返回NULL
// 检测是否带环同时也避免指针越界访问
if (NULL == pFast || NULL == pFast->next)
return NULL;
pFast = pFast->next->next;
pSlow = pSlow->next;
// 此时我们再判断两个指针是否相遇,不能在指针还没走的时候判断,因为最开始,两个指针就是相遇的
if (pFast == pSlow)
break;
// 程序运行到此处,两个指针已经相遇,此时我们求环的长度
while ( 1 )
pFast = pFast->next->next;
fastLength += 2;
pSlow = pSlow->next;
++slowLength;
if (pFast == pSlow)
break;
loopLength = fastLength - slowLength;
// 环长度已知,此时我们来找环的入口结点
while ( 1 )
// p1先走环的长度步 loopLength
for ( int i = 0; i < loopLength; ++i )
p1 = p1->next;
// 因为这个带环链表入口结点有可能就是第一个结点,所以如果此时p1走了环的长度步后回到了原点(即回到环的入口处(链表的第一个结点))与p2相遇了,则直接返回p1。即为环入口结点
if ( p1 == p2 )
return p1;
//否则p1 p2以相同速度开始走
while ( 1 )
p1 = p1->next;
p2 = p2->next;
//同样的,先走后判断
//此时即找到了环的入口结点
if ( p1 == p2 )
return p1;
二.非剑指offer
1.模拟实现 strstr
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
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);
:
#define MIN(A,B) ((A) <= (B) ? (A) : (B))
注意:
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.为什么标准头文件都有类似以下的结构?
#ifndef __INCvxWorksh
#define __INCvxWorksh
#ifdef __cplusplus
extern "C"
#endif
/*...*/
#ifdef __cplusplus
#endif
#endif /* __INCvxWorksh */
头文件中的编译宏
#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的原型为:
class String
public:
String( const char* str = NULL ); // 普通构造函数
String(const String &other); // 拷贝构造函数
~ String(void); // 析构函数
String & operator =(const String &other); // 赋值函数
private:
char *m_data; // 用于保存字符串
;
// 普通构造函数
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
记忆方法: 地址的增长顺序与值的增长顺序相同
:
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的值(假定结果不会超过长整型变量的范围)
:
long Sum( long n )
return ( (long)1 + n ) * n / 2;
8.宏函数 --> 交换两个数
:
#define SWAP( a, b ) \\
do \\
(a) = ((a)^(b)); \\
(b) = ((a)^(b)); \\
(a) = ((a)^(b)); \\
while (0)
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
:
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 所指的内存区域不能重叠,
9.不创建临时变量,交换两个数
:
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.数组除一个数字出现一次外别的数字都出现了两次,求这个数字
:
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.数组中有两个数字只出现了一次,其余数字都出现了两次,找出这个数字
:
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元,最多喝几瓶
:
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.获取一个数二进制序列的奇数位和偶数位
:
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.输入一个非负整数,返回组成它的数字之和
:
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.
:
// 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.仿函数
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. 写一个函数来判断一个字符串是否是对称的
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.二分查找的算法
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.快速排序
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 );
http://blog.sina.com.cn/s/blog_4dff8712010136jh.html
20.插入排序
插入排序由N-1趟排序组成。对于p=1到N-1趟,插入排序保证从位置0到位置p上的元素为已排序状态。事实:位置0到位置p-1上的元素是已经排过序的。
在第p趟,我们将位置p上的元素向左移动至它在前p+1个元素中的正确位置上。
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.冒泡、选择
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.堆排序
// 根据数组构建大堆
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个独立的子数组进行一次插入排序。
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相同的部分。
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
int MyStrlen( const char* str )
assert( NULL != str );
int len = 0;
while ('\\0' != *str++)
len++;
return len;
不用变量版本:
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
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;
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;
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.从键盘输入一个十进制正整数,将其转换成八进制后输出
#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( "以上是关于面试-;代码的主要内容,如果未能解决你的问题,请参考以下文章