C语言学习第8周-指针与字符串

指针

 第一部分--取地址运算(&运算符取出变量的地址)

 sizeof是一个运算符,给出某个类型或变量在内存中所占据的字节数

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>

int main()
{
	int a;
	a = 6;
	printf("sizeof(int)=%ld\n", sizeof(int));
	printf("sizeof(a)=%ld\n", sizeof(a));
	return 0;
}

上述语句一个是计算sizeof(int)占据多少字节,一个是变量a占据多少字节。在打印sizeof的时候要用的符号是%ld

int占用4个字节,double占用8个字节

运算符&:&是一个运算符,与加减乘除一样,他是一个运算符。

scanf("%d", &a);

 它的作用是:获取变量的地址,它的操作数必须是变量。

为什么变量会有地址:因为C语言的变量是放在内存里面的每个变量都会有一定的字节,这些字节会在内存中占据一定的地方,它放在某个地方就会有个地址。&运算符就是把那个变量的地址,拿出来告诉你。

  • 获取变量的地址,它的操作数必须是整数。
  • int i ;  printf("%x",&i);%x是以十六进制的类型打印
  • 地址的大小是否与int相同取决于编译器.(这取决于你的架构,和架构有关)
  • int i ;printf("%p",&p);%p是打印指针的
  • &不能对没有地址的东西取地址。

&在取地址的时候只能对变量取地址,如果不是变量就不能取地址。 &不能对没有地址的东西取地址。假如a和b都是变量,&(a+b);&(++a);&(a++),这些都是不能取地址的。

试一试以下&

  • 变量的地址
  • 相邻变量的地址
  • &的结果的sizeof
  • 数组的地址
  • 数组单元的地址
  • 相邻的数组单元的地址。

 打印地址,看一下地址的打印符号。

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>

int main()
{
	int i=0;
	int p;
	printf("%p\n", &i);
	printf("%p\n", &p);
	return 0;
}

后面的C语言的内存模型会讲,上面的两个变量都是本地变量(也就是局部变量),i是先定义的那个变量,p是后定义的变量,这两个东西会被存放在一个叫堆栈的地方,在堆栈里面分配变量是自顶向下进行堆放的,所以 i 在高为,而 p 在低位。

 数组的地址

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>

int main(void)
{
	int a[10] ;
	
	printf("%p\n", &a);
	printf("%p\n", a);
	printf("%p\n", &a[0]);
	printf("%p\n", &a[1]);
	return 0;
}

 数组中相邻两个变量之间相差4个字节。数组中&a与a与a[0]的地址是一样的,a[0]与a[1]相差4个字节。相邻两个int之间是相差4个字节的。

 第二部分--指针(指针变量就是记录地址的变量)

 学习计算机一定要有一个非常强大的心理状态,计算机的所有东西,全都是人做出来的别人能想的出来的,我也一定能够想的出来,在计算机里头没有任何黑魔法,所有的东西只是我现在不知道而已,总有一天,我会把所有的细节,所有的内部东西,全都搞明白了。这些函数,只是我们现在还没有搞清楚而已,这些也是某个人搞出来的,那个人和我们一样也是只有一个脑袋而已。

从已知入手,我们学习的scanf()函数中使用到了&符号。

如果能够将取得的变量地址传递给一个函数,能否通过这个地址在那个函数内访问这个变量。

scanf("%d",&i);

scanf()的原型应该是怎么样的?我们需要一个参数能保存别的变量的地址,如何表达能够保存地址的变量?如果用整形(int)变量来保存地址,是不可取的。那么那个类型的变量可以保存地址呢?

答案就是指针,一个指针类型的变量就是保存地址的变量。

int i;
int*p = &i;

这里的*p表示的是p是一个指针,上面第二条语句的意思就是,我们把 i 的地址交给了 p,我们就说p指向了  i 。p 指向了 i 就是说 p 里面的值就是 i 那个变量的地址。

int*p, q;

