来源:https://www.cnblogs.com/cyyz-le/p/11514477.html
文本
“如无特殊说明,以下题目均为Linux下的32位C程序。”
让我们从一些简单的热身开始。
“1.计算以下值。”
char str1[] = {'a', 'b', 'c', 'd', 'e'};
char str2[] = "abcde";char *ptr = "abcde";char book[][80]={"计算机应用基础","C语言","C++程序设计","数据结构"};(str1)=?
(str2)=?
(指针)=?
(书)=?
(书[0])=?
“分析:”
(str1)=5,即5*(char)=5;
(str2)=6,字符串以'\0'结尾,所以占用字节数为6;
(ptr)=4,ptr在32位平台上是一个指针,大小为4字节;
(book)=320,book是一个二维数组,4801
(book[0])=80,book[0]是第一维数组,因为这个80*1
求数组元素个数也很简单。 取第一个为(str1)/(char)。
“2、上面是计算它们占用的字节数,下面看看如何求字符串或数组的实际长度,计算下面的值。”
char arryA[] = {'a','b','c','\0','d','e'};
char arryB[] = {'a','b','c','d','e'};
char arryC[6] = {'a','b','c','d','e'};
char *str = "abcde";“分析:”
(arryA) = 3,遇到'\0'就返回,不管后面有多少个字符;
(arryB) 无法确定长度,没有人为写'\0',会继续计算,直到找到终止符,结果未知;
(arryC)=5, 指定数组的大小,编译器会自动在空闲空间加上'\0',其实和char arryC[6] = {'a','b','c是一样的','d', 'e','\0'}; 相等的。
(str) = 5,不包括尾随的“\0”。
我们来看看上面两者的区别:
(1) 是C语言中的一元运算符,类似于++、--等;
对于数据类型,(type),例如(int)
对于变量,()
注意:不能用于函数类型、不完整类型或位域。 不完整类型是指存储大小未知的数据类型,例如存储大小未知的数组类型,
内容未知的结构或联合类型、void类型等。例如:(max),如果变量max定义为int max(); (), 然后
定义为 char[MAX],MAX 未知。
(2) 是一个原型为int(char *s)的函数;
的计算必须依赖字符序列中的'\0',用于判断字符序列是否结束。
《3.骗人的char str[]和char *str》
(1) 下面的操作是否合法? 如果出现问题,在什么阶段? 编译时还是运行时?
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;
cout << ( str3 == str4 ) << endl;
cout << ( str5 == str6 ) << endl;
cout << ( str7 == str8 ) << endl;“分析:”
结果是:0 0 1 1
先了解一下str1、str2、str3、str4,它们是什么? 它们是数组名,也就是数组第一个元素的地址! “str1 == str2”的本质是比较两个数组的地址是否相同。 正如我们上面所说,编译器为它们分配了新的存储空间来复制字符串“abc”。 这些变量在内存中是相互独立的,所以它们的地址一定是不同的!
然后了解str5、str6、str7、str8,它们是什么? 它们是指针,它们的值是字符串常量的地址! 它们都指向“abc”所在的静态数据区,所以都是相等的。
(3)更进一步:下面的程序是不是有问题? 如果有,问题出在哪里? 如何修改?
#include
char *returnStr()
{
char p[]="hello world!";
return p;
}
int main()
{
char *str = NULL;
str = returnStr();
printf("%s\n", str);
return 0;
}“分析:”
p是局部变量,复制字符串"hello word!"即可,局部变量存放在栈中,当函数退出时,栈被清空,p会被释放,所以返回的是释放的内存地址,这样做是错误的。
可以进行以下修改:
#include
char *returnStr()
{
char *p = "hello world!";
return p;
}
int main()
{
char *str = NULL;
str = returnStr();
printf("%s\n", str);
return 0;
}搜索公众号C语言中文社区,后台回复“资源”,免费领取200G编程资料。
这样写就不会有问题了,因为“hello world!” 存放在静态数据区,将该区的首地址赋值给指针p并返回,即使函数退出,字符串常量所在的内存也不会被回收,所以可以访问到字符串常量。
当然你也可以这样修改:
#include
char *returnStr()
{
static 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;iprintf("%d ",a[i]);
a[i]++;
}
}原来n的值永远是1! 为什么会这样? 这是因为在C中,将数组传递给函数时,不能按值传递,而是会自动退化为指针。 下面三种写法其实是等价的:
“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;
}“分析:”
当程序运行调用函数时,会将参数压入栈中,并为其分配新的空间。 这时候传入的其实是一个副本,如下图所示:
图像
a的值和b的值都是地址,交换a和b的值就是交换两个地址,也就是说只改变副本的地址,而指向的对象地址没变! .
正确的做法应该是这样的:
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);
}“分析:”
程序崩溃。 上面已经分析过,传递给函数形参的只是一个拷贝,修改形参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指向新内存的首地址,也就是把首地址赋值给指针变量str。 正如我们前面所说,指针传递本质上是值传递。 如果要在子函数中修改str的值,必须传递一个指向str的指针。 所以子函数需要传递str的地址,这样就可以通过指针修改str的值。 , 将内存首地址赋值给str。
《七、关于数组指针的疑惑》
(1) 说出下列词语的意思?
int *p1[10];
int (*p2)[10];第一个是指针数组。 首先,它是一个数组,数组的元素都是指针。
第二个是数组指针,首先他是一个指针,它指向一个数组。
下面这张图可以很清楚的说明:
图像
(2)写出下面程序运行的结果
int a[5] = { 1, 2, 3, 4, 5 };
int *ptr = (int *)(&a + 1);
printf("%d,%d", *(a + 1), *(ptr - 1));“分析:”
答案是2,5。 这道题的关键是理解指针运算,“+1”就是偏移量的问题:类型T的指针以(T)为单位移动。
**a+1:** 基于数组首元素的地址,偏移一个(a[0])个单位。 因此,a+1代表数组的第一个元素,即2;
&a+1:在数组第一个元素的基础上,偏移一个(a)个单位,&a实际上是一个数组指针,类型为int()[5]。 因此&a+1实际上偏移了5个元素的长度,即a+5; 而ptr是int类型,所以“ptr-1”是负号(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) 以上都不正确
“分析:”
答案是A。
先从“一级指针”说起: (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将其final对象限制为只读。显然,这个final对象也是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《参考博客:》
1、《C语言与区别》:
2、“常量字符串为什么位于静态存储区?” “:
3.《值传递、指针传递、引用传递的区别》:
4.纽客网mlc回答:#
--- 结束---