一、指针
视频1:取地址运算:&运算符取得变量的地址
sizeof()
//sizeof
#include<stdio.h>
int main()
{
int a;
a=6;
printf("sizeof(int)=%ld\n",sizeof(int)); //sizeof(double)
printf("sizeof(a)=%ld\n",sizeof(a)); //sizeof(a)
printf("sizeof(double)=%ld\n",sizeof(double));//sizeof(int)
return 0;
}
运算符&
- scanf("%d",&i);里的&
- 获得变量的地址,他的操作数必须是变量
- int i;printf("%x",&i);
int main(void)
{
int i = 0;
printf("0x%x\n", &i); //用%x表示十六进制
return 0;
}
运行结果,需要注意的是,取地址变量在每台PC和每个编译器的结果都是不一样的,我们不必太过关注每次结果不同
0x62feac
老师视频这里解释他编译器这里有报错,上面的%x需要替换成%p,我用的Dev C++没有报错,这里不再继续探究,该地方跳过,如果报错,记的选择32位编译
#include <stdio.h>
int main(void)
{
int i = 0;
int p;
p = (int)&i; //需要(int)&强制转换
printf("0x%x\n", &p);
printf("%p\n", &i);
return 0;
}
强制转换后的运行结果
0xbfff5d6c
0xbfff5d6c
上面在编译器里用32位架构编译的结果,老师换成64位再编译,因为强制转换,没有报错。
在64位架构下用sizeof()求int 与&i 大小,分别为4字节和8字节
在32位架构下用sizeof()求int 与&i 大小,分别为4字节和4字节
从这里可以看出&可以给我们取出一个变量的地址,从而可以得出,我们需要printf输出一个地址时,需要的是%p,地址和整数是不同的
地址的大小是否与int相同取决于编译器
int i; printf("%p",&i);
&不能取的地址
· &不能对没有地址的东西去地址
· 如&(a+b)
· 如&(a++)
· 如&(++a)
#include <stdio.h>
int main(void)
{
int i = 0;
int p;
p = (int)&i;
// p = (int)&(i+p);
// p = (int)&(i++);
// p = (int)&(++i);
printf("0x%x\n", &p);
printf("%p\n", &i);
return 0;
}
把上面三个注释分别去掉,得出的报错信息,三个报错类型基本一致的,都是提示不能取这样的值
D:\C\指针.cpp:8:16: error: lvalue required as unary ‘&’ operand p =
(int)&(i+p);
在取地址符的右边必须有一个明确的变量才能取他的地址。
其他类型的&
· 变量的地址
· 相邻的变量的地址
· &的结果的sizeof
· 数组的地址
· 数组单元的地址
· 相邻的数组单元的地址
// 变量的地址
#include <stdio.h>
int main(void)
{
int i = 0;
int p;
printf("%p\n",&i); // 变量的地址
printf("%p\n", &p); // 相邻变量的地址
printf("%p\n",sizeof(int)); // sizeof(int)地址
return 0;
}
执行结果
0061FEAC
0061FEA8
00000004
C在16进制代表的是12,从这里可以看出AC和A8中间差了4位,从上面得知int占据的是4个字节,因此他们是相邻的内存地址,在堆栈里自顶向下分配的,他们的差距是4,刚好等于sizeof(int)
// 数组的地址
#include <stdio.h>
int main(void)
{
int a[10];
printf("&a=%p\n",&a); // a交给取地址符
printf("a=%p\n", a); // a直接当一个地址
printf("a[0]=%p\n",&a[0]); // 获取a[0]的地址
printf("a[1]=%p\n",&a[1]); // 获取a[1]的地址
return 0;
}
执行结果,从这里可以看出相邻单元的数组差距,永远是4
&a =0061FE88
a =0061FE88
a[0] =0061FE88
a[1] =0061FE8C
视频2:指针_指针变量就是记录地址的变量
scanf函数
-
如果能够将取得的变量的地址传递给一个函数,能否通过这个地址在那个函数内访问这个变量
-
scanf("%d", &i)
-
scanf()的原型应该是怎样的?我们需要一个参数能保存别的变量的地址,如何表达能够保存地址的变量?
scanf()是C语言中的一个输入函数。与printf函数一样,都被声明在头文件stdio.h里,因此在使用scanf函数时要加上#include <stdio.h>。(在有一些实现中,printf函数与scanf函数在使用时可以不使用预编译命令#include <stdio.h>。)它是格式输入函数,即按用户指定的格式从键盘上把数据输入到指定的变量之中。
什么样的类型,可以接收取地址得到的地址?
指针
· 指针类型的变量就是保存地址的变量
int i;
int* p = &i; //“*”,指针符号,*p,表示p是一个指针
int* p,q; //q只是一个普通的int类型变量
int *p,q; //无论*号靠近int还是有空格,意义一样,都表示p是一个指针,指向int
指针变量
· 变量的值是内存的地址
· 普通变量的值是实际的值
· 指针变量的值是具有实际值的变量地址
作为参数的指针
- void f ( int *p)
- 在被调用的时候得到了某个变量的地址:
- int i = 0; f ( &i );
- 在函数里面可以通过这个指针访问外面的这个i
void f(int *p);
f函数是它是一个int类型的指针,在我们去调用这个f时,就需要给它一个地址,而不能给一个变量本身或者是这个变量的值int i=0;f(&i);
用&符号取得变量i的地址,将这个地址赋予指针p
#include<stdio.h>
void f(int *p);
int main(void)
{
int i = 6;
printf("&i=%p\n", &i);// 打印出i的地址
f(&i);// 将i的地址赋予f
return 0;
}
void f(int *p)
{
printf(" p=%p\n", p); // 打印出p的值,看他们是否相等
}
运行结果相同。
这里看出,在main里有一个变量i=6,它的地址是 9C ,我们将9C这个地址取出来赋予了另外一个变量p,p的值是70,同时可以理解为p是一个指针,指向了变量i
有了上面的情况后,在f函数里面,我们有外面的main里面的i的地址,我们不知道它是i,我么只知道它的地址
如果不传地址进去,只得到它的值会是什么情况,看看下面的例子,新增g 函数
#include<stdio.h>
void f(int *p);
void g(int k); // 新增g函数
int main(void)
{
int i = 6;
printf("&i=%p\n", &i); // 打印出i的地址
f(&i); // 将i的地址赋予f
g(i); // 将i的值赋予g函数
return 0;
}
void f(int *p) // 将*p的值赋予f
{
printf(" p=%p\n", p); // 打印出p的值,看他们是否相等
}
void g(int k) //
{
printf(" k=%d\n", k); //打印出g,g得到的是i的值,
}
在g函数里,k取到的值k=i=6,k和外面的i没有任何关系,这是之前函数课程里的内容,现在我们通过指针变量p得到了i的地址,这使得f函数里面拥有能够访问外面那个函数的地址的能力
访问那个地址上的变量*
- *是一个单目运算符,用来访问指针的值所表示的地址上的变量
- 可以做右值也可以做左值
- int k = *p;
- *p = k + 1;
#include<stdio.h>
void f(int *p);
void g(int k); // 新增g函数
int main(void)
{
int i = 6;
printf("&i=%p\n", &i); // 打印出i的地址
f(&i); // 将i的地址赋予f
g(i); // 将i的值赋予g函数
return 0;
}
void f(int *p) // 将*p的值赋予f
{
printf(" p=%p\n", p); // 打印出p的值,看他们是否相等
printf("*p=%d\n",*p); // 打印*p的值,将*p看成一个整数的整体
*p = 26;
}
void g(int k) //
{
printf(" k=%d\n", k); //打印出g,g得到的是i的值
}
输出结果
&i=0061FE9C
p=0061FE9C
*p=6
k=26
从这里看出,i的值被f函数调用后被改变了
我们在*p=26;时,也可以理解为是对i进行了改变,这就是指针
**指针*左值之所以叫左值
· 是因为出现在赋值号左边的不是变量,而是值,是表达式计算的结果:
· a[0] = 2;// 数组,取下标运算符,a[0]是运算结果
· *p = 3; // 是运算符,p是取得p的指针的地址所代表的变量,表达是运算的结果
· 指针是特殊的值,所以叫做左值
*指针的运算符&
· 互相反作用
· *&yptr -> *(&yptr) -> *(yptr的地址) ->得到地址上的变量 -> yptr
· &yptr -> &(yptr) -> &(y) ->得到y得地址,也就是yptr -> yptr
传入地址
· 为什么
· int i;scanf("%d",i);
· 当我们用scanf时,忘记&时编译没有报错,运行起来就会犯错(因为scanf把读进来的数字写到了不该写的地方)
视频3:指针与数组:为什么数组传进函数后sizeof不对了
传入函数的数组成了什么?
· 当我们向一段函数传入数组变量,参数接收到的是值,如果我们传入指针时,参数接收到的是也是值(一个地址);
· 如果传入函数的是一个普通变量,它接收到的是一个值
· 如果传入函数的是一个指针,它接收到的也是一个值(地址)
· 如果传入的是一个数组,它接收到的是什么?
#include <stdio.h>
void minmax(int a[], int len, int *min, int *max);
int main(void)
{
int a[] = {1,2,3,4,5,6,7,8,9,12,13,14,16,17,21,23,55,};
int min,max;
minmax(a,sizeof(a)/sizeof(a[0]),&min,&max);
printf("min=%d,max=%d\n",min,max);
return 0;
}
void minmax(int a[], int len, int *min, int *max)
{
int i;
*min = *max =a[0];
for (i=1;i<len;i++){
if(a[i]<*min){
*min =a[i];
}
if(a[i]>*max){
*max =a[i];
}
}
}
分别在main函数和minmax函数里打印出sizeof(a)
的大小
#include <stdio.h>
/*
取出一组数组里的最小的和最大的值
*/
void minmax(int a[], int len, int *min, int *max);
int main(void)
{
int a[] = {1,2,3,4,5,6,7,8,9,12,13,14,16,17,21,23,55,};
int min,max;
/*[Error] cannot convert 'const char*' to 'int*' for argument '1' to 'void minmax(int*, int, int*, int*)'*/
minmax("main sizeof(a)=%lu\n",sizeof(a)); // main调用之前再看下minmax的大小
minmax(a,sizeof(a)/sizeof(a[0]),&min,&max);
printf("min=%d,max=%d\n",min,max);
return 0;
}
void minmax(int a[], int len, int *min, int *max)
{
int i;
printf("minmax sizeof(a)=%lu\n",sizeof(a)); //输入minmax的大小
*min = *max =a[0];
for (i=1;i<len;i++){
if(a[i]<*min){
*min =a[i];
}
if(a[i]>*max){
*max =a[i];
}
}
}
main函数里,
sizeof(a)=68
minmax函数里,
sizeof(a)=4
注意:我的编译器这里会报错,而老师的只是一个警告([Error] cannot convert 'const char’ to ‘int*’ for argument ‘1’ to ‘void minmax(int*, int, int*, int*)’)*
无法将参数’1’的’const char’转换为’int*‘传入’void minmax(int*, int, int*, int*)’*
看到警告或者报错后,我们再试一段代码
#include <stdio.h>
/*
取出一组数组里的最小的和最大的值
*/
void minmax(int a[], int len, int *min, int *max);
int main(void)
{
int a[] = {1,2,3,4,5,6,7,8,9,12,13,14,16,17,21,23,55,};
int min,max;
printf("main a=%p\n",a);
minmax(a,sizeof(a)/sizeof(a[0]),&min,&max);
printf("min=%d,max=%d\n",min,max);
return 0;
}
void minmax(int a[], int len, int *min, int *max)
{
int i;
printf("minmax a=%p\n",a);
*min = *max =a[0];
for (i=1;i<len;i++){
if(a[i]<*min){
*min =a[i];
}
if(a[i]>*max){
*max =a[i];
}
}
}
行结果
main a=0062FE44
minmax a=0062FE44
将查看a的大小语句改为查看a的指针地址,两个值获取是一样的
问题来了
从这里可以看出maina[]其实就是minmax里的a[]
在这里我们再将minmax函数里的a[0]=1000来看看结果,再回到main里时,它会发生怎样的改变
#include <stdio.h>
#include <stdio.h>
/*
取出一组数组里的最小的和最大的值
*/
void minmax(int a[], int len, int *min, int *max);
int main(void)
{
int a[] = {1,2,3,4,5,6,7,8,9,12,13,14,16,17,21,23,55,};
int min,max;
printf("main a=%p\n",a);
minmax(a,sizeof(a)/sizeof(a[0]),&min,&max);
printf("a[0]=%d\n",a[0]); //执行main后我们查看a[0]等于多少
printf("min=%d,max=%d\n",min,max);
return 0;
}
void minmax(int a[], int len, int *min, int *max)
{
int i;
printf("minmax a=%p\n",a);
a[0]=1000;
*min = *max =a[0];
for (i=1;i<len;i++){
if(a[i]<*min){
*min =a[i];
}
if(a[i]>*max){
*max =a[i];
}
}
}
main a=0062FE44
minmax a=0062FE44
a[0]=1000
min=2,max=1000
从这里可以看出来a[]它就是一个指针
刚才报错 main函数里获取sizeof(a)的个数,原因是因为a[]它是一个指针
现在我们将两个函数的a[]全部改为*a, 看看编译是否会通过
结果:编译通过,运行正确。
传入函数的数组成了什么?
· 函数参数表中的数组实际上就是指针
· sizeof(a) ==sizeof(int)
· 但是可以用数组的运算符[]进行运算*
数组参数
· 以下四种函数原型是等价的:
· int sum(int *ar,int n);
· int sum(int *,int);
· int sum(int ar[], int n);
· int sum(int[], int);
数组变量是特殊的指针
**· 数据变量本身表达地址,所以
· int a[10];int *p =a; //无需用 &取地址
· 但是数组的单元表达的是单个变量,对单个需要用&取地址
· a == &a[0]
· []运算符可以对数组做,也可以对指针做:
· p[0]=*p
· 运算符可以对指针做,也可以对数组做:
·a = 25
· 数组变量是 const的指针,所以不能被赋值,两个数组之前不能互相赋值
· int[a] <==> int * const a=…
#include <stdio.h>
/*
取出一组数组里的最小的和最大的值
*/
void minmax(int *a, int len, int *min, int *max);
int main(void)
{
int a[] = {1,2,3,4,5,6,7,8,9,12,13,14,16,17,21,23,55,};
int min,max;
printf("main a=%p\n",a);
minmax(a,sizeof(a)/sizeof(a[0]),&min,&max);
printf("a[0]=%d\n",a[0]); //执行main后我们查看a[0]等于多少
printf("min=%d,max=%d\n",min,max);
/* 验证p[0]=*p */
int *p= &min;
printf(" *p=%d\n", *p);
printf("p[0]=%d\n", p[0]);
/* 验证*a=a[0]*/
printf("*a=%d\n",*a);
return 0;
}
void minmax(int *a, int len, int *min, int *max)
{
int i;
printf("minmax a=%p\n",a);
a[0]= 1000;
*min = *max =a[0];
for (i=1;i<len;i++){
if(a[i]<*min){
*min =a[i];
}
if(a[i]>*max){
*max =a[i];
}
}
}
执行结果
main a=0062FE40
minmax a=0062FE40
a[0]=1
min=1,max=55
*p=1
p[0]=1
课后习题:
二、字符类型
视频1:字符类型
字符类型
- char是一种整数,也是一种特殊的类型——字符,因为:
- 用单引号表示的字符字面量’a’,‘2’
- ''也是一种字符
- printf和scanf可以用%c来输入输出字符。
字符的输入输出
- 如何输出'1'这个字符给char c?
#include<stdio.h>
int main(){
char c;
char d;
c=1;
d='1';
if(c==d){
printf("相等\n");
}else{
printf("不相等\n");
}
printf("%d",c);
printf("%d",d);
return 0;
}
不相等
1
49
字符的输入输出
- 如何输入'1'这个字符给char a?
- scanf("%c",&c);——> 1
- scanf("%d",&i);——> 49
#include<stdio.h>
int main(){
char c;
scanf("%c",&c);
printf("%d\n",c);
printf("%c\n",c);
return 0;
}
输入:1
输出:49
1
#include<stdio.h>
int main(){
int i;
char c;
scanf("%d",&i);
c=i;
printf("%d\n",c);
printf("'%c'\n",c);
return 0;
}
输入:1
输出:1
' '
- '1'的ASCII编码是49,所以当c==49时,它代表'1'
混合输入
- 有何不同
- scanf("%d %c",&i,&c);
- scanf("%d%c",&i,&c);
带不带空格
#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;
}
如果带了空格,他会读入一个整数,然后空格几个都不要紧,然后再读入一个字符。
如果没有空格,他就会把空格当作下一个字符读进去。
字符计算
- 一个字符加一个数字得到ASCII码表中的那个数之后的数字
- 两个字符的减,得到它们在表中的距离
大小写转换
- 字母在ASCII表中是顺序排列的
- 大写字母和小写字母是分开排列的,并不在一起
- 'a'-'A'可以得到两端之间的距离,于是
- a+'a'-'A'可以把一个大写字母变成小写字母
- a+'A'-'a'可以把一个小写字母变成大写字母
视频2:逃逸字符
\t
\b
三、字符串
视频1:字符串
字符数组
如果我们定义了这样的一个字符数组
char word[] = {'H','e','l','l','o','!'};
word[0] | H |
word[1] | e |
word[2] | l |
word[3] | l |
word[4] | o |
word[5] | ! |
{}大括号是用来初始化这个数组,这样的字符数组里有很多的字符连起来,但是它不是C语言的字符串,因为不能用字符串的方式做运算。它只是字符数组
那么如何定义一个字符串了,我们则需要这样
char word[] = {'H','e','l','l','o','!','\0'};
word[0] | H |
word[1] | e |
word[2] | l |
word[3] | l |
word[4] | o |
word[5] | ! |
word[6] | \0 |
我们在初始化的最后加上了\0,这个\0就是一个C语言的字符串,可以进行运算
字符串
· 以0(整数0)解维的一串字符
· 0或’\0’是一样的,但是和’0’不同,单引号里的0表达的是asc码里面的 0, 而\0它是整数0
· 0 是用来标值字符串的结束,但是它本身不是字符串的一部分
· 计算字符串长度的时候不包括这个0
· 字符串以数组的形式存在,访问时,它可以是数组,也可以是指针
· 更多时候访问是以指针的形式访问
· string.h 里有很多处理字符串的函数
字符串变量
字符串变量写法的的表现形式,通常有以下几种
· char *str = "Hello"; // str指针指向一个字符数组,里面放的内容是“Hello".
· char word[] = "Hello";
· char line[10] = "Hello"; //有数组line,有10个字节大小,
//在里面放了“Hello”.占据6个位置,因为字符串结尾还有个0(编译器自动生成)
字符串常量
· “Hello”
· "Hello"会被编译器变成一个字符数组放在内存里,这个数组的长度是6,结尾还有表示结束的0
· 在C语言中,如何有两个相连的字符串,并没有任何结束时,会自己将他们联系起来
字符串
· C语言的字符串是以字符数组的形态存在的
· 不能用运算符对字符串做运算
· 通过数组的方式可以遍历字符串
· 唯一的特殊地方是字符串字面量可以用来初始化字符数组
· 标准库提供了一系列字符串函数
视频2:字符串变量
字符串常量
char* s = "Hello,world!"
#include<stdio.h>
int main(void) {
char *s = "Hello World";
s[0] = 'B';
printf("Here!s[0]=%c\n", s[0]);
return 0;
}
我在这里编译时会报错,老师这里竟然时通过的,网上查了下,有两种方案,有几种方案,我是用了const 改变为常量
[Warning] deprecated conversion from string constant to ‘char*’ [-Wwrite-strings]
#include<stdio.h>
int main(void) {
const char *s ="Hello World";
//s[0] = 'B';
const char *s2 ="Hello World";
printf("s=%p\n", s);
printf("s2=%p\n", s2);
printf("Here!s[0]=%c\n", s[0]);
return 0;
}
输出结果:
s=00404000
s2=00404000
Here!s[0]=H
从这里我们可以看出,他们在内存中的地址是一样的,我们再添加一个i
#include<stdio.h>
int main(void) {
int i=0;
const char *s ="Hello World";
//s[0] = 'B';
const char *s2 ="Hello World";
printf("i=%p\n", &i);
printf("s=%p\n", s);
printf("s2=%p\n", s2);
printf("Here!s[0]=%c\n", s[0]);
return 0;
}
i=0062FE94
s=00404000
s2=00404000
Here!s[0]=H
这里可以看出i的地址比s和s2的地址要大很多,i是本地变量,它比较大,而s和s2很小它们存放在内存中的代码段
并且是只读属性,所以刚才我们那里有报错,因为是只读的不可修改的
字符串常量
char* s =“Hello World”;
· s是一个指针,初始化为指向一个字符串常量;
· 由于这个常量所在的地方,实际上s是const char* s,但是由于历史原因,IDE接受不带const的写法
· 但是视图对s所指的字符串写入,会导致严重后果
· 如果想要修改字符串,应该用数组
char s[ ] =“Hello World”;
下面我们来试试
#include<stdio.h>
int main(void) {
int i=0;
const char *s ="Hello World";
//s[0] = 'B';
const char *s2 ="Hello World";
char s3[] = "Hello World";
printf("i=%p\n", &i);
printf("s=%p\n", s);
printf("s2=%p\n", s2);
printf("s3=%p\n", s3);
s3[0] = 'B';
printf("Here!s3[0]=%c\n", s3[0]);
return 0;
}
输出结果:
i=0062FE94
s=00404000
s2=00404000
s3=0062FE88
Here!s3[0]=B
我们看到s3也在一个比较大的地方,同时也是可以修改的
那么,当我们需要一个字符串的时候,我们该如何写了?
指针还是数组
· char *str = “Hello”;
· char word[] = “Hello”;
· 数组:这个字符串在内存固定区域地址
· 作为本地变量空间会被自动回收的
· 指针:这个字符串不知道在内存那一块
· 处理参数
· 动态分配内存空间
从这里可以得出:
如果要构造一个字符串,需要用数组
如果要处理一个字符串,需要用指针
char*是不是字符串
· 字符串可以表达char*的形式
· char*不一定是字符串
· 本意是指向字符的指针,可能指向的是字符的数组(就像int*一样)
· 只有它所指的字符数组有结尾的\0,才能说明它所指的是字符串
四、字符串计算
视频1:字符串输入与输出
字符串赋值?
- char *t = "title";
- char *s;
- s = t;
- 并没有产生新的字符串,只是让指针s指向了t所指的字符串,对s的任何操作就是对t做的
字符串的输入与输出
- char string[8];
- scanf("%s",string);
- printf("%s",string);
- scanf读入一个单词(到空格,tap或回车为止)
- scanf是不安全的,因为不知道要读入的内容长度
#include <stdio.h>
int main(void)
{
char word[8];
char word2[8];
scanf("%s",word);
scanf("%s",word2);
printf("%s##%s##\n", word,word2);
return 0;
}
安全的输入
- char string[8];
- scanf("%7s",string);
- 在%和s之间的数字表示最多允许读入的字符的数量,这个数字应该比数组的大小小一些
- 下一次的scanf从哪里开始?
常见错误
- char *string;
- scanf("%s",string);
- 以为char*是字符串类型,定义了一个字符串类型的变量string就可以直接使用了
- 由于没有对string初始化,所以不一定每次运行都出错
空字符串
- char butter[100] = “ ”;
- 这是一个空的字符串,butter[0] == '\0'
- char butter [] = " ";
- 这个数组的长度只有1!