这条语句是说 p是一个指针,q是一个普通int变量。

指针变量

  • 变量的值是内存的地址
  • 普通变量的值是实际的值
  • 指针变量的值是具有实际值的变量的地址。 
  • 指针变量里面只会放别的变量(包括指针变量)的地址。

作为参数的指针

定义函数的时候如:void f(int*p);在被调用的时候得到了某个变量的地址,举例 int i =0;f(& i)

在函数里面可以通过这个指针访问外面的这个变量 i。

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>

void f(int*p);

int main(void)
{
	int i=6;
	printf("&i=%p\n", &i);
	f(&i);//这条语句的意思就是把i的地址这个参数传递给f()函数。
	return 0;
}
void f(int*p)
{
	printf("p=%p", p);
}

对比int*p和int k的区别

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>

void f(int*p);

int main(void)
{
	int i=6;
	printf("&i=%p\n", &i);
	f(&i);   //这条语句的意思就是把i的地址这个参数传递给f()函数。
	g(i);    //这条语句的意思是把i里面的值传递给g()函数。
	return 0;
}
//对比如下两个函数,一个为地址,一个为值。

void f(int*p)//这个函数需要的参数是一个地址
{
	printf("p=%p", p);
}

void g(int k)//这个函数的参数需要的是一个整形的值。
{
	printf("%d\n", k);
}

 访问那个地址上的那个变量*

  • *是一个单目运算符,用来访问指针上的值表示的变量的地址。(*作为单目运算符时不是乘号,因为他只能带一个算子。)在*p中,p是指针,指针里面存放的是地址,*号是用来访问指针里地址所对应变量的值
  • 可以做左值,也可以做右值。意思就是我们可以放在赋值号的右边我们读它的值,也可以放在赋值号的左边我们去写它的值
  • int k=*p      可以*p=k+1。

 对于int*p中p和*p看如下代码

int main()
{
	int i = 0;
	int*p = &i;
	printf("p=%p\n",p );  //这里的p是一个指针,指针里面是地址,所以p打印出来是地址。
	printf("*p=%d\n", *p);//int *p中*p可理解为是一个整体,所以*p可以理解为int类型的变量。
	                      //实际上*号是访问的指针中的那个地址,所以访问到的是指针中地址所对应变量的值,这个值就是上面变量i中的值1。
	
	return 0;
}

 通过*p我们访问到了p指针中地址所对应变量的值,然而访问就意味着可以读和写

读意味着读取到那个值

写意味着要改变那个值

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>

void f(int*p);
void g(int k);

int main(void)
{
	int i=6;
	printf("i=%d\n", i);
	printf("&i=%p\n", &i);
	f(&i);   //这条语句的意思就是把i的地址这个参数传递给f()函数。
	g(i);    //这条语句的意思是把i里面的值传递给g()函数。
	return 0;
}
//对比如下两个函数,一个为地址,一个为值。

void f(int*p)//这个函数需要的参数是一个地址
{
	printf("p=%p\n", p);//p是一个指针。
	printf("p=%d\n", *p);//*p是一个整数。
	*p = 26;
}

void g(int k)//这个函数的参数需要的是一个整形的值。
{
	printf("k=%d\n", k);
}

 传入地址

 int i;  scanf("%d",i);   为什么编译器没有报错呢?

因为& i 和 i 大小一样都是整形变量,所以编译器识别不出来。

没有用scanf(),没有用&符号,在编译时不会有错,但是在运行时会出错,因为scanf没有输入的值放到  i  里面去。

第三部分--为什么数组传进函数后的sizeof不对了

传入函数的数组变成了什么?

void minmax(int a[], int len, int *max, int *min);

int main()
{
    
    int a[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 12, 13, 14, 16, 17, 21, 23, 55 };
	int min, max;
    printf("main sizeof(a)=%lu\n", sizeof(a));
    return 0;
}

void minmax(int a[], int len, int*min, int*max)
{
	int i;
	printf("minmax sizeof(a[])=%lu\n", sizeof(a));
	
}

