目录
c语言-数组、指针面试题 - CYYZ古月 - 博客园 ☆☆☆☆☆☆
char *p="abc" 与 char p[]="abc" 的区别_安徽祝子的博客-CSDN博客
1. char p[]和char *p ☆☆☆☆
char str[] = "hello"; // 将字符串常量拷贝到了变量内存
str[0] = 's'; //合法
char *str = "hello"; //字符串存储在静态存储区
p[0] = 's'; //不合法
这两个都可以成功编译,只是第二个会在运行时期出现段错误。首先"hello"是一个字符串常量,存储在静态数据区域(data段),这是在编译时期就确定的。第一个是将字符串常量赋值给了一个变量(全局变量在数据段,局部变量在栈区),实际上是将字符串常量拷贝到了变量内存中,因此修改的只是str[]这个变量的值。第二个是将字符串常量的首地址赋值给p,对p操作就是对字符串常量进行修改!因此出现了段错误。
2.
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; // 0
cout << ( str3 == str4 ) << endl; // 0
cout << ( str5 == str6 ) << endl; // 1
cout << ( str7 == str8 ) << endl; // 1
- str1 str2 str3 str4是数组名,也是数组首元素的地址。编译器给他们分配了新的存储空间来对字符串"abc"进行拷贝,这些变量在内存里是相互独立的,因此他们的地址肯定不同!
- str5,str6,str7,str8 是指针,他们的值就是字符串常量的地址!它们都指向“abc"所在的静态数据区,所以他们都相等。
3. 函数里的指针**
#include <stdio.h>
char *returnStr()
{
char p[]="hello world!";
return p; //p 是局部变量,将字符串拷贝在栈区
} //函数退出时,栈被清空,p被释放
int main()
{
char *str = NULL;
str = returnStr(); //返回的是被释放的地址
printf("%s\n", str);
return 0;
}
因此返回的是被释放的地址!!!下面为修改方式:
#include <stdio.h>
char *returnStr()
{
char *p = "hello world!";
return p; //字符串存储在静态存储区
}
int main()
{
char *str = NULL;
str = returnStr();
printf("%s\n", str);
return 0;
}
4.数组作为函数参数传递
int func(int a[])
{
int n = sizeof(a)/sizeof(int);
for(int i=0;i<n;i++)
{
printf("%d ",a[i]);
a[i]++;
}
}
将数组传递给一个函数时,无法按值传递,而是会自动退化为指针。下面的三种写法其实是等价的:"int func(int a[20]);" 等价于 "int func(int a[]);" 等价于 "int func(int *a);"。
5. 两数交换的那些坑
void swap(int* a, int* b)
{
int *p;
p = a;
a = b;
b = p;
} // 错误
void swap(int* a, int* b)
{
int tmp;
tmp = *a;
*a = *b;
*b = tmp;
} //正确
a和b虽然也是副本,但是在函数内部通过该地址直接修改了对象的值,对应的实参就跟着发生了变化。
其实,指针传递和值传递的本质都是值传递,值传递是传递了要传递变量的一个副本。复制完后,实参的地址和形参的地址没有任何联系,对形参地址的修改不会影响到实参,但是对形参地址所指向对象的修改却能直接反映在实参中,这是因为形参所指向的对象就是实参的对象。正因如此,我们在传递指针作为参数时,要用const进行修饰,就是为了防止形参地址被意外修改。
6. 函数参数为指针应小心
void GetMem(char *p)
{
p = (char*)malloc(100);
} //对实参没有影响
void main()
{
char *str = NULL;
GetMem(str);
strcpy(str, "hello word!");
printf(str);
}
程序崩溃。在上面已经分析过了,传递给GetMem函数形参的只是一个副本,修改形参p的地址对实参str丝毫没有影响。所以str还是那个str,仍为NULL,这时将字符串常量拷贝到一个空地址,必然引发程序崩溃。
void GetMem(char **p)
{
*p = (char*)malloc(100);
}
void main()
{
char *str = NULL;
GetMem(&str);
strcpy(str, "hello word!");
printf(str);
free(str); //不free会引起内存泄漏
}
让指针变量str指向新malloc内存的首地址,也就是把该首地址赋值给指针变量str。前面我们说过,指针传递本质上也是值传递,要想在子函数修改str的值,必须要传递指向str的指针,因此子函数要传递的是str的地址,这样通过指针方式修改str的值,将malloc的内存首地址赋值给str。
7. 数组指针 **
int *p1[10]; // 指针数组,数组的元组都是指针
int (*p2)[10]; //数组指针,是一个指针,指向一个数组
int a[5] = { 1, 2, 3, 4, 5 };
int *ptr = (int *)(&a + 1);
printf("%d,%d", *(a + 1), *(ptr - 1)); // 2 , 5
”+1“就是偏移量的问题:一个类型为T的指针移动,是以sizeof(T)为单位移动的。
a+1:在数组首元素地址的基础上,偏移一个sizeof(a[0])单位。因此a+1就代表数组第1个元素,为2;
&a+1:在数组首元素的基础上,偏移一个sizeof(a)单位,&a其实就是一个数组指针,类型为
int(*)[5]。因此&a+1实际上是偏移了5个元素的长度,也就是a+5;再看ptr是int*类型,因此"ptr-1"就是减去sizeof(int*),即为a[4]=5;
a是数组首地址,也就是a[0]的地址,a+1是数组下一个元素的地址,即a[1]; &a是对象的首地址,&a+1是下一个对象的地址,即a[5]。
8. 二级指针
给定声明 const char * const *pp;下列操作或说明正确的是?
(A)pp++ (B)(*pp)++ (C)(**pp)=\\c\\; (D)以上都不对
一级指针:
(1)const char p : 限定变量p为只读。这样如p=2这样的赋值操作就是错误的。
(2)const char *p : p为一个指向char类型的指针,const只限定p指向的对象为只读。这样,p=&a或 p++等操作都是合法的,但如*p=4这样的操作就错了, 因为企图改写这个已经被限定为只读属性的对象。
(3)char *const p : 限定此指针为只读,这样p=&a或 p++等操作都是不合法的。而*p=3这样的操作合法,因为并没有限定其最终对象为只读。
(4)const char *const p :两者皆限定为只读,不能改写。
二级指针:
(1)const char **p : p为一个指向指针的指针,const限定其最终对象为只读,显然这最终对象也是为char类型的变量。故像**p=3这样的赋值是错误的, 而像*p=? p++这样的操作合法。
(2)const char * const *p :限定最终对象和 p指向的指针为只读。这样 *p=?的操作也是错的,但是p++这种是合法的。
(3)const char * const * const p :全部限定为只读,都不可以改写
9. *p++、 (*p)++、 *++p、 ++*p
int a[5]={1, 2, 3, 4, 5};
int *p = a;
*p++ 先取指针p指向的值(数组第一个元素1),再将指针p自增1;
cout << *p++; // 结果为 1
cout <<(*p++); // 1
(*p)++ 先去指针p指向的值(数组第一个元素1),再将该值自增1(数组第一个元素变为2
cout << (*p)++; // 1
cout <<((*p)++) // 2
*++p 先将指针p自增1(此时指向数组第二个元素),* 操作再取出该值
cout << *++p; // 2
cout <<(*++p) // 2
++*p 先取指针p指向的值(数组第一个元素1),再将该值自增1(数组第一个元素变为2)
cout <<++*p; // 2
cout <<(++*p) // 2