二重指针详解
- 格式:pdf
- 大小:669.00 KB
- 文档页数:3
二级指针的用法
二级指针是指指向指针的指针。
它在C语言中经常用于动态内存分配、多级数据结构中的数据类型以及函数指针的使用。
二级指针的使用可
以让程序在操作指针的过程中更加灵活,提高程序的可读性和可维护性。
动态内存分配是二级指针的重要应用之一。
在程序运行过程中,可以
使用malloc()函数来动态分配内存空间。
此时需要声明一个指向一级
指针的二级指针,通过将一级指针指向所分配的内存空间,即可实现
动态内存分配。
另一个常见应用是在多级数据结构中使用二级指针。
例如,要在一个
链表中插入一个新节点,需要将这个节点链接到链表中的某个位置。
此时需要将这个节点的地址存储在一个指向节点指针的指针中,即二
级指针。
这样可以在链表中动态添加或删除节点,提高程序的效率和
灵活性。
函数指针也可以使用二级指针。
为了扩展函数指针的功能,可以使用
二级指针作为函数的参数或返回值。
例如,可以使用一个指向指针的
指针来修改原指针所指向的数据,或者在函数中创建一个新指针并返
回其地址。
总之,二级指针在C语言中扮演着重要的角色。
使用二级指针可以提高程序的灵活性和可维护性。
需要注意的是,在使用二级指针时,需要特别小心,避免产生内存泄漏等问题。
因此,在编写程序时,需要仔细考虑数据结构的设计,确保使用二级指针的正确性和安全性。
双指针算法是一种高效的编程技巧,在处理线性数据结构如数组和链表时尤为有用。
它通过同时维护两个或多个指针在数据结构上的位置,并根据特定条件调整这些指针的位置关系,来解决一系列复杂度较高的问题。
双指针方法通常可以将时间复杂度从O(n^2)降低到O(n),从而极大地提高了代码执行效率。
双指针算法的基本形式:1.同步指针:o两个同向移动的指针:在这种模式下,两个指针按照相同的方向遍历数据结构,例如在数组中,两个指针从头或尾部开始,逐步向中间靠拢。
这种策略常用于查找数组中的有效对(如数组中和为给定值的元素对)、去除重复元素等问题。
2.快慢指针:o两个不同步速的指针:其中一个指针(快指针)比另一个指针(慢指针)移动更快,通常是每次移动两个单位而非一个单位。
此策略适用于判断链表是否有环、找到环的入口点、找出链表的中点等场景。
3.对撞指针:o两个反向移动的指针:两个指针从两端开始向中间移动,直到相遇为止。
这种技术常用于去除重复字符形成唯一字符串、寻找旋转数组中的最小/最大元素、找出无重复子数组的最大长度等问题。
算法原理与应用实例:•删除排序数组中的重复项:在已排序的数组中,我们可以设置两个指针i和j,初始时都指向数组的第一个元素。
i指针用来遍历数组,j指针则指向最后一个未重复的元素之后的位置。
当i所指向的元素与j-1所指向的元素不同时,才将arr[i]的值复制到arr[j],并将j向前推进一位。
这样,j之前的区域就是去重后的数组部分。
•寻找两个有序数组的中位数:在一个问题中,有两个升序排列的数组,我们可以通过分别从两个数组头部开始移动两个指针,使得它们所指向的元素之和尽可能接近整个合并后数组的中位数,进而快速求解中位数。
•检查回文串:在字符串中确定是否为回文,可以通过使用一对指针分别从首尾开始向中间移动,比较对应位置上的字符是否相同,若所有对应字符都相同,则字符串为回文。
总之,双指针算法的核心在于利用指针之间的相对位置关系来巧妙地解决问题,其关键在于理解问题的本质并合理设计指针的移动规则,以达到解决问题的目的。
c语言二级指针详解C语言中,指针是一种重要的数据类型,它可以指向另一个变量或者数据结构中的一个元素,并且可以进行不同种类的操作(如解引用、赋值、比较、运算等)。
在C语言中,指针本身也是一个变量,它具有一个内存地址,并且其值就是指向的地址。
而指针变量可以通过指定自己的类型来控制指向的变量或者数据结构元素的类型。
在C语言中,指针本身也可以被指针所指向,这样的指针就被称为“二级指针”或者“指向指针的指针”。
二级指针在一些情况下比普通指针更加灵活,比如当我们需要在函数内部进行指针变量的修改或者返回值时,就可以使用二级指针。
1、指向指针的指针需要使用两个星号(**)来声明,例如:int **p;2、在函数中传递指向指针的指针时,需要将变量的地址传递给函数,而函数需要使用指向指针的指针来访问实际的指针变量。
3、在使用二级指针时,我们需要防止指针变量指向非法内存地址,否则会导致程序出现意想不到的错误。
二级指针是C语言中非常重要的概念,尤其在函数调用和指针变量的修改或返回值时,更是非常有用。
不过,我们在使用二级指针时需要额外注意指向内存地址的合法性,否则会导致程序出现异常。
二级指针是指指向指针对象的指针,即指针的指针,它可以通过间接的方式访问一个指针变量所指向的地址,这种间接的访问方式可以增加程序的灵活性,从而使程序更加易于理解和维护。
1、动态内存管理在C语言中,动态内存分配是通过调用malloc函数来实现的,而释放动态内存则需要使用free函数。
在使用malloc函数分配内存时,它会返回一个指针,指向分配的内存空间的首地址,我们可以将这个指针赋值给一个普通的指针变量,然后通过这个普通指针变量来访问分配的内存空间。
不过,当我们使用malloc来分配一个指针数组时,我们就需要使用二级指针来存储这个指针数组的首地址。
int **p = (int **)malloc(sizeof(int *) * 10);for (int i = 0; i < 10; ++i) {p[i] = (int *)malloc(sizeof(int) * 10);}以上代码中,我们使用了二级指针来存储指向指针数组的地址,然后使用循环语句来为每一个指针分配空间。
双重指针的用法双重指针是指一个指针变量的地址存储在另一个指针变量中,也就是指向指针的指针。
它的主要作用是可以通过改变指针变量的值,来改变指针所指向的变量。
双重指针通常用于以下几种情况:1. 函数参数传递:当需要在函数中修改指针指向的变量时,可以使用双重指针作为函数的参数。
这样可以将指针变量的地址传递给函数,通过修改指针变量的值来改变原始指针的指向。
```c++void modifyPointer(int** p) {int* newPtr = new int(10);*p = newPtr; // 改变原始指针的指向}int main() {int* ptr = nullptr;modifyPointer(&ptr);// 现在ptr指向了一个新的内存地址return 0;}```2. 动态内存分配:当需要动态分配多级指针所指向的内存时,可以使用双重指针。
一级指针用于指向二级指针,二级指针再指向实际的内存。
```c++int** allocate2DArray(int row, int col) {int** arr = new int* [row];for (int i = 0; i < row; i++) {arr[i] = new int[col];}return arr;}int main() {int** arr = allocate2DArray(3, 4);// arr现在指向一个二维数组return 0;}```3. 字符串处理:在处理字符串时,双重指针可以用于修改或返回指向字符串的指针。
```c++void modifyString(char** str) {char* newStr = new char[20];strcpy(newStr, "Hello, World!");*str = newStr;}int main() {char* str = nullptr;modifyString(&str);// 现在str指向了一个新的字符串return 0;}```需要注意的是,在使用双重指针时需要确保所指向的内存已经被分配,避免出现悬空指针或内存泄漏的情况。
C语言指针用法详解C语言指针用法详解指针可以说是集C语言精华之所在,一个C语言达人怎么可以不会指针呢。
下面店铺给大家介绍C语言指针用法,欢迎阅读!C语言指针用法详解(1)关于指针与数组的存储a、指针和数组在内存中的存储形式数组p[N]创建时,对应着内存中一个数组空间的分配,其地址和容量在数组生命周期内一般不可改变。
数组名p本身是一个常量,即分配数组空间的地址值,这个值在编译时会替换成一个常数,在运行时没有任何内存空间来存储这个值,它和数组长度一起存在于代码中(应该是符号表中),在链接时已经制定好了;而指针*p创建时,对应内存中这个指针变量的空间分配,至于这个空间内填什么值即这个指针变量的值是多少,要看它在程序中被如何初始化,这也决定了指针指向哪一块内存地址。
b、指针和数组的赋值与初始化根据上文,一般情况下,数组的地址不能修改,内容可以修改;而指针的内容可以修改,指针指向的内容也可以修改,但这之前要为指针初始化。
如:int p[5];p=p+1; 是不允许的而p[0]=1; 是可以的;//int *p;p=p+1; 是允许的p[0]=1; 是不允许的,因为指针没有初始化;//int i;int *p=&i;p[0]=1; 是允许的;对于字符指针还有比较特殊的情况。
如:char * p="abc";p[0]='d'; 是不允许的为什么初始化了的字符指针不能改变其指向的内容呢?这是因为p 指向的是“常量”字符串,字符串"abc"实际是存储在程序的静态存储区的,因此内容不能改变。
这里常量字符串的地址确定在先,将指针指向其在后。
而char p[]="abc";p[0]='d'; 是允许的这是因为,这个初始化实际上是把常量直接赋值给数组,即写到为数组分配的内存空间。
这里数组内存分配在先,赋值在后。
(2)关于一些表达式的含义char *p, **p, ***p;char p[],p[][],p[][][];char *p[],*p[][],**p[],**p[][],*(*p)[],(**p)[],(**p)[][];能清晰地知道以上表达式的含义吗?(知道的去死!)第一组:char *p, **p, ***p;分别为char指针;char*指针,即指向char*类型数据地址的指针;char**指针,即指向char**类型数据的指针;他们都是占4字节空间的指针。
C语⾔指针知识点总结1.指针的使⽤和本质分析(1)初学指针使⽤注意事项1)指针⼀定要初始化,否则容易产⽣野指针(后⾯会详细说明);2)指针只保存同类型变量的地址,不同类型指针也不要相互赋值;3)只有当两个指针指向同⼀个数组中的元素时,才能进⾏指针间的运算和⽐较操作;4)指针只能进⾏减法运算,结果为同⼀个数组中所指元素的下表差值。
(2)指针的本质分析①指针是变量,指针*的意义:1)在声明时,*号表⽰所声明的变量为指针。
例如:int n = 1; int* p = &n;这⾥,变量p保存着n的地址,即p<—>&n,*p<—>n2)在使⽤时,*号表⽰取指针所指向变量的地址值。
例如:int m = *p;②如果⼀个函数需要改变实参的值,则需要使⽤指针作为函数参数(传址调⽤),如果函数的参数数据类型很复杂,可使⽤指针代替。
最常见的就是交换变量函数void swap(int* a, int* b)③指针运算符*和操作运算符的优先级相同例如:int m = *p++;等价于:int m= *p; p++;2.指针和数组(1)指针、数组、数组名如果存在⼀个数组 int m[3] = {1,2,3};定义指针变量p,int *p = m(这⾥m的类型为int*,&a[0]==>int*)这⾥,其中,&m为数组的地址,m为数组0元素的地址,两者相等,但意义不同,例如:m+1 = (unsigned int)m + sizeof(*m)&m+1= (unsigned int)(&m) + sizeof(*&m)= (unsigned int)(&m) + sizeof(m)m+1表⽰数组的第1号元素,&m+1指向数组a的下⼀个地址,即数组元素“3”之后的地址。
等价操作:m[i]←→*(m+i)←→*(i+m)←→i[m]←→*(p+i)←→p[i]实例测试如下:1 #include<stdio.h>23int main()4 {5int m[3] = { 1,2,3 };6int *p = m;78 printf(" &m = %p\n", &m);9 printf(" m = %p\n", m);10 printf("\n");1112 printf(" m+1 = %p\n", m + 1);13 printf(" &m[2] = %p\n", &m[2]);14 printf(" &m+1 = %p\n", &m + 1);15 printf("\n");1617 printf(" m[1] = %d\n", m[1]);18 printf(" *(m+1) = %d\n", *(m + 1));19 printf(" *(1+m) = %d\n", *(1 + m));20 printf(" 1[m] = %d\n", 1[m]);21 printf(" *(p+1) = %d\n", *(p + 1));22 printf(" p[1] = %d\n", p[1]);2324return0;25 }输出结果为:(2)数组名注意事项1)数组名跟数组长度⽆关;2)数组名可以看作⼀个常量指针;所以表达式中数组名只能作为右值使⽤;3)在以下情况数组名不能看作常量指针:- 数组名作为sizeof操作符的参数- 数组名作为&运算符的参数(3)指针和⼆维数组⼀维数组的指针类型是 Type*,⼆维数组的类型的指针类型是Type*[n](4)数组指针和指针数组①数组指针1)数组指针是⼀个指针,⽤于指向⼀个对应类型的数组;2)数组指针的定义⽅式如下所⽰:int (*p)[3] = &m;②指针数组1)指针数组是⼀个数组,该数组⾥每⼀个元素为⼀个指针;2)指针数组的定义⽅式如下所⽰:int* p[5];3.指针和函数(1)函数指针函数的本质是⼀段内存中的代码,函数的类型有返回类型和参数列表,函数名就是函数代码的起始地址(函数⼊⼝地址),通过函数名调⽤函数,本质为指定具体地址的跳转执⾏,因此,可定义指针,保存函数⼊⼝地址,如下所⽰:int funcname(int a, int b);int(*p)(int a, int b) = funcname;上式中,函数指针p只能指向类型为int(int,int)的函数(2)函数指针参数对于函数int funcname(int a, int b);普通函数调⽤ int funcname(int, int),只能调⽤函数int func(int, int)函数指针调⽤ intname(*func)(int,int),可以调⽤任意int(int,int)类型的函数,从⽽利⽤相同代码实现不同功能,实例测试如下,假设有两个相同类型的函数func1和func2:1int func1(int a, int b, int c)2 {3return a + b + c;4 }56int func2(int a, int b, int c)7 {8return a - b - c;9 }普通函数调⽤和函数指针调⽤⽅式及结果如下所⽰1 printf("普通函数调⽤\n");2 printf("func1 = %d\n", func1(100, 10, 1));3 printf("func2 = %d\n", func2(100, 10, 1));4 printf("\n");56 printf("函数指针调⽤\n");7int(*p)(int, int, int) = NULL;8 p = func1;9 printf("p = %d\n", p(100, 10, 1));10 p = func2;11 printf("p = %d\n", p(100, 10, 1));12 printf("\n");需要注意的是,数组作为函数参数的时候,会变为函数指针参数,即:int funcname( int m[] )<——>int funcname ( int* m );调⽤函数时,传递的是数组名,即funcname(m);(3)回调函数利⽤函数指针,可以实现⼀种特殊的调⽤机制——回调函数。
C语言变量名命名规则一、程序风格:1、严格采用阶梯层次组织程序代码:各层次缩进的分格采用VC的缺省风格,即每层次缩进为4格,括号位于下一行。
要求相匹配的大括号在同一列,对继行则要求再缩进4格。
例如:2、提示信息字符串的位置在程序中需要给出的提示字符串,为了支持多种语言的开发,除了一些给调试用的临时信息外,其他所有的提示信息必须定义在资源中。
3、对变量的定义,尽量位于函数的开始位置。
二、命名规则:1、变量名的命名规则①、变量的命名规则要求用“匈牙利法则”。
即开头字母用变量的类型,其余部分用变量的英文意思或其英文意思的缩写,尽量避免用中文的拼音,要求单词的第一个字母应大写。
即:变量名=变量类型+变量的英文意思(或缩写)对非通用的变量,在定义时加入注释说明,变量定义尽量可能放在函数的开始处。
见下表:对未给出的变量类型要求提出并给出命名建议给技术委员会。
②、指针变量命名的基本原则为:对一重指针变量的基本原则为:“p”+变量类型前缀+命名如一个float*型应该表示为pfStat对多重指针变量的基本规则为:二重指针:“pp”+变量类型前缀+命名三重指针:“ppp”+变量类型前缀+命名......③、全局变量用g_开头,如一个全局的长型变量定义为g_lFailCount,即:变量名=g_+变量类型+变量的英文意思(或缩写)④、静态变量用s_开头,如一个静态的指针变量定义为s_plPerv_Inst,即:变量名=s_+变量类型+变量的英文意思(或缩写)⑤、成员变量用m_开头,如一个长型成员变量定义为m_lCount;即:变量名=m_+变量类型+变量的英文意思(或缩写)⑥、对枚举类型(enum)中的变量,要求用枚举变量或其缩写做前缀。
并且要求用大写。
如:enum cmEMDAYS{EMDAYS_MONDAY;EMDAYS_TUESDAY;……};⑦、对struct、union、class变量的命名要求定义的类型用大写。
春秋华师《C语言程序设计A》在线作业华师《C语言程序设计A》在线作业一、单选题(共20 道试题,共40 分.)1. 一个C程序地执行是从A. 本程序地main函数开始,到main函数结束B. 本程序文件地第一个函数开始,到本程序文件地最后一个函数结束C. 本程序地main函数开始,到本程序文件地最后一个函数结束D. 本程序文件地第一个函数开始,到本程序main函数结束正确答案:2. 在一个被调用函数中,关于return语句使用地描述,错误地是().A. 被调用函数中可以不用return语句B. 被调用函数中可以使用多个return语句C. 被调用函数中,如果有返回值,就一定要有return语句D. 被调用函数中,一个return语句可以返回多个值给调用函数正确答案:3. 以下不能对二维数组a进行正确初始化地语句是A. int a[2][3]={0};B. int a[][3]={{1,2},{0}};C. int a[2][3]={{1,2},{3,4},{5,6}};D. int a[][3]={1,2,3,4,5,6};正确答案:4. 下面有关for循环地正确描述是A. for循环只能用于循环次数已经确定地情况B. for循环是先执行循环体语句,后判断表达式C. 在for循环中,不能用break语句跳出循环体D. for循环地循环体语句中,可以包含多条语句,但必须用花括号括起来正确答案:5. 在下列运算符中,()优先级最低.A. !B. &&C. !=D. ?:正确答案:6. C语言中,长整型long数据在内存中地存储形式是().A. ASCIIB. 原码C. 反码D. 补码正确答案:7. 在C语言中,引用数组元素时,其数组下标地数据类型允许是A. 整型常量B. 整型表达式C. 整型常量或整型表达式D. 任何类型地表达式正确答案:8. 判断char型变量ch是否为大写字母地正确表达式是A. ‘A’<=ch<=‘Z’B. (ch>=‘A’)&(ch<=‘Z’)C. (ch>=‘A’)&&(ch<=‘Z’)D. (‘A’<=ch)AND(‘Z’>=ch)正确答案:9. 将两个字符串连接起来组成一个字符串时,选用()函数.A. strlen( )B. strcpy( )C. strcat( )D. strcmp( )正确答案:10. 下面四个选项中,均是合法转义字符地选项是A. ‘\’’ ‘\\’ ‘\n’B. ‘\’ ‘\017’ ‘\”’C. \018’ ‘\f’ ‘xab’D. \\0’ ‘\101’ ‘x1f’正确答案:11. 若有说明:int a[3][4];则对a数组元素地正确引用是A. a[2][4]B. a[1,3]C. a[1+1][0]D. a(2)(1)正确答案:12. 对以下说明语句地正确理解是______. int a[10]={6,7,8,9,10};A. 将5个初值依次赋给a[1]至a[5]B. 将5个初值依次赋给a[0]至a[4]C. 将5个初值依次赋给a[6]至a[10]D. 因为数组长度与初值地个数不相同,所以此语句不正确正确答案:13. 若有说明:int a[][4]={0,0};则下面不正确地叙述是A. 数组a地每个元素都可得到初值0B. 二维数组a地第一维大小为1C. 因为二维数组a中第二维大小地值除以初值个数地商为1,故数组a地行数为1D. 只有元素a[0][0]和a[0][1]可得到初值0,其余元素均得不到初值0正确答案:14. C语言中地标识符只能由字母、数字和下划线三种字符组成,且第一个字符A. 必须为字母B. 必须为下划线C. 必须为字母或下划线D. 可以是字母、数字和下划线中任一种字符正确答案:15. 逻辑运算符两侧运算对象地数据类型A. 只能是0或1B. 只能是0或非0正数C. 只能是整型或字符型数据D. 可以是任何类型地数据正确答案:16. 以下不正确地C语言标识符是().A. AB1B. _ab3C. 4abD. a2_b正确答案:17. 若有说明:int a[3][4];则对a数组元素地非法引用是A. a[0][2*1]B. a[1][3]C. a[4-2][0]D. a[0][4]正确答案:18. 在C语言中(以16位PC机为例),5种基本数据类型地存储空间长度地排列顺序为A. char<int<="float<double</p">B. char=int<="float<double</p">C. char<intD. char=int=long int<=float<double< p="">正确答案:19. 对for(表达式1;;表达式3)可理解为A. for(表达式1;0;表达式3)B. for(表达式1;1;表达式3)C. for(表达式1; 表达式1;表达式3)D. for(表达式1; 表达式3;表达式3)正确答案:20. 在下列表达式选项中,()是正确地.A. a++++bB. +a++C. a++bD. a+++b正确答案:华师《C语言程序设计A》在线作业二、多选题(共15 道试题,共30 分.)1. 表示一个算法地方式包括哪些?A. 用自然语言表示B. 用流程图表示C. 用N-S流程图表示D. 用伪代码表示正确答案:2. 关于switch语句叙述正确地是A. 语句中,case地冒号后面允许没有语句.B. switch语句中,每一个case地冒号后面都允许跟有多条语句.C. 在与switch语句配套地case语句中所使用地表达式可以是变量或常量.D. switch语句中,可以没有default语句.正确答案:3. 若a为整型变量,则下列表达式地值为0地是A. a%aB. a!=aC. 3/4D. 3&&0正确答案:4. 下列描述正确地是:A. getchar函数只能接收一个字符B. getchar函数得到地字符可以赋给一个字符变量C. getchar函数得到地字符可以赋给一个整型变量D. getchar函数得到地字符可以作为表达式地一部分正确答案:5. 哪些运算符属于逻辑运算符?A. &&B. ||C. !D. ^正确答案:6. 以下哪些函数可以实现字符地输出?A. putchar函数B. getchar函数C. printf函数D. scanf函数正确答案:7. 函数调用strcat(strcpy(str1,str2),str3)地功能,以下说法错误地有:A. 将串str1复制到串str2中后在连接到串str3之后;B. 将串str1连接到串str2之后再复制到串str3之后;C. 将串str2复制到串str1中后再将串str3连接到串str1之后;D. 将串str2连接到串str1中后再将串str1复制到串str3中正确答案:8. 以下哪些属于C语言地运算符?A. 算术运算符B. 关系运算符C. 逻辑运算符D. 位运算符正确答案:9. 哪些运算符属于关系运算符?A. <B. <=C. >=D. %正确答案:10. 下列函数中,能够输出字符数据地函数有A. puts( )B. printf( )C. scanf( )D. putchar( )正确答案:11. 假设float x,y,z;,并通过scanf("%f%f%f ",&a,&b,&c);对a赋于10.0,b赋予22.0,c赋予33.0地值,下面正确地输入形式是(注:符号【代表空格):A. 10<回车>22<回车> 33<回车>B. 10.0<回车>22.0<回车>33.0<回车>C. 10.0,22.0,33.0<回车>D. 10.0【22.0【33.0<回车>正确答案:12. 以下正确地说法有A. C语言程序地基本组成单位是函数;B. 函数必须要有返回值;C. 一个函数地类型为void,在定义时,可以省略void;D. 函数声明是一条C语句正确答案:13. 下面属于转义符地有:A. ‘\t ’B. ‘\n’C. ‘%d’D. ‘\376’正确答案:14. 以下关于函数地叙述正确地有A. 一个C语言程序有且仅有一个main函数;B. C语言程序中,main函数是没有参数地;C. 一个函数通过其他函数间接地调用了自身,这种情况也是一种递归调用;D. main函数是由系统调用地正确答案:15. 若有char ch;,下面可以对变量c正确赋值地语句是:A. ch =‘A’;B. getchar(ch);C. ch = getchar();D. scanf(“%c”,&ch);正确答案:华师《C语言程序设计A》在线作业三、判断题(共15 道试题,共30 分.)1. 若有定义:int a[5],*p;,则操作p=a;是合法地操作.A. 错误B. 正确正确答案:2. 在一个函数定义中,只能有一个return语句.A. 错误B. 正确正确答案:3. C语言中,任意两个函数间不能嵌套定义,但可以互相调用.A. 错误B. 正确4. 若a=1,b=2,c=3,则执行表达式(a>b)&& (c++)后,c 地值为4.A. 错误B. 正确正确答案:5. 如果指针占用地内存大小为2Byte,那么指向它地二重指针占用内存大小为4Byte.A. 错误B. 正确正确答案:6. 变量分为全局和局部两种,自动局部变量没有赋初值时,其值是不确定地.A. 错误B. 正确正确答案:7. 一个C程序无论有多少个源程序文件组成,在其中一个源程序文件中定义地全局变量地作用域默认为整个C程序.A. 错误B. 正确正确答案:8. C语言与其他高级语言一样,对于所有地同级运算符均遵循左结合原则.A. 错误B. 正确正确答案:9. 整型变量m地值为27,语句printf("m=%x",m);地输出结果为m=1b.A. 错误B. 正确10. 未初始化地int类型数组,其各元素地值全是0.A. 错误B. 正确正确答案:11. 实际上,可以用顺序、分支、循环三种结构构造任何算法.A. 错误B. 正确正确答案:12. 若有定义:int a[5][5],(*p)[5];,则操作p=*a;是合法操作.A. 错误B. 正确正确答案:13. 定义一个具有10个元素地整型数组,应当使用语句int a[10]; .A. 错误B. 正确正确答案:14. 在C程序中,函数调用可以作为函数地形参.A. 错误B. 正确正确答案:15. 当主程序执行完毕时,亦即程序执行完毕.A. 错误B. 正确正确答案:版权申明本文部分内容,包括文字、图片、以及设计等在网上搜集整理.版权为个人所有This article includes some parts, including text, pictures, and design. Copyright is personal ownership.文档来源网络及个人整理,勿用作商业用途用户可将本文地内容或服务用于个人学习、研究或欣赏,以及其他非商业性或非盈利性用途,但同时应遵守著作权法及其他相关法律地规定,不得侵犯本网站及相关权利人地合法权利.除此以外,将本文任何内容或服务用于其他用途时,须征得本人及相关权利人地书面许可,并支付报酬.文档来源网络及个人整理,勿用作商业用途Users may use the contents or services of this article for personal study, research or appreciation, and othernon-commercial or non-profit purposes, but at the same time, they shall abide by the provisions of copyright law and other relevant laws, and shall not infringe upon the legitimate rights of this website and its relevant obligees. In addition, when any content or service of this article is used for other purposes, written permission and remuneration shall be obtained from the person concerned and the relevant obligee.文档来源网络及个人整理,勿用作商业用途转载或引用本文内容必须是以新闻性或资料性公共免费信息为使用目地地合理、善意引用,不得对本文内容原意进行曲解、修改,并自负版权等法律责任.文档来源网络及个人整理,勿用作商业用途Reproduction or quotation of the content of this article must be reasonable and good-faith citation for the use of news or informative public free information. It shall not misinterpret or modify the original intention of the content of this article, and shall bear legal liability such as copyright.文档来源网络及个人整理,勿用作商业用途</double<></int</int。
c语言结构体的二级指针C语言中的结构体是一种用户自定义的数据类型,它允许将不同类型的变量组合在一起,形成一个更复杂的数据结构。
在使用结构体时,我们可以通过指针来访问结构体的成员变量。
而当需要对结构体指针进行动态内存分配或传递指针参数时,就需要使用二级指针。
二级指针是指一个指向指针的指针,也就是说,它存储的是指针变量的地址。
在使用二级指针访问结构体时,需要先通过一级指针访问结构体指针,再通过二级指针访问结构体成员。
例如,我们定义了一个结构体类型:```struct student {char name[20];int age;};```然后定义一个指向结构体的指针变量:```struct student *p;```对于这个指针变量,我们可以使用一级指针访问结构体成员: ```(*p).age = 18;strcpy((*p).name, 'Tom');```也可以使用箭头运算符(->)来简化访问:```p->age = 18;strcpy(p->name, 'Tom');```但是如果需要使用二级指针来访问结构体,就需要先定义一个指向结构体指针的指针变量:```struct student **pp;```然后使用一级指针访问结构体指针,再使用二级指针访问结构体成员:```(*pp)->age = 18;strcpy((*pp)->name, 'Tom');```或者使用箭头运算符来简化访问:```(*pp)->age = 18;strcpy((*pp)->name, 'Tom');```需要注意的是,在使用二级指针访问结构体时,需要先为结构体指针分配内存,然后再为指向结构体指针的指针分配内存。
同时,在使用完毕后需要释放内存,以避免内存泄漏。
《深入BREW开发》——第二章软件基础版权所有,未经本作者同意不得转载,否则视为侵权行为,保留追究责任的权力。
第二章软件基础我们正在向我们的软件王国进发,千万别急,在这条路上“枯燥”是我们最大的敌人,不知有多少人在它的面前臣服,但愿您不是其中之一。
或许您觉得应该获得一些鼓励,写一些代码,能够看见一些诸如“Hello, World!”之类的信息。
非常幸运,从这里开始您将能够看见它们了,我会将部分内容使用源程序的方式向您讲解。
在这本书里,我将使用Visual Studio .Net2003的开发环境来执行这些测试程序。
建立测试工程的步骤如下:1、首先打开Visual Studio .Net2003开发环境,选择新建项目。
如图2.1,选择项目类型Visual C++项目/Win32控制台项目,选择路径,填写测试程序名称是Test1,点击确定按钮。
2、在Win32应用程序向导中接受默认设置,点击完成按钮。
如图2.2。
图2.1 建立测试程序这样就完成了一个测试用的应用程序,在以后的测试程序中我将不再重复这个步骤。
由于我们主要是在C语言的基础上讲解,因此在这里创建的是Win32控制台应用程序,它使用Windows的命令窗口显示输出结果。
不过请注意,虽然它使用Windows的命令窗口,但是它是一个Win32的应用程序,而不是DOS应用程序。
在Windows环境下,应用程序分为控制台(Console)应用程序和窗口应用程序。
控制台应用程序不显示窗口,其表现形式类似于DOS环境下的应用程序,但是由于它可以调用Windows的API来实现,所以它是一个Windows的应用程序而不是DOS应用程序。
同时控制台应用程序可以使用标准C/C++的库,这样Windows下的控制台应用程序就和DOS环境下的C/C++十分的相似了。
在这一部分我们将通过实例来演示一些C语言中较比令人“眩晕”的话题,期望能够通过这些实例让您弄明白这些问题的本质。
C语⾔⼆重指针传参数错误案例:void Getmemery(char *p){p=(char *)malloc(100);}void main(){char *str=NULL;Getmemery(str);strcpy(str,"hello world");printf("%s",str);free(str);}错误原因:char* p传递的是参数拷贝,不要指望可以通过char*p进⾏参数传递改正⽅法⼀:char *Getmemery(void){char *p=(char *)malloc(100);return p;}void main(){char *str=NULL;str = Getmemery();strcpy(str,"hello world");printf("%s",str);free(str);}改正⽅法⼆:void Getmemery(void **p){*p=(void **)malloc(100);}void main(){char *str=NULL;Getmemery(&str);strcpy(str,"hello world");printf("%s",str);free(str);}操作案例⼀:void testm(char** a)//传递指针地址,这样才能将指针传递进来,即将主函数中参数的地址传递进来{*a = (char**)calloc(1,sizeof(char)); //*a就是将指针的内容赋值,付给他⼀个地址**a = 'i';//指针所指向的地址的内容}int main(int argc, const char * argv[]) {// insert code here...char* a = NULL;testm(&a);printf("a1----%s-----\n",a);printf("Hello, World!\n");return 0;}操作案例⼆:void testm(char** a){ char* b = (char*)calloc(1,sizeof(char));*b = 'k';*a = &(*b);//先取指针b的内容,即指针b所指向的内容的地址,再取内容的地址,让指针a指向}int main(int argc, const char * argv[]) {// insert code here...char* a = NULL;testm(&a);printf("a1----%s-----\n",a);printf("Hello, World!\n");return 0;}。
CC++——⼆维数组与指针、指针数组、数组指针(⾏指针)、⼆级指针的⽤法1. ⼆维数组和指针要⽤指针处理⼆维数组,⾸先要解决从存储的⾓度对⼆维数组的认识问题。
我们知道,⼀个⼆维数组在计算机中存储时,是按照先⾏后列的顺序依次存储的,当把每⼀⾏看作⼀个整体,即视为⼀个⼤的数组元素时,这个存储的⼆维数组也就变成了⼀个⼀维数组了。
⽽每个⼤数组元素对应⼆维数组的⼀⾏,我们就称之为⾏数组元素,显然每个⾏数组元素都是⼀个⼀维数组下⾯我们讨论指针和⼆维数组元素的对应关系,清楚了⼆者之间的关系,就能⽤指针处理⼆维数组了。
设p是指向⼆维数组a[m][n]的指针变量,则有:int* p=a[0];//此时P是指向⼀维数组的指针。
P++后,p指向 a[0][1]。
如果定义int (*p1)[n];p1=a;p1++后,p1指向a[1][0];则p+j将指向a[0]数组中的元素a[0][j]。
由于a[0]、a[1]┅a[M-1]等各个⾏数组依次连续存储,则对于a数组中的任⼀元素a[i][j],指针的⼀般形式如下:p+i*N+j 相应的如果⽤p1来表⽰,则为*(p1+i)+j元素a[i][j]相应的指针表⽰为:*( p+i*N+j) 相应的如果⽤p1来表⽰,则为*(*(p1+i)+j)同样,a[i][j]也可使⽤指针下标法表⽰,如下:p[i*N+j]例如,有如下定义:int a[3][4]={{10,20,30,40,},{50,60,70,80},{90,91,92,93}};则数组a有3个元素,分别为a[0]、a[1]、a[2]。
⽽每个元素都是⼀个⼀维数组,各包含4个元素,如a[1]的4个元素是a[1][0]、a[1][1]、a[1]2]、a[1][3]。
若有:int *p=a[0];则数组a的元素a[1][2]对应的指针为:p+1*4+2元素a[1][2]也就可以表⽰为:*( p+1*4+2)⽤下标表⽰法,a[1][2]表⽰为:p[1*4+2]特别说明:对上述⼆维数组a,虽然a[0]、a都是数组⾸地址,但⼆者指向的对象不同,a[0]是⼀维数组的名字,它指向的是a[0]数组的⾸元素,对其进⾏“*”运算,得到的是⼀个数组元素值,即a[0]数组⾸元素值,因此,*a[0]与a[0][0]是同⼀个值;⽽a是⼀个⼆维数组的名字,它指向的是它所属元素的⾸元素,它的每⼀个元素都是⼀个⾏数组,因此,它的指针移动单位是“⾏”,所以a+i指向的是第i个⾏数组,即指向a[i]。
⼆级指针作⽤详解概念: 在如下的A指向B、B指向C的指向关系中: ⾸先,C是"⼀段内容",⽐如你⽤malloc或者new分配了⼀块内存,然后塞进去"⼀段内容",那就是C了。
C的起始地址是0x00000008。
B是⼀个指针变量,其中存放着C的地址,但是B也要占空间的啊,所以B也有地址,B的起始地址是0x00000004,但是B内存中存放的是C的地址,所以B⾥⾯的内容就是0x00000008。
B= 0x00000008; //B的内容*B = "⼀段内容"; //B解引⽤,也就是B指针指向的C的值&B = 0x00000004; //B取地址,B的地址是0x00000004 那么,再来看A:A是⼆级指针变量,其中存放着B的地址0x00000004,A也有地址,是0x00000000;*A = B= 0x00000008; //A解引⽤也就是B的内容**A = *B = "⼀段内容"; //B解引⽤,也就是B指针指向的C的值A = &B = 0x00000004; //A存的是B的地址,B的地址是0x00000004&A = 0x00000000; //A取地址 即⼆级指针A指向⼀级指针B,⼀级指针B指向C。
重点理解指针地址,指针内容,指针指向的内容。
下⾯代码:有两个变量a,b,指针q,q指向a,我们想让q指向b,在函数⾥⾯实现。
例⼦1,⼀级指针的实现#include<iostream>using namespace std;int a= 10;int b = 100;int *q;void func(int *p){cout<<"func:&p="<<&p<<",p="<<p<<endl; //note:3p = &b;cout<<"func:&p="<<&p<<",p="<<p<<endl; //note:4}int main(){cout<<"&a="<<&a<<",&b="<<&b<<",&q="<<&q<<endl; //note:1q = &a;cout<<"*q="<<*q<<",q="<<q<<",&q="<<&q<<endl; //note:2func(q);cout<<"*q="<<*q<<",q="<<q<<",&q="<<&q<<endl; //note:5system("pause");return0;}输出结果:&a=0032F000,&b=0032F004,&q=0032F228*q=10,q=0032F000,&q=0032F228func:&p=0018FD24,p=0032F000func:&p=0018FD24,p=0032F004*q=10,q=0032F000,&q=0032F228我们看输出:note:1->a,b,q都有⼀个地址.note:2->q指向a.note:3->我们发现参数p的地址变了,跟q不⼀样了,是的参数传递是制作了⼀个副本,也就是p和q不是同⼀个指针,但是指向的地址0x0032F000(a的地址)还是不变的.note:4->p重新指向b.note:5->退出函数,p的修改并不会对q造成影响。
三级指针:#include<stdio.h>char *c[ ] = { "ENTER" , "NEW" , "POINT" , "FIRST"};char **cp[ ] = { c+3 , c+2 , c+1 , c };char ***cpp = cp;main ( ){printf ( "%s#" , **++cpp );printf ( "%s#" , *--*++cpp);printf ( "%s#" , *cpp[-2]+3);printf ( "%s#" , cpp[ -1] [ -1] + 1 );}char *c[ ] = { "ENTER" , "NEW" , "POINT" , "FIRST" };这个肯定能理解,就是定义几个字符串,然后将其放入一个数组中,因为数组内放的是字符串,所以数组类型必须为char*。
我们在定义一个字符串的时候,经常是这么写char* str = "hello world";那么我们要定义多个字符串的时候,就可以利用数组了,像上边那样。
char **cp[ ] = { c+3 , c+2 , c+1 , c };这是二重指针。
因为前边定义的c是数组类型,也就是一种常量指针(地址),所以c+3,c+2,c+1,c都是指向数组c每个元素的地址。
现在要将数组元素的地址放在数组中,那么该用什么数据类型呢?存放一个变量(也可以是字符串)地址时,需要的是指针,那么存放一个地址的地址,就需要一个二重指针(因为只有指针可以存放地址,这里是存放一个地址的地址,所以要用二重指针)。
char ***cpp = cp;cp本来就是一个常量指针,再加上他的类型是char**,所以它就是一个三级指针。
二重指针详解
朱有鹏
1.二重指针
1.1、二重指针与普通一重指针的区别
本质上来说,二重指针和一重指针的本质都是指针变量,指针变量的本质就是变量。
一重指针变量和二重指针变量本身都占4字节内存空间,
1.2、二重指针的本质
(1)二重指针本质上也是指针变量,和普通指针的差别就是它指向的变量类型必须是个一重指针。
二重指针其实也是一种数据类型,编译器在编译时会根据二重指针的数据类型来做静态类型检查,一旦发现运算时数据类型不匹配编译器就会报错。
(2)C语言中如果没有二重指针行不行?其实是可以的。
一重指针完全可以做二重指针做的事情,之所以要发明二重指针(函数指针、数组指针),就是为了让编译器了解这个指针被定义时定义它的程序员希望这个指针被用来指向什么东西(定义指针时用数据类型来标记,譬如int*p,就表示p要指向int型数据),编译器知道指针类型之后可以帮我们做静态类型检查。
编译器的这种静态类型检查可以辅助程序员发现一些隐含性的编程错误,这是C 语言给程序员提供的一种编译时的查错机制。
(3)为什么C语言需要发明二重指针?原因和发明函数指针、数组指针、结构体指针等一样的。
1.3、二重指针的用法
(1)二重指针指向一重指针的地址
(2)二重指针指向指针数组的
(3)实践编程中二重指针用的比较少,大部分时候就是和指针数组结合起来用的。
(4)实践编程中有时在函数传参时为了通过函数内部改变外部的一个指针变量,会传这个指针变量的地址(也就是二重指针)进去
1.4、二重指针与数组指针
(1)二重指针、数组指针、结构体指针、一重指针、普通变量的本质都是相同的,都是变量。
(2)所有的指针变量本质都是相同的,都是4个字节,都是用来指向别的东西的,不同类型的指针变量只是可以指向的(编译器允许你指向的)变量类型不同。
(3)二重指针就是:指针数组指针
2、二维数组
2.1、二维数组的内存映像
一维数组在内存中是连续分布的多个内存单元组成的,而二维数组在内存中也是连续分布的多个内存单元组成的。
从内存角度来看,一维数组和二维数组没有本质差别。
如:二维数组int a[2][5]和一维数组int b[10]对应关系如下:
a[0][0]a[0][1]a[0][4]a[1][0]a[1][1]a[1][4]
b[0]b[1]b[4]b[5]b[6]b[9]
既然二维数组都可以用一维数组来表示,那二维数组存在的意义和价值在哪里?明确告诉大家:二维数组a和一维数组b在内存使用效率、访问效率上是几乎相同。
使用用二维数组而不用一维数组,原因是在某些情况下,二维数组更好理解、利于组织。
我们使用二维数组,并不是必须,而是一种简化编程的方式。
一维数组的出现其实也不是必然的,也是为了
简化编程。
2.2、二维数组的识别之第一维和第二维
二维数组必然是两个维度,假设数组为a[2][5]从左到右看:
[2]是第一个维度,表示a 这个数组里有两个元素。
[5]是第二个维度,需要进入第一维度的内部观察。
它的内部有5个int 型的元素。
[0][0][0][1][0][2][0][3][0][4]
[1][0][1][1][1][2][1][3][1][4]
a[0]a[1]图数组为a[2][5]内部模型
对后面小节的理解,请读者结合上图进行理解。
2.3、数组名代表数组首元素的地址
“数组名代表数组首元素的地址”这句话既适用于一维数组,也适用于二维数组。
对于一维数组int a[5]而言:数组名a 就表示首元素a[0]的地址,及数组名a 等价于&a[0]。
对于二维数a[2][5]组而言:数组名a 就表示首元素a[0]的地址,及数组名a 等价于&a[0]。
接着看a[0],此时的a[0]有两重身份:在二维数组的第一个维度里a[0]是数组的首元素;而在第二维度里,a[0]本身就是个数组,该数组的首元素是a[0][0],所以此时a[0]也代表一个数组名。
(本段结论同样适合a[1])。
通过“数组名代表数组首元素的地址”可知,a[0]等价于&a[0][0]。
而前面同时有a 等价于&a[0]的结论,所以可以得到a 等价于&&a[0][0]。
记住这个结论,对后面理解数组指针访问二维数组的方式大有助益。
2.4、指针访问二维数组的两种方式
2.4.1、普通指针指向二维数组的第一维
还是拿数组a[2][5]举例,在第一维度里,该数组有两个元素,分别是a[0]和a[1],而a[0]和a[1]本身是个一维数组,及a[0]和a[1]就是数组的数组名,所以我们可以像访问普通的一维数组那样访问a[0]和a[1]:
int*p1=a[0];//数组名代表数组首元素的地址
int*p2=a[1];//数组名代表数组首元素的地址
printf("a[0][0]=%d.\n",*p1);//*p1对应的是a[0][0]的值
printf("a[0][1]=%d.\n",*(p1+1));//*(p1+1)对应的是a[0][1]的值
printf("a[1][0]=%d.\n",*p2);//*p2对应的是a[1][0]的值
printf("a[1][1]=%d.\n",*(p2+1));//*(p2+1)对应的是a[1][1]的值
2.4.2、数组指针访问二维数组
终于到了本章节的重点,前面的内容都是这节的铺垫。
比如:int a[3][3]这样一个二位数组来说,通过前面的学习,我们已经知道,a[3][3]可以分为如下三个小一维数组,
a[0][0]a[0][1]a[0][2]
a[1][0]a[1][1]a[1][2]
a[2][0]a[2][1]a[2][2]
当a即是二维数组名称,但是同时a也表示二维数组的第一个小一维数组(a[0][0] a[0][1]a[0][2])的整个数组的数组首地址,a等价于a[0],那么一问就来了,什么样的指针才能存放数组首地址呢?答案就是数组指针。
如二维数组int a[2][5],能指向改二维数组的数组指针类型为int(*)[5],需要注意的是数组指针类型中的5不是乱填的,它的值必须和他指向的二维数组的第二维中的元素相等。
如char b[77][9]那么此时需要的数组指针类型为char(*)[9]。
了解数组指针的选型之后,就来讲解数组指针访问二维数组的过程,还以int a[2][5]为例:
int(*p)[5]=a;//数组指针p指向二维数组a[2][5]
那么如何通过p来访问a[2][5]中的元素呢:
printf("a[0][0]=%d.\n",**p);//**p对应的是a[0][0]的值
printf("a[0][1]=%d.\n",*(*p+1));//*(*p+1)对应的是a[0][1]的值
printf("a[0][4]=%d.\n",*(*p+4));//*(*p+1)对应的是a[0][4]的值
printf("a[1][0]=%d.\n",**(p+1));//**(p+1)对应的是a[1][0]的值
printf("a[1][1]=%d.\n",*(*(p+1)+1));//*(*(p+1)+1)对应的是a[1][1]的值
printf("a[1][4]=%d.\n",*(*(p+1)+4));//*(*(p+1)+4)对应的是a[1][4]的值
很多人对p要进行两次解引用才能得到值,表示不理解,如果读者能想到前面提到的结论,可能会恍然大悟。
前面结论提到——当a是二维数组的数组名时,a等价于&&a[0][0]。
首先,a和数组指针p是类型匹配的;其次“解引用(*)”和“取地址(&)”这个两个过程是逆过程。
之前a[0][0]连续取两次地址才和a等价,那现在p连续两次解引用得到a[0][0]也是理所当然(**p对应的是a[0][0]的值)。