上面的程序运行之后会发现在main函数中运行的sizeof(a)为68,而在minmax函数中运行打印的sizeof(a)为4。

  • 函数参数表中的数组实际上是指针。
  • sizeof(a)=sizeof(int*);
  • 但是可以用数组的运算符[ ]进行运算。这句话意思是,int sum(int *ar,int n)与int sum(int ar[])是等价的。

函数中数组参数等价如下所示

 上述四种函数原型,在参数表中出现的话就是等价的。

 数组变量其实是特殊的指针

  • 数组变量本身表达地址,我们去取数组的地址,可以不用加&符号,直接拿数组变量的名字就可以得到数组的地址。
  • 因此我们取数组地址时可以不加&符号,int a[10];  int*p=a;中a无需加*符号。
  • 但是数组的单元表达的是变量,需要用&取地址。
  • 因为a[0]的地址与a数组的地址是一样的,所以a=&a[0];这里也可以看出a数组可以不用&,而a[0]是变量需要用&。
  • [ ]运算符可以对数组做也可以对指针做。p[0]<==>a[0]。
  • *运算符可以对指针做,也可以对数组做。*a=25。
  • 数组变量是const的指针,所以不能被赋值。所以两个数组之间不能做赋值。

字符类型

第一部分--字符类型

char是一种整数,也是一种特殊的类型:字符。

  • 用单引号表示的字符的字面量,’a‘,'1'。
  • ' '也是一个字符
  • printf和scanf里面用%c来输入输出字符

 字符的输入与输出

 如何输入’1‘这个字符给char c?

scanf("%c",&c);

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>

int main()
{
	char c;
	scanf("%c", &c);
	printf("c=%d\n", c);
	printf("c='%c'\n", c);
	return 0;
}

 '1'的ascll编码是49,你把它当成整数的时候它是49,你把他当成字符的时候他是1。

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>

int main()
{
	if (49 == '1'){
		printf("ok\n");
	}
	return 0;
}

混合输入

  空格要慎用,因为空格的ascll码值为32。带空格和不带空格是不一样的。

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>

int main()
{
	int i;
	char c;
	scanf("%d%c", &i, &c);
	printf("i=%d,c=%d,c='%c'\n", i, c, c);
	return 0;
}

这个程序运行起来比较有意思。在scanf()函数中如果输入两个以上的数字或字符,在scanf中是怎么输入的,在屏幕上就要怎么输入,如%d%c(没有空格),在屏幕上输入时也不要有空格,12c,这个输出结果是符合我们的预期的。如果中间有空格,屏幕输入时也要有空格。

 第二部分--逃逸字符

字符串

第一部分--字符串

字符数组

字符数组和字符串是不一样的,区别就在于,字符数组不能用字符串的方式去做运算,就是字符的数组。

