C语言为何如此长寿并实用?C++为什么有那么多精彩?指针可以说是C/C++中的灵魂所在,虽然早期中pascal也有指针,但是和C/C++比起来不是一个级别的.今天为大家深入浅出的解析一下指针的有关笔试,面试题.所有题目来源网络,分析也不是我写的...
0.预备知识,最基础的指针
其实最基础的指针也就应该如下面代码:
int a;
int* p=&a;
也就是说,声明了一个int变量a,然后声明一个int 的指针,*p指向a的地址,&也就是取地址符号,而*是指针中取内容的符号,仅仅在声明的时候标记这个变量是指针.可能有点绕口,但是看代码来的容易的多...
1.与const在一起的时候
常常声明的时候会让人一头雾水,比如下面的声明,均是声明一个char* p的指针:
char * const p; // 指针不可改,也就说指针只能指向一个地址,不能更改为其他地址
char const * p; // 所指内容不可改,也就是说*p是常量字符串
char const * const p; // 内容和指针都不能改
const char * const p; // 同上...内容和指针不能改
额...别晕,别晕....其实方法很简单...你别真死记硬背...其实可以以*为分界符,在*左边有const就说明内容不能改,在*右边就说明指针不能改,而左边的char和const顺序是不要紧的...呵呵...你也可以理解成const是修饰后面的,正常顺序应该这样:const char * const p; 是不是看起来简单了?
2.忽悠人的陷阱,str[]和*str的区别
先告诉你哦,下面的题目可是陷阱啊....说说程序结果...
char str1[] = “abc”;
char str2[] = “abc”;
const char str3[] = “abc”;
const char str4[] = “abc”;
const char *str5 = “abc”;
const char *str6 = “abc”;
char *str7 = “abc”;
char *str8 = “abc”;
cout << ( str1 == str2 ) << endl;
cout << ( str3 == str4 ) << endl;
cout << ( str5 == str6 ) << endl;
cout << ( str7 == str8 ) << endl;
怎么样?都输出true?那显然你中标了...而且cout输出bool值的时候,就算全是真也应该都输出1啊...4个1?那也不对...答案是0011,不信你试试...为什么呢?
其实都说了这题是个大陷阱,因为这题根本不是比较字符串内容!而是比较字符串的地址.哦...恍然大悟...那为什么前两个是假呢?因为这可是说是一个深拷贝/浅拷贝的问题.当字符串是数组形式声明并初始化,编译器认为是新数组,分配新空间,但不是深拷贝,因为根本就不算拷贝.而如果是相同的字符串,用指针声明,那就是比较如果有一样的字符串,就直接把新指针指过去,这是正宗的浅拷贝.哇哈...你就中计了...
3.str[]用sizeof判断会出错么?
应该说我们常常用指针有很多时候是解决字符串的问题,一般我们用strlen,这当然没有问题,但是要你编一个呢?看看下面这个MyStrlen有问题么?
int MyStrlen(char str[])
{
return (int)(sizeof(str)-1);
}
呵呵...咱们上当过一次..这个当然也是不对的...不错...这个函数是错的...为什么呢?
首先,可以告诉你,无论何时,返回的总是3...额...是不是很奇怪,为什么不是数组长度呢?str不是char数组指针么?不错...确实是数组的指针,但是,当用函数传递的数组指针的时候就自动退化为指针了,而指针的长度是4,你减去1了自然就是3了.但是如果按照下面代码就可以得到正常的值.
char str[]="hello world";
int len=sizeof(str)-1; //记得减1哦,最后有'/0'结尾
cout<<len;
这样输出的是正常值,也就是你所希望的11;
4.注意数组指针和指针
继续上面的话题,刚刚提到了数组指针和指针,现在看看下面这端程序代码:
int a[5]={1,2,3,4,5};
int *ptr=(int *)(&a+1);
cout<<*(a+1)<<*(ptr-1);
呵呵...BaihowFF总是给陷阱..肯定不是想当然的说就是21...确实...答案是25...额...奇怪吧..为什么呢?
首先,a是一个数组,所以编译器解释&a就是a的全部长度,就是说(&a+1)也就是说移动了一个数组,指向了并不存在的a[5],所以ptr-1才会指向a数组的最后一个元素a[4],而a+1和a[1]是一样的...所以答案是25,如果你去掉了(&a+1)的括号,那么答案就是想当然的21了...呵呵...很微妙吧....
5.注意指针要分配给足够的空间
新手在刚刚接触指针的时候经常会忘记给指针分配空间,直接用肯定是有问题的,那么下面的程序呢?
char a;
char *str=&a;
strcpy(str,”hello”);
cout<<str;
BaihowFF是坏蛋..总会下套...呵呵..确实是圈套...这段程序能够输出hello,但是输出后就崩溃了...原因就在你分配str指针的时候仅仅给了1字节的空间,但是你拷贝了6字节过去(不要忘记了最后的'/0'结束).运行输出后程序因为访问了没有分配的呵呵空间,当然崩溃了.如果你只strcpy(str,"");那程序是可以正常运行的.
6.小心编译器的指针字符串初始化
经常我们想自己处理字符串,但是像下面的初始化是很危险的!!!
char* s="AAA";
cout<<s<<endl;
s[0]='B';
cout<<s<<endl;
你可以拿这段程序去编译...没错!编译器报告正常!...这是最要命的...其实程序不能运行的...输出AAA后就崩溃了..为什么?因为当你在第一句初始化的时候,编译器就认为这是个字符串常量了...再做数组操作的时候肯定错了罗...最好的习惯是声明一个指针,用new分配空间,然后用库函数操作,比如strcpy,strcat等等...
7.让人一眼看上去迷糊的函数指针
看看这句代表什么意思?
int (*s[10])(int);
咦...这是什么?其实这是一个函数指针数组,指向了一组int fun(int)的函数,第一眼确实让人有点迷糊...但是请习惯这样...
8.注意函数传递指针的时候是副本
副本?又下副本?...汗...老兄...不是这个意思...别沉浸在WOW里了啊...看看下面程序的问题:
void GetMemory(char *p)
{
p=new char[100];
strcpy(p,"hello world");
}
void main(void)
{
char *str=NULL;
GetMemory(str);
cout<<str;
delete []str;
str=NULL;
}
当然了..喜欢下套的BaihowFF又给了错程序....错在哪呢?看上去是对的,而且编译器编译也正确啊..怎么就是不能通过呢?而且还崩溃了...好费解吧...
其实原因很简单...GetMemory这个函数出问题了!函数参数是不能传递分配空间的...因为传递过去实际上是一个副本p...不能返回的...而且你在delete那就是件很危险的事情..因为压根没有内容...那我实在想这样用函数分配怎么办呢?像下面这样改一下就ok了:
void GetMemory(char **p) // 改成晦涩难懂的指针的指针
{
*p=new char[100]; //给*p的分配地址
strcpy(*p,"hello world"); // 拷贝内容到*p
}
void main(void)
{
char *str=NULL;
GetMemory(&str); //这地方取地址
cout<<str;
delete []str;
str=NULL;
}
这样就能正常工作了,但是看起来好别扭啊...嗯..确实...但是还可以用其他方法哦....你想想...肯定有办法的...
9.请时刻记住要初始化字符串
嗯...这点大家都知道...那你猜猜下面的程序结果是多少?
char a[10];
cout<<strlen(a)<<endl;
答案应该让你以外...竟然是15...没道理吧?!其实strlen函数的结果和是否初始化有关的...虽然你分配了空间..但是没有初始化..库函数会出错的..sizeof不受影响...切忌初始化哦....
10.小括号,大区别
看看这两端声明,有什么不同?我直接在注释里告诉你答案吧...这样好看点...
char (*str)[20]; //str是一个数组指针,即指向数组的指针.
char *str[20]; //str是一个指针数组,其元素为指针型数据.
千万别小看括号哦...区别大了吧....
最后给个完整程序,想一下运行结果...分析在最后 #include<iostream.h> #include <string.h> #include <malloc.h> #include <stdio.h> #include <stdlib.h> #include <memory.h> typedef struct AA { int b1:5; int b2:2; }AA; void main() { AA aa; char cc[100]; strcpy(cc,"0123456789abcdefghijklmnopqrstuvwxyz"); memcpy(&aa,cc,sizeof(AA)); cout << aa.b1 <<endl; cout << aa.b2 <<endl; }
答案:-16和1
首先sizeof(AA)的大小为4,b1和b2分别占5bit和2bit.经过strcpy和memcpy后,aa的4个字节所存放的值是: 0,1,2,3的ASC码,即00110000,00110001,00110010,00110011所以,最后一步:显示的是这4个字节的前5位,和 之后的2位分别为:10000,和01,因为int是有正负之分
11. 以下代码中的两个sizeof 用法有问题吗?
void UpperCase( char str[] ) // 将str 中的小写字母转换成大写字母
{
for ( size_t i=0; i< sizeof (str)/ sizeof (str[0]); ++i )
{
if ( 'a' <=str[i] && str[i]<= 'z' )
{
str[i] -= ( 'a' - 'A' );
}
}
}
char str[] = "aBcDe" ;
cout << "str 字符长度为: " << sizeof (str)/ sizeof (str[0]) << endl;
UpperCase( str );
cout << str << endl;
答:函数内的sizeof 有问题。根据语法,sizeof 如用于数组,只能测出静态数组的大小,无法检测动态分配的或外部数组大小。函数外的str 是一个静态定义的数组,因此其大小为,函数内的str 实际只是一个指向字符串的指针,没有任何额外的与数组相关的信息,因此sizeof 作用于上只将其当指针看,一个指针为个字节,因此返回。
12. 非C++ 内建型别A 和B ,在哪几种情况下B 能隐式转化为A ?
答:
a. class B : public A { …… } // B 公有继承自A ,可以是间接继承的
b. class B { operator A( ); } // B 实现了隐式转化为A 的转化
c. class A { A( const B& ); } // A 实现了non-explicit 的参数为B (可以有其他带默认值的参数)构造函数
d. A& operator = ( const A& ); // 赋值操作,虽不是正宗的隐式类型转换,但也勉强算一个
13. 以下代码有什么问题?
struct Test
{
Test( int ) {}
Test() {}
void fun() {}
};
void main( void )
{
Test a(1);
a.fun();
Test b();
b.fun();
}
答:变量b 定义出错。按默认构造函数定义对象,不需要加括号。
14. 以下代码有什么问题?
cout << ( true ?1: "0" ) << endl;
答:三元表达式“?: ”问号后面的两个操作数必须为同一类型。
15. 以下代码能够编译通过吗,为什么?
unsigned int const size1 = 2;
char str1[ size1 ];
unsigned int temp = 0;
cin >> temp;
unsigned int const size2 = temp;
char str2[ size2 ];
答:str2 定义出错,size2 非编译器期间常量,而数组定义要求长度必须为编译期常量。
16. 以下反向遍历array 数组的方法有什么错误?
vector array ;
array .push_back( 1 );
array .push_back( 2 );
array .push_back( 3 );
for ( vector::size_type i= array .size()-1; i>=0; --i ) // 反向遍历array 数组
{
cout << array [i] << endl;
}
答:首先数组定义有误,应加上类型参数:vector<int> array 。其次vector::size_type 被定义为unsigned int ,即无符号数,这样做为循环变量的i 为时再减就会变成最大的整数,导致循环失去控制。
17. 以下代码中的输出语句输出吗,为什么?
struct CLS
{
int m_i;
CLS( int i ) : m_i(i) {}
CLS()
{
CLS(0);
}
};
CLS obj;
cout << obj.m_i << endl;
答:不能。在默认构造函数内部再调用带参的构造函数属用户行为而非编译器行为,亦即仅执行函数调用,而不会执行其后的初始化表达式。只有在生成对象时,初始化表达式才会随相应的构造函数一起调用。
18. C++ 中的空类,默认产生哪些类成员函数?
答:
class Empty
{
public :
Empty(); // 缺省构造函数
Empty( const Empty& ); // 拷贝构造函数
~Empty(); // 析构函数
Empty& operator =( const Empty& ); // 赋值运算符
Empty* operator &(); // 取址运算符
const Empty* operator &() const ; // 取址运算符const
};
19. 以下两条输出语句分别输出什么?
float a = 1.0f;
cout << ( int )a << endl;
cout << ( int &)a << endl;
cout << boolalpha << ( ( int )a == ( int &)a ) << endl; // 输出什么?
float b = 0.0f;
cout << ( int )b << endl;
cout << ( int &)b << endl;
cout << boolalpha << ( ( int )b == ( int &)b ) << endl; // 输出什么?
答:分别输出false 和true 。注意转换的应用。(int)a 实际上是以浮点数a 为参数构造了一个整型数,该整数的值是,(int&)a 则是告诉编译器将a 当作整数看(并没有做任何实质上的转换)。因为以整数形式存放和以浮点形式存放其内存数据是不一样的,因此两者不等。对b 的两种转换意义同上,但是的整数形式和浮点形式其内存数据是一样的,因此在这种特殊情形下,两者相等(仅仅在数值意义上)。
注意,程序的输出会显示(int&)a=1065353216 ,这个值是怎么来的呢?前面已经说了,以浮点数形式存放在内存中,按ieee754 规定,其内容为x0000803F (已考虑字节反序)。这也就是a 这个变量所占据的内存单元的值。当(int&)a 出现时,它相当于告诉它的上下文:“把这块地址当做整数看待!不要管它原来是什么。”这样,内容x0000803F 按整数解释,其值正好就是(十进制数)。
通过查看汇编代码可以证实“(int)a 相当于重新构造了一个值等于a 的整型数”之说,而(int&) 的作用则仅仅是表达了一个类型信息,意义在于为cout<< 及== 选择正确的重载版本。
20. 以下代码有什么问题?
typedef vector IntArray;
IntArray array ;
array .push_back( 1 );
array .push_back( 2 );
array .push_back( 2 );
array .push_back( 3 );
// 删除array 数组中所有的2
for ( IntArray::iterator itor= array .begin(); itor!= array .end(); ++itor )
{
if ( 2 == *itor ) array .erase( itor );
}
答:同样有缺少类型参数的问题。另外,每次调用“array.erase(itor); ”,被删除元素之后的内容会自动往前移,导致迭代漏项,应在删除一项后使itor-- ,使之从已经前移的下一个元素起继续遍历。
21. 长重复子串问题 //最长重复子串问题 //想要把一个字符串中重复最长的部分提取出来,比如说有如下字符串: //abcdbcdbcb //对于这个字符串最长的重复子串为bcdbc! //可以用后缀树来做。 #include <stdlib.h> #include <string.h> #include <stdio.h> #define MAXCHAR 5000 //最长处理个字符 char c[MAXCHAR], *a[MAXCHAR]; // int comlen( char *p, char *q ){ int i = 0; while( *p && (*p++ == *q++) ) ++i; return i; } //比较字符串 int pstrcmp( const void *p1, const void *p2 ){ return strcmp( *(char* const *)p1, *(char* const*)p2 ); } int main() { char ch; int n=0; int i, temp; int maxlen=0, maxi=0; printf("Please input your string:/n"); while( (ch=getchar())!='/n' ){ a[n]=&c[n]; c[n++]=ch; } c[n]='/0'; qsort( a, n, sizeof(char*), pstrcmp ); for(i=0; i<n-1; ++i ){ temp=comlen( a[i], a[i+1] ); if( temp>maxlen ){ maxlen=temp; maxi=i; } } printf("maxlen = %d ",maxlen); printf("%.*s/n",maxlen, a[maxi]); system("PAUSE"); return 0; }
22.字符串最长不重复子串长度
#include <cstdio>
#include <cstring>
#include <algorithm>
using namespace std;
int main()
{
const char* str = "abcbecd";
const int n = strlen(str);
int dp[256], last[256];
for (int i = 0; i < 256; ++i)
last[i] = n;
dp[n] = n;
//用dp[i]表示以第i个字符开始的最长不重复子串的右端点位置。
//用last[x]表示字符x最近一次的出现位置。
for (int i = n - 1; i >= 0; --i)
{
dp[i] = min(dp[i+1], last[str[i]] - 1);
last[str[i]] = i;
}
int ans = -1;
for (int i = 0; i < n; ++i)
if (dp[i] - i > ans)
ans = dp[i] - i;
printf("%d/n", ans + 1);
getchar();
return 0;
}