10 重忆C之 指针、内存、位运算

指针(复习)

指针(pointer) 或 指针变量(pointer variable)。是“储存地址的变量”。
它存储的是某一块内存的地址,而其它变量直接存储的是值。

int *p;           //变量p是个指向整数型变量内存空间的指针变量,存的是整数型变量a的内存地址。
p = &a;
pointer

指针作为函数参数(复习)

而内存地址做函数参数时void swap(int *a, int *b);ab都是指针变量,形参a和b也是两个指向整数型变量内存空间的指针变量。
当函数被调用,两个相应的地址就会被作为实际地址传入,分别被保存在指针变量a和指针变量b中,并在函数内使用。

指针与数组 (复习)

int a[3] = {1, 2, 3};
int *p;
p = &a[0];    //p保存了数组a首元素地址,指向数组a首元素。
//通过在地址p上+1或-1,可使指针内存储地址改变,使指针指向前一或后一元素。

字符串和指针

char *string2 = "Hello";

这里string2实际是一种字符指针。在string2中存储了一个程序运行时"Hello"这个字面量在内存中一个“字面量池区”的地址。
(注意,“字面量池区”与“栈区”、“堆区”和“全局区”都没包含关系)

指针保存的是内存的地址。C 语言编译器会根据指针类型决定指针指向的对象长度。

  • int *类型指针会从指定地址向后寻找4个字节作为变量的储存单元;-
  • double *类型指针会从指定地址向后寻找8个字节作为变量的储存单元。

程序本身会被加载到内存,程序的字面量和变量在程序运行时也都存储在内存中。指针让人更灵活地进行涉及内存的操作,也为程序设计中的一些复杂结构设计提供了方便。

动态分配内存

栈区 for 局部变量

C 语言程序在编译时会被分配到内存上的一片有限的连续区域,这部分内存会被用于存储局部变量(在某一个函数内声明的变量)的值。我们平时声明局部变量、给局部变量赋值的时候就在使用这部分的内存。这部分的内存区域被我们称为——栈区(stack)。

  • 栈上的内存并不需要程序员进行管理。

堆区 for 程序主动申请

这部分的内存是我们通过程序手动地向系统申请的。栈区内存大小编译时就已经被限制,如果使用超过限制的内存就会出现“溢出”的情况,而堆区的内存可以一直被申请、使用,直到操作系统中的有效内存无法再被申请为止,相比之下,堆区控制起来更为灵活。

  • 堆区上内存由程序员自己管理,如果不释放可能会造成内存泄露的严重后果。

全局区(或静态区,static storage area) for 全局静态变量

程序中的全局变量和静态变量都被存储在这块内存区域中。这块内存我们既不说它是“栈区”,也不说它是“堆区”。

动态申请内存

正因栈区上内存大小受限,在内存需要比较大的(超出栈区限制)的情况下,需要申请堆区上的内存。但堆区被申请后,在使用过程中若不释放,就可能“内存泄漏(memory leak)”。很多企业级应用,都因内存泄漏而在“正常”运转很长时间后,突然崩溃。

若需使用堆上内存,需将malloc.h或stdlib.h引入程序。这两个标准库都定义了申请、管理堆区上内存的函数。引入后,可通过

int *p; 
p = (int *) malloc(sizeof(int));

的方式声明一个整数型的指针p,向系统申请堆区上sizeof(int)(表示一个整数型变量所需的内存空间大小)的一块内存空间,并将指针p赋值为这片空间所在的起始地址,使得p指向这片空间。

在这里,malloc返回默认为void *(无类型指针)类型,在malloc之前添加的(int *)会将这片内存空间的起始地址标记为整数型的地址,使之与整数型的指针变量相匹配,否则编译器就会说出现了类型不匹配的问题,不让你的程序通过编译。

  • 如果要修改栈区内存空间中的值应该怎么做?
    可以直接通过p = 4;这样的语句实现吗?

  • 不能。因为p是个指针变量,写p = 4;的话我们将修改存储地址。
    要对值进行修改,需使用之取值符号*,用*p = 4;修改它指向的空间内存储的值。这点堆区空间与栈区空间相同。

相关语句