char word[ ]={'H',’e','l','l','o','!'};这就是一个字符数组。

字符串

char word[ ]={'H',’e','l','l','o','!','\0'};这就是一个字符数串。字符串末尾‘\0’这其实就是0,不同的是加上单引号占用一个字节,不加单引号占用4个字节。可以直接写为0。字符串本身也是字符数组。一位末尾多了一个\0\;所以就变成字符串了。

字符串变量的几种形式:

1>char *str="hello";   2>char word[ ]="hello";3>char line[10]="10";

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>

int main()
{
	printf("请分别输入身高的英尺,"
		"如输入\"5 7\"表示5英尺7英寸");//这里面的\"表示转义字符。
	//C语言中如果两个字符之间上上述情况,会把这两句话连接起来。
	return 0;
}

C语言的字符串是以字符数组的形态出现的,不能用运算符对字符做运算,通过数组的方式可以遍历字符串。唯一特殊的地方是字符串字面量可以用来初始化字符数组。

第二部分--字符串变量

字符串常量

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>

int main()
{
	char*s = "hello word";
	char*s2 = "hello word";

	printf("s=%p\n", s);
	printf("s2=%p\n", s2);

	return 0;
}

 这两个指针指向了同一个地址。

char*s="hello,word!";

  • s是一个指针,初始化为指向一个字符串常量。
  • 由于这个常量所在的地方,所以实际上s是const,char*s,但是由于历史原因,编译器接受不带const的写法。
  • 但是试图对s所指的字符串做写入会导致严重的后果
  • 如果需要修改字符串,应用数组:char s[ ]="hello,word!"

 做一下对比

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>

int main()
{
	char*s = "hello word";
	char*s2 = "hello word";
	char s3 = "hello,word";

	printf("s=%p\n", s);
	printf("s2=%p\n", s2);
	printf("s3=%p\n", s3);

	return 0;
}

当我们要做一个字符串的时候,我们到底是把它写成指针的形式还是数组的形式。

  • char*str="hello";
  • char word[ ]="hello";

 如果写成数组的形式,它表示说这个字符串就在这里。作为本地变量空间会自动会被回收

如果是作为指针:这个字符串不知道在哪里,我们通常用来只读,不会去写他。如果是函数的参数,在数组哪里我们已经知道,数组作为函数的参数,实际上它和指针是一样的,这个时候不用就用指针来表示函数的参数,反正进来的都是指针。

如果要构造一个字符串——>数组

如果要处理一个字符串——>指针

char*是不是字符串?答案是不一定

 符串计算

第一部分--字符串输入输出

char string[8];

scanf("%s",string);

printf("%s",string);

  • scanf读入一个单词(到空格,tab或回车为止)
  • scanf是不安全的,因为不知道要读入的内容的长度
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>

int main(void)
{
	char word[8];
	scanf("%7s", word);
	printf("%s\n", word);
	return 0;
}

安全的输入

  • char string[8];
  • scanf("%7",string);
  • 在%和s之间的数字表示最多允许读入的字符的数量,这个数字应该比数组的大小小一
  • 下一次scanf从哪里开始?

空字符串

  • char buffer[100]=" ";
  • 这是一个空的字符串,buffer[0]=='\0'
  • char buffer[ ]=" "
  • 这个数组的长度只有1。

第二部分--字符串函数

字符串函数头文件string.h

  • strlen
  • strcmp
  • strcpy
  • strcat
  • strchr
  • strstr

使用这些函数时要包含一个头文件,#include<string.h> 

strlen函数:

size-t strlen(const char*s) ,  返回s的字符串长度(不包括结尾的0)

#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>

int main(void)
{
	char line[] = "hello";//我们用数组的方式这了一个字符串。
	printf("strlen=%lu\n", strlen(line));//这个函数是用来求字符串长度。
	printf("sizeof(line)=%lu\n", sizeof(line));//这条语句时这个字符数组占了多少空间。
	
	return 0;
}

 strcmp函数:

  • int strcmp(const char *s1,const char*s2);
  • 比较两个字符串,返回:
  • 0:s1==s2
  • 1:   s1>s2
  • -1:  s1<s2
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>

int main(void)
{
	char s1[] = "abc";
	char s2[] = "abc";
	printf("%d\n", strcmp(s1, s2));

	return 0;
}

当s1与s2相等时输出0;当s1>s2时输出1;当s1<s2时输出-1。

strcpy函数

  • char*strcpy(char*restrict dst,const char*restrictsrc);
  • 把src的字符串拷贝到dst
  • restrict表明src和dst不重叠
  • 返回dst
  • 为了能链起代码来

strcat函数

  • cahr*strcat(char*restrict s1,const char*restrict s2);
  • 把s2拷贝到s1的后面,接成一个长的字符串
  • 返回s1
  • s1必须具有足够的空间

strcpy和strcat都可能会出现安全问题。就是目的地没有足够的空间?

上面的代码是安全版本,相比stecpy,这里的strncpy中多了一个n这个n表示可以拷贝的字符数量。

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值