指针(复习)
指针(pointer) 或 指针变量(pointer variable)。是“储存地址的变量”。
它存储的是某一块内存的地址,而其它变量直接存储的是值。
int *p; //变量p是个指向整数型变量内存空间的指针变量,存的是整数型变量a的内存地址。
p = &a;
指针作为函数参数(复习)
而内存地址做函数参数时void swap(int *a, int *b);
,a
,b
都是指针变量,形参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