int *arr = (int*)malloc( n * sizeof (int));//这只有一个参数
int *arr = (int*)calloc( n , sizeof (int));这有两个参数

  • calloc与malloc主要有两点不同:

calloc函数申请的内存空间是经过初始化的,全部被设成了0,而不是像malloc所申请的空间那样都是未经初始化的。
calloc函数适合为数组申请空间,我们可以将第二个参数设置为数组元素的空间大小,将第一个参数设置为数组的元素数量。

栈内存的释放

只申请空间而不释放,在工程上不安全。
一般要在main函数结束返回之前,使用free(arr);释放arr数组指向的被分配的堆区上的空间。同时避免错误地使用arr指针,再次触碰到那个已经不应该再被访问的地址对应的内存,要将这个指针置为空,即arr = NULL;.

指向指针的指针

在给出的代码中,我们声明了一个指针变量p并且让它指向了整数型变量a。我们有没有可能声明另一个指针,让它指向我们已经有的指针p呢?

从内存上来想,指针变量p也是保存在内存里的,所以它的地址也应该是可以通过&p获得到的。
可以再赋给它指针

int **q; 
q = &p;
#include <stdio.h>
int main() {
    int a = 4;
    int *p;
    p = &a;
    int **q;
    q = &p;
    printf("p in %p, q =%p\n",&p,q);
    printf("a in %p, p = %p, *q = %p\n",&a, p, *q);     //以p格式输出成0x十六进制数
    printf("a = %d, *p = %d, **q = %d\n", a, *p, **q);
    return 0;
}

运行结果:

p in 0x7ffd60bf0f58, q =0x7ffd60bf0f58                                        
a in 0x7ffd60bf0f54, p = 0x7ffd60bf0f54, *q = 0x7ffd60bf0f54                  
a = 4, *p = 4, **q = 4  

无类型指针以及类型转化

malloc函数的返回值类型为void *,是一种特殊的指针类型,任何一个其他指针变量都可以被直接赋值给void *类型的指针,例如:

void *vp;
int *p;
p = &"123";
vp = p;

写法合法。

强制类型转换

但如果反过来,直接将无类型指针赋给一个其他类型指针变量,就必须要在前面加上被赋值指针变量的类型,如(int *)。通过(int *)这种方式使值的类型发生改变,称为 强制类型转换(explicit type conversion)。
例如:

float num = 2.3f;
printf("%d\n", (int) num);

会将一个浮点型的变量在输出时强制转换为整数型的变量。这里包围了int的圆括号()被称为 类型转换运算符,可将之后的数值(变量、字面量或者函数的返回值等)强制变为括号内说明的类型。

隐式类型转换

除了强制类型转换,在混合多种不同类型的运算中,还存在一种 隐式类型转换(implicit type conversion 或 type coercion)。

  • 如果一个运算(和一个运算符关联)中,参与运算的数值类型不同,则会先转成同一类型,然后再进行运算。
  • 隐式转换有一个固定的转换方向,尽可能保证数据的精度。例如,int型会被转为long型,float型会被转为double型。
  • 赋值时,类型会以赋值号左侧为准,右侧的表达式的结果类型会被转为左边变量的类型。如果右侧表达式其实精度高于左侧的变量类型的精度时,一部分超出精度的数据将会丢失。
隐式类型转换

位运算

我们不仅可以把数值按照其声明类型看待,也可以把他们当成最本质的内存中的二进制存储看待。
C 语言提供了直接对内存中每一个按位进行运算的操作符,使用按位运算可以将一个存储单位中的各个二进制位左移或右移一位,也可以将一个存储单位中所有的二进制位取反,这些操作多数要比直接进行数值上的运算来得要高效。

位运算
  • 按位异或:
    异或操作符“^”,“a^b”的位运算规则是:当且仅当按位运算两数位不同则该结果为1,否则位运算结果为0
移位运算

举例:输入一个数 以及取反。

#include <stdio.h>
int main() {
    int number;
    scanf("%d", &number);
    printf("%08x %08x" , number ,~number);
    return 0;
}

这里使用了%08x进行占位,其中%x表示以 1616 进制形式作为数字格式,在中间插入的08可以使得输出不足8格数字时,在左侧补齐知道达到8位数字。

#include <stdio.h>  //取反

int main() {
    int number;
    scanf("%d", &number);
    printf("%08x %08x" , number ,~number);
    return 0;
}
#include <stdio.h>     

int main() {
    int number;
    number = 3;
    printf("%d\n", number<<1);   //左移
    printf("%d\n", number>>1);   //右移
    return 0;
}

左移一位时,实际相当于被乘以 2;
右移一位时,相当于被除以 2(若二进制形式最右位是 1,则相当于减 1 再除以 2);负数右移是最高位补1的。

题目:





代码:

#include <stdio.h>
int plus(char operator_ , int c ){
    if ((c == 1) && (operator_ == '+'))
        return 1;
    if ((c == 0) && (operator_ == '+'))
        return 1;    
    if ((c == 1) && (operator_ == '-'))
        return 0;    
    if ((c == 0) && (operator_ == '-'))
        return 0;
}
int main() {
    char t,operator_;
    int r_pri = 0;
    int w_pri = 0;
    int x_pri = 0;
    int output,i;
    i = 0;
    while(i<3){
        scanf("%c",&t);
        switch (t){
            case 'r': r_pri = 1;
                continue;
            case 'w': w_pri = 1;
                continue;
            case 'x': x_pri = 1;
                continue;
            case '\n':
                i = 3;
                break;  //switch里的break是终止switch的,要想跳出while得另设别的条件。
        }
    }
    //printf("begin:rwx %d%d%d\n",r_pri,w_pri,x_pri);
    while(scanf("%c",&t)!=EOF){
        switch(t){
            case '+': operator_ = '+'; continue;
            case '-': operator_ = '-'; continue;
            case 'r': r_pri = plus(operator_,r_pri); continue;
            case 'w': w_pri = plus(operator_,w_pri); continue;
            case 'x': x_pri = plus(operator_,x_pri); continue;
            case '\n': //printf("%d%d%d\n",r_pri,w_pri,x_pri);
                continue;
        }
    }
        output = 1*x_pri + 2*w_pri + 4*r_pri;
        printf("%d",output);
    return 0;
}

踩坑的题目:

一个班级 n个学生,每学生有一个名字。班主任希望知道学生中名最长(名字中的一个空格长度计为 11)的学生是谁。

提示 1:

带有空格的输入,可以使用 scanf 读入时可以逐字符读入,第一个参数使用 "%c",每行读入以 \n 字符被读入来判断结束。对于是否还有新的行没有读入的情况,可以用:while (scanf(/* 这部分省略*/) != EOF) {}的方式进行。

提示 2:

由于 scanf 之后使用 "%c" 格式,读入 n 之后的 \n 一定要在之前进行处理。

  • 练习对字符的操作和使用
  • 练习使用循环
  • 练习 strlen 函数使用
  • 鼓励使用 strcpy 函数
  • 练习使用 EOF 表示读到文件末尾
  • 输入格式

程序接受的输入的第一行是一个整数 n,表示学生的总数。之后的 n行,每行会接受一个学生的名字(可能有空格)。学生的名字不超过 100个字符。
输出 n位学生中最长的学生名字(如果有多个名字一样长的学生,输出第一个)。

样例输入

3
Steve Jobs
Bill Ma
Sunny Fei

样例输出

Steve Jobs

解:这道题就非常恶心了,与其说是恶心,不如说是知识储备不够,这里需要我做的是读入字符串,但是反复会有溢出/段错误的提示,因此怎么样使得字符串不溢出是需要考虑的问题。踩过的坑:https://wenda.jisuanke.com/course/604?question_id=6054#discussion-3887
代码释读

#include <stdio.h>
#include <string.h>
int main() {
    int n,i,j;
    char string[101] ;   //为啥是101呢?100字符串+一格=个结尾字符
    scanf("%d\n", &n);  //这里如果不写\n的话,光标位置实际上就还在\n以前
    char t;
    char arr[n][101];   //为啥是101呢?100字符串+一格=个结尾字符
    i = 0;
    j = 0;
    while (scanf("%c",&t)!=EOF){
        if (t != '\n'){
            arr[i][j] = t;
            j++;
        }else{
            arr[i][j] = '\0';
            j = 0;
            i++;//检测到行末的\n,数组就换行读入下个名字,
         //还记得第一个scanf的\n吧,不写的话,本个while第一步就会读入第一行数字的\n,触发i++
        }
    }
    strcpy(string,arr[0]);
    for (i = 1; i< n; i++){
        //printf("%s\n",arr[i]);
        if (strlen(arr[i]) > strlen(string)){
            strcpy(string,arr[i]);
        }
    }
        printf("%s",string);
    return 0;
}

遇到问题的习题:哈希函数:

小明设计了一个哈希函数,将一个长度为 k 的字符串转成个长度为 32的字符串。设计如下:
声明一个长度为 32 的数组 arr,并将其中元素全部初始化为 0。
取出每一位的 ASCII 值,将长度为 k 的字符串中第 ii位的 ASCII 码加入到 arr[i % 32] 中(1≤i≤k)。
声明一个长度为 32 的数组 bits,令 bits[j] 为 arr[31 - j] 与 arr[j] << 1 的值进行了按位异或运算后得到的结果(0≤j≤31)。
计算出 bits[j] % 85 + 34 并将该十进制数在 ASCII 码中对应的字符输出到结果字符串的第 j + 1j+1 位(0≤j≤31)。
请实现一个程序,当输入一个字符串 s 后,输出哈希函数的结果 f(s)。

输入格式

输入为一行,包括一个长度为 kk 的字符串(32 < k < 50032<k<500),这个字符串由大写字母、小写字母和数字组成(不含空格)。

输出格式

输出为一行,为一个长度为 3232 的字符串哈希结果

样例输入

123456789012345678901234567890123

样例输出

"p*+,)&'ebst*+,)&'ebst*+,)&'eb&r

代码:

#include <stdio.h>
#include <string.h>
int main(){
    char s[501];
    scanf("%s",s);
    int k,i,j;
    char l,m;
    int arr[32];
    for (i = 0 ;i <32 ;i++){
        arr[i] = 0;
    }
    k = strlen(s);
    for (i = 1;i <= k;i++){
        arr[i%32] = (int)(s[i-1]) + arr[i%32];
    }
    int bits[32];
    for (j = 0;j < 32;j++){
        bits[j] = (arr[31 - j] ^ (arr[j]<<1));
        //printf("arr(31-%d) = %d ; arr[%d]<<1 = %d; bits[%d] = %d\n",j,arr[31-j],j,arr[j]<<1,j,bits[j]);
    }

    for (j = 0; j < 32 ; j++){
            bits[j] = ( bits[j] % 85 + 34);
            printf("%c",(unsigned char)(bits[j]));
        } 

    return 0;
}

踩过的坑:https://wenda.jisuanke.com/course/604?question_id=6108#answer-7544

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,607评论 6 507
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,239评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,960评论 0 355
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,750评论 1 294
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,764评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,604评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,347评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,253评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,702评论 1 315
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,893评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,015评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,734评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,352评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,934评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,052评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,216评论 3 371
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,969评论 2 355

推荐阅读更多精彩内容

  • 指针是C语言中广泛使用的一种数据类型。 运用指针编程是C语言最主要的风格之一。利用指针变量可以表示各种数据结构; ...
    朱森阅读 3,446评论 3 44
  • void* 类型指针:通用变体类型指针;可以不经转换,赋给其他指针,函数指针除外;malloc返回的就是void*...
    冰吉凌阅读 3,329评论 0 18
  • C语言指针的总结 1. 变量 不同类型的变量在内存中占据不同的字节空间。 内存中存储数据的最小基本单位是字节,每一...
    xx_cc阅读 3,748评论 11 39
  • C语言中内存分配 在任何程序设计环境及语言中,内存管理都十分重要。在目前的计算机系统或嵌入式系统中,内存资源仍然是...
    一生信仰阅读 1,162评论 0 2
  • 一定有一个人,可以和我 彻夜长谈 从物质与精神的思辨 到印度尼西亚 鲜为人知的群岛 幻想随着日本暖流 远见 大阪、...
    刘贤裕V阅读 236评论 0 0