C primer plus (更新中)
C语言的学习笔记
1.1C语言概述总结
1.int main()中int表明main()函数返回一个整数,viod 表明main()不带任何参数。、
2./* */中间加注释(可以多行注释),“//"也可以注释。
3.对于变量命名可以用小写,大写字母,数字,下划线来命名,名称第一个字符必须是字母或下划线,不能是数字。操作系统和C库中常使用一个或者两个下划线作为开始,所以最好避免这样命名
4.函数由函数头和函数体结合,函数头:函数名,传入该函数的信息类型和函数返回类型。函数体:语句,声明。
一个简单的C程序格式如下:
#include <stdio.h>
int main(void)
{
语句
return 0;
}
5.程序需要有可读性
6.如何调用函数
#include <stdio.h>
void butler(void);
int main(void)
{
printf("I will summon the bulter function.\n");
butler();
pritnf("Yes.Bring me some tea and writeable DVDs.\n");
return 0;
}
void butler(void)
{
printf("You rang ,sir?\n");
}
1.2数据和c的总结
1.最小的存储单位是位(bit)。
2字节(byte)是常用的计算机存储单位。
3.一个字长有8位,字长越大,其数据转移越快。
4.0X或0x表示十六进制值。
5.以十进制显示数字,使用%d。以八进制显示数字,使用%o。以十六进制显示数字,使用%x。
要显示各进制数的前缀0,0x,必须分别使用%#o,%#x
#include <stdio.h>
int main(void)
{
int x = 100;
printf("dec = %d;octal = %o;hex = %x\n",x,x,x);
printf("dec = %d; octal = %#o; hex = %#x\n",x,x,x);
return 0;
}
运行结果:dec = 100;octal = 144;hex = 64
dec = 100;octal = 0144;hex = 0x64
6.打印unsigned int类型的值,使用%u;打印long类型的值,使用%ld。
在x和o之前可以使用前缀l,%lx表示以十六进制格式打印long类型整数,%lo表示以八进制格式打印long类型整数。
使用单引号括起来的单个字符被称为字符常量,而用双引号括起来的为字符串。
char bro;//声明一个char类型的变量
bro = 'T';//为其赋值,该T为字符,且在ASCII中有相对应的值
bro = T;//此时T为变量
bro = T;//此时T为字符串
printf函数使用%c打印字符,使用%f打印浮点数,用e打印指数记数法的浮点数、
使用sizeof可以知道当前系统的指定类型的大小是多少
#include <stdio.h>
int main(void)
{
pritnf("Type int has a size of %zd bytes.\n",sizeof(int));
pritnf("Type char has a size of %zd bytes.\n",sizeof(char));
pritnf("Type long has a size of %zd bytes.\n",sizeof(long));
return 0;
}
运行结果;Type int has a size of 4 bytes
Type char has a size of 1 bytes
Type long has a size of 8 bytes
把一个类型的数值初始化给不同类型时,编译器会把其转换成匹配的数值
int cost = 12.99;
float pi = 3.1415926;
C语言编译器会把cost的浮点数值转换成整型,会直接截掉小数点后面的部分,并且不进行四舍五入。
*而第二个会丢失精度,因为float只能保证前6位的精度。
2.1printf()和scanf()笔记
1.printf函数
%c单个字符 %d十进制整数 %e浮点数用e计数 %f浮点数 %p指针
2. 格式字符串中的转换说明一定要与后面的每个项目匹配
3.sizeof运算符以字节为单位返回类型或值的大小
4.例如%2d就是两个字符宽度,在转换说明前加数字是表示最小宽度
5.对于.数字来说就是表示数字的精度。
6.数组由连续的存储单元组成,c语言用\0来表示字符串的结束。
include<stdio.h>
#define PRAISE "You are an extraordinary being"
int main(void)
{
char name[40];
printf("What's your name?");
scanf("%s",name);
printf("Hello, %s. %s\n",name,PRAISE);
return 0 ;
}
结果What's your name?Angela Plains
Hello,Angela。You are an extraordinary being
scanf只读取了Angela Plains的Angela 。它在遇到第一个空白后就不在读取了。根据%s转换说明,scanf只会读取字符串中的一个单词,而不是一整句。
7.strlen函数给出字符串的字符长度
8.#define 可以定义字符和字符串常量 字符串用双引号,字符用单引号
2.2scanf函数的运用
1.如果scanf读取基本变量类型的值,在变量名前加上一个&
如果scanf把字符串读入字符数组中,不用使用&
例子:
include <stdio.h>
int main
{
int age ;
float assets;
char pet[30];
printf("Enter your age ,assets,and favorite pet.\n");
scanf("%d %f", &age ,&assets);//这里使用&
scanf("%s",pet);//字符数组不使用&
return 0;
}
2.scanf的转换说明和修饰符跟printf几乎一样。
3.scanf会跳过空白字符去读取字符,scanf读取句子时只会提取第一个单词。
3. 运算符,表达式和语句
3.1基本运算符
赋值运算符 “=”
1.=将右边的值赋予给左边
2.左值用于标识可修改的对象。右值指的是能赋给可修改的左值的量,且本身不是左值。
加法运算符
1.加法运算符用于加法运算,使其两侧的值相加。
减法运算符
1.减法运算符用于减法运算,使其左侧的数减去右侧的数
除法运算符
1.整数除法会截断计算结果的小数部分,不会四舍五入结果。混合整数和浮点数计算的结果是浮点数。
3.2其他运算符
1.求模运算符
求模运算符就是求余数。例如13%5的余数为3
2.递增运算符“++”
“++”有两种模式,第一种是前缀模式++出现在其作用的变量前面。第二种是后缀模式++出现在其作用的变量后面。
在复杂情况下前后缀的作用不一样
例如:
#include <stdio.h>
int main(void)
{
int a =1 ,b=1;
int a_post , pre_b;
a_post = a++;
pre_b = ++b;
printf("a a_post b pre_b \n");
printf("%ld %5d %5d %5d\n",a ,a_post , b , pre_b);
return 0;
}
运行结果:
a a_post b pre_b
2 1 2 2
a_post=a的初始值,则a=a+1;pre_b=++b的值。
前后缀对左值的赋值顺序不一样。
3.递减运算符“——”
递减运算符的运算方法与递增运算符一样
4.优先级
递增和递减运算符的优先级很高所以x**y++是(x)乘(y++)
3.3表达式和语句,类型转换
3.3.1表达式和语句
1.C把末尾加上一个分号的表达式看作是一条语句
2.序列点是程序执行的点:在该点上,所有副作用都在进入下一步之前发生。在一个语句中,赋值运算符,递增运算和递减运算符对运算对象做的改变必须在程序执行下一条语句之前完成。
3.序列点有助于分析后缀递增何时发生
例如:
while(guess++<10)
{
printf("%d\n",guess);
}
先使用值,再递增它
3.3.2类型转换
1.强制类型转换:再某个量前用圆括号括起来的类型名。
例;
mice = (int)1.6+(int)1.7
因为强制转换为int所以mice=1+1=2.
4. 循环
4.1while循环
/*summing .c*/
#include <stdio.h>
int main(void)
{
long num;
long sum = 0L;
int status;
printf("Please enter an integer to be summed");
printf("(q to quit):");
status = scanf("%ld",&num);
while (status ==1)
{
sum = sum +num;
printf("Please enter next integer (q to quit):");
status = scanf("%ld",&num);
}
printf("Those integers sum to %ld.\n",sum);
return 0;
}
while (status ==1)
{
sum = sum +num;
printf("Please enter next integer (q to quit):");
status = scanf("%ld",&num);
}
对于这个while函数的循环结束是通过scanf函数的返回值来判断的。
1. scanf 函数的返回值反映的是按照指定的格式符正确读入的数据的个数。
如果输入数据与指定格式不符,则会产生输入错误。遇到输入错误,scanf函数会立即终止,返回已经成功读取的数据的个数。
printf("(q to quit):");
status = scanf("%ld",&num);
2.如上代码中不仅仅可以使用q来停止还可以使用其他字母符号,因为scanf()函数有返回值且为int型。
3.在while循环语句后面没有花括号的时候,while语句只作用在它后面的一句话。
例:
while (n<3)
printf("n is %d\n",n);
n++;
printf("That's all this program does\n");
while循环只作用在printf("n is %d\n",n); n++并没有用。
4.2用关系运算符和表达式比较大小
关系表达式:依赖测试表达式作比较的循环程序
关系运算符:出现在关系表达式中间的运算符
C中所有关系运算符如下:
<---小于 <=----小于或等于 ==-----等于 >=-----大于或等于 >-----大于 !=-----不等于
下列while程序中就包含关系表达式:
while (ch !='$')
{
count++;
scanf("%c",&ch);
}
上述程序还可以用于比较字符,使用的是机器字符码。(不能用关系运算符比较字符)
还需注意:关系运算符可以用来比较浮点数,但比较时尽量只使用<和>。因为浮点数的舍入误差会导致逻辑上相等的俩数不相等。使用fabs()函数可以更方便的比较浮点数,会返回一个浮点数的绝对值。
// cmpflt.c -- 浮点数比较
#include <math.h>
#include <stdio.h>
int main(void)
{
const double ANSWER = 3.14159;
double response;
printf("What is the value of pi?\n");
scanf("%lf", &response);
while (fabs(response - ANSWER) > 0.0001)
{
printf("Try again!\n");
scanf("%lf", &response);
}
printf("Close enough!\n");
return 0;
}
上述程序会一直提示用户输入,直到用户输入的值与正确值之间相差0.0001
4.1.1 什么是真
在c中,表达式一定有一个值(包括关系表达式)
/* t_and_f.c -- C中的真和假的值 */
#include <stdio.h>
int main(void)
{
int true_val, false_val;
true_val = (10 > 2); // 关系为真的值
false_val = (10 == 2); // 关系为假的值
printf("true = %d; false = %d \n", true_val, false_val);
return 0;
}
4.1.2 其他真值
// truth.c -- 哪些值为真
#include <stdio.h>
int main(void)
{
int n = 3;
while (n)
printf("%2d is true\n", n--);
printf("%2d is false\n", n);
n = -3;
while (n)
printf("%2d is true\n", n++);
printf("%2d is false\n", n);
return 0;
}
关系式为真,求值为1;关系式为假,求值为0.
4.1.3 真值的问题
// trouble.c -- 误用=会导致无限循环
#include <stdio.h>
int main(void)
{
long num;
long sum = 0L;
int status;
printf("Please enter an integer to be summed ");
printf("(q to quit): ");
status = scanf("%ld", &num);
while (status = 1)
{
sum = sum + num;
printf("Please enter next integer (q to quit): ");
status = scanf("%ld", &num);
}
printf("Those integers sum to %ld.\n", sum);
return 0;
}
该程序会一直提示内容直到强行关闭。
原因是该程序改动了while循环的测试条件,把status == 1替换成 status = 1。后者是一个赋值表达式语句。导致while (status = 1)实际上相当于while (1),循环不会退出。
一定要注意==和=
4.1.4 新的_Bool类型
布尔变量:表示真或假的变量。
Bool是和C语言中布尔变量的类型名。这个类型的变量只能储存1(真)或0(假)。其他非零数值赋给Bool变量将会被设置为1.(体现C把所有非零值都视为真)
// boolean.c -- 使用_Bool类型的变量 variable
#include <stdio.h>
int main(void)
{
long num;
long sum = 0L;
_Bool input_is_good;
printf("Please enter an integer to be summed ");
printf("(q to quit): ");
input_is_good = (scanf("%ld", &num) == 1);
while (input_is_good)
{
sum = sum + num;
printf("Please enter next integer (q to quit): ");
input_is_good = (scanf("%ld", &num) == 1);
}
printf("Those integers sum to %ld.\n", sum);
return 0;
}
如果系统不支持Bool类型,导致无法运行该程序,可以把Bool替换成 int
4.1.5 优先级和关系运算符
关系运算符的优先级比算术运算符低,比赋值运算符高。
关系运算符之间有两种不同的优先级。
高优先级组: <<= >>=
低优先级组: == !=
关系运算符的结合律也是从左往右
4.3 不确定循环和计数循环
不确定循环:在测试表达式为假之前,预先不知道要执行多少次循环。一些wwhile循环就是不确定循环。
计数循环:在执行循环之前就知道要重复执行多少次。
// sweetie1.c -- 一个计数循环
#include <stdio.h>
int main(void)
{
const int NUMBER = 22;
int count = 1; // 初始化
while (count <= NUMBER) // 测试
{
printf("Be my Valentine!\n"); // 行为
count++; // 更新计数
}
return 0;
}
在创建一个重复执行固定次数的循环中涉及了3个行为:
1.必须初始化计数器;
2.计数器与有限的值作比较;(测试)
3.每次循环时递增计数器。(更新)
4.4 for循环
for循环将for循环把上述3个行为(初始化、测试和更新)组合在一处。
// sweetie2.c -- 使用for循环的计数循环
#include <stdio.h>
int main(void)
{
const int NUMBER = 22;
int count;
for (count = 1; count <= NUMBER; count++)
printf("Be my Valentine!\n");
return 0;
}
上述程序用for循环修改了6.4中的程序
for后圆括号内的三个表达式分别代表了初始化、测试、更新。同时,这三个表达式也叫做控制表达式。(控制表达式:为完整表达式且表达式副作用发生在对下一个表达式求值之前)
4.4.1 利用for的灵活性
for循环的灵活性源于如何使用循环中的三个表达式。
for循环的几种用法:
1.可以使用递减运算符来递减计数器
/* for_down.c */
#include <stdio.h>
int main(void)
{
int secs;
for (secs = 5; secs > 0; secs--)
printf("%d seconds!\n", secs);
printf("We have ignition!\n");
return 0;
}
2.可以让计数器递增2、10等
/* for_13s.c */
#include <stdio.h>
int main(void)
{
int n; // 从2开始,每次递增13
for (n = 2; n < 60; n = n + 13)
printf("%d \n", n);
return 0;
}
3.可以用字符代替数字计数
/* for_char.c */
#include <stdio.h>
int main(void)
{
char ch;
for (ch = 'a'; ch <= 'z'; ch++)
printf("The ASCII value for %c is %d.\n", ch, ch);
return 0;
}
注意:该循环本质上还是用整数来计数的。(因为字符在内部是以整数形式储存)
4.可以让递增的量几何增长,而不是算术增长
/* for_geo.c */
#include <stdio.h>
int main(void)
{
double debt;
for (debt = 100.0; debt < 150.0; debt = debt * 1.1)
printf("Your debt is now $%.2f.\n", debt);
return 0;
}
5.可以省略一个或多个表达式(但是不能省略分号),只要在循环中包含能结束循环的语句即可。
/* for_none.c */
#include <stdio.h>
int main(void)
{
int ans, n;
ans = 2;
for (n = 3; ans <= 25;)
ans = ans * n;
printf("n = %d; ans = %d.\n", n, ans);
return 0;
}
注意:在执行循环的其他部分之前,只对第1个表达式求值一次或执行一次。
6.循环体中的行为可以改变循环头中的表达式
小结
for循环的形式:
for ( initialize; test; update )
statement
注:initialize初始化 test测试 update更新
在test为假或0之前,重复执行statement部分。
4.5逗号运算符
#include<stdio.h>
int main(void)
{
const int FIRST_OZ = 46;
const int NEXT_OZ = 20;
int ounces,cost;
printf("ounce cost\n");
for(ounce = 1,cost = FIRST_OZ;ounces <=16;ounces++,cost +=NEXT_OZ)
printf("%5d $%4.2f\n",ounces , cost/100.0);
return 0;
}
该程序在for循环初始化表达式和更新表达式中使用了逗号运算符。初始化表达式中的逗号使ounces和cost都进行了初始化,更行表达式中的逗号使每次迭代ounces递增1,cost递增20.
4.6do while出口条件循环
出口条件循环是在循环的每次迭代之后检查测试条件,保证了至少执行循环体中的内容一次。
do while循环通用形式
do
statement
while(expression);
要在while后面加分号;
4.7如何选择循环
1.确定是需要入口循环还是出口循环。大多数情况下使用入口循环。
对于入口循环看个人需求使用for或者while
一般而言对于循环涉及初始化和更新变量时使用for循环比较合适。
4.7.1嵌套循环
嵌套循环常用于按行列显示数据,一个循环处理行,一个循环处理列。
5.分支与跳转
5.1逻辑运算符
&&:与
||:或
!:非
&&:一假都假,都真则真
||:一真都真
if else语句
if else的通用语句:
if(expresssion)
statement
else
statement
5.2if语句的嵌套
1.如果要在if和else之间执行多条语句,必须用花括号把这些语句括起来 成为一个块。
因为在if和else之间只允许有一 条语句(简单语句或复合语句)
2.在编写程序的代码之前要先规划好。首先,要总体设计一下程序。
3.为方 便起见,程序应该使用一个循环让用户能连续输入待测试的数。
这样测试 一个新的数字时不必每次都要重新运行程序。
if else语句作为一条单独的语句,不必使用花括号。
外 层if也是一条单独的语句,也不必使用花括号。
但是,**当语句太长时,使用 花括号能提高代码的可读性,而且还可防止今后在if循环中添加其他语句时 忘记加花括号。
5.3else if
if()
else
if()
else
这种形式等于
if()
else if()
if()
else if()
else()
5.4if与else配对
1.如果if与else之间无花括号,则if与其最近的else配对
5.5条件运算符:?
x= (y<0)?-y:y;
这个表达式的意思是如果y<0,则x=-y;否则x=y。
条件表达式的通用形式如下:
expression1 ? expression2 : expression3
如果 expression1 为真(非 0),那么整个条件表达式的值与 expression2 的值相同;如果expression1为假(0),那么整个条件表达式的值与 expression3的值相同。
5.6continue和break
continue
while (scanf("%f", &score) == 1)
{
if (score < MIN || score > MAX)
{
printf("%0.1f is an invalid value.Try again: ",score);
continue; // 跳转至while循环的测试条件
}
}
break
如果break语句位于嵌套循环内,它只会影响包含它的当前循环。
5.7switch和break
switch在圆括号中的测试表达式的值应该是一个整数值(包括char类 型)。case标签必须是整数类型(包括char类型)的常量或整型常量表达式 (即,表达式中只包含整型常量)。不能用变量作为case标签。
switch ( 整型表达式)
{
case 常量1:
语句 <--可选
case 常量2:
语句 <--可选
default : <--可选
语句 <--可选
}
作业
对字符输入\输出函数:getchar()和putchar()
ch = getchar()的效果和scanf(“%c”,&ch)一样。
putchar(ch)的效果和printf(“%c”,ch);
因为这两个函数只处理字符,所以他们比scanf和printf函数更快,更简洁。
6. 字符输入/输入输出验证
6.1输入验证
暂时不怎么会。😂😂😂😂😂
6.2getchar和putcahr
6.3缓冲区
缓冲区分为完全缓冲i/o和行缓冲i/o。完全缓冲输入指的是当缓冲 区被填满时才刷新缓冲区(内容被发送至目的地),通常出现在文件输入 中。缓冲区的大小取决于系统,常见的大小是 512 字节和 4096字节。行缓 冲I/O指的是在出现换行符时刷新缓冲区。键盘输入通常是行缓冲输入,所 以在按下Enter键后才刷新缓冲区。
6.3.1流
C程序处理的是流而不是直接处理文件。流(stream)是 一个实际输入或输出映射的理想化数据流。这意味着不同属性和不同种类的 输入,由属性更统一的流来表示。于是,打开文件的过程就是把流与文件相 关联,而且读写都通过流来完成。
stdin流表示键盘输入,stdout流表示屏幕输出。getchar()、putchar()、 printf()和scanf()函数都是标准I/O包的成员,处理这两个流。
6.3.2文件结尾
在C语言中,用 getchar()读取文件检测到文件结尾时将返回一个特殊的值,即EOF(end of file的缩写)。scanf()函数检测到文件结尾时也返回EOF。
通常, EOF定义 在stdio.h文件中: #define EOF (-1)
为什么是-1?因为getchar()函数的返回值通常都介于0~127,这些值对 应标准字符集。但是,如果系统能识别扩展字符集,该函数的返回值可能在 0~255之间。无论哪种情况,-1都不对应任何字符,所以,该值可用于标记 文件结尾。
/* echo_eof.c -- 重复输入,直到文件结尾 */
#include <stdio.h>
int main(void)
{
int ch;
while ((ch = getchar()) != EOF)
putchar(ch);
return 0;
}
变量ch的类型从char变为int,因为char类型的变量只能表示0~255的无 符号整数,但是EOF的值是-1。还好,getchar()函数实际返回值的类型是 int,所以它可以读取EOF字符。如果实现使用有符号的char类型,也可以把 ch声明为char类型,但最好还是用更通用的形式。
由于getchar()函数的返回类型是int,如果把getchar()的返回值赋给char类 型的变量,一些编译器会警告可能丢失数据。
用 getchar()处理字符输入,用 scanf()处理数值输入,这两 个函数都能很好地完成任务,但是不能把它们混用。因为 getchar()读取每个 字符,包括空格、制表符和换行符;而 scanf()在读取数字时则会跳过空格、 制表符和换行符。
作业截图
7. 函数
函数(function)是完成特定任务的独立程序代码 单元。语法规则定义了函数的结构和使用方式。
一般而言,函数原型指明了函数的返回值类型和函数接受的参数类型。 这些信息称为该函数的签名(signature)。
首先函数头包括函数类型、 函数名和圆括号,接着是左花括号、变量声明、函数表达式语句,最后以右 花括号结束
形参。和定义在函数中变量一样,形式 参数也是局部变量,属该函数私有。这意味着在其他函数中使用同名变量不 会引起名称冲突。每次调用函数,就会给这些变量赋值。
当函数接受参数时,函数原型用逗号分隔的列表指明参数的数量和类 型。
形式参数是被调函数(called function)中 的变量,实际参数是主调函数(calling function)赋给被调函数的具体值.
实际参数是出现在函数调用圆括号中的表达式。形式参数是函数定义的 函数头中声明的变量。调用函数时,创建了声明为形式参数的变量并初始化 为实际参数的求值结果。
声明函数时必须声明函数的类型。带返回值的函数类型应该与其返回值 类型相同,而没有返回值的函数应声明为void类型。
函数类型指的是返回值的类 型,不是函数参数的类型。
作业
7.1编译多源代码文件的程序
使用多个函数最简单的方法是把它们都放在同一个文件中,然后像编译只有一个函数的文件那样编译该文件即可。
把函数原型和已定义的字符常量放在头文件中是一个良好的编程习惯
如果把main()放在第1个文件中,把函数定义放在第2个文件中,那么第1个文件仍然要使用函数原型。把函数原型放在头文件中,就不用在每次使用函数文件时都写出函数的原型
程序中经常用C预处理器定义符号常量。把#define 指令放进头文件,然后在每个源文件中使用#include指令包含该文件即可。
C语言的两个规则:从左往右对逻辑表达式求值;一旦求值结果为假,立即停止求值
用不同的函数处理不同的任务时应检查数据的有效性
7.2查找地址:&运算符
指针用于储存变量的地址。
如果主调函数不使用return返回的值,则必须通过地址才能修改主调函数中的值。
一元&运算符给出变量的存储地址。如果pooh是变量名,那么&pooh是变量的地址。
8. 数组和指针
8.1函数,数组,指针
int sum(int * ar, int n) // 更通用的方法
{
int i;
int total = 0;
for (i = 0; i < n; i++) // 使用 n 个元素
total += ar[i]; // ar[i] 和 *(ar + i) 相同
return total;
}
int *ar形式和int ar[]形式都表示ar是一个指向int的指针。
ar[i] 和 (ar + i) 相同
// sum_arr1.c -- 数组元素之和
// 如果编译器不支持 %zd,用 %u 或 %lu 替换它
#include <stdio.h>
#define SIZE 10
int sum(int ar[], int n);
int main(void)
{
int marbles[SIZE] = { 20, 10, 5, 39, 4, 16, 19, 26,
31, 20 };
long answer;
answer = sum(marbles, SIZE);
printf("The total number of marbles is %ld.\n", answer);
printf("The size of marbles is %zd bytes.\n",
sizeof marbles);
return 0;
}
int sum(int ar[], int n) // 这个数组的大小是?
{
int i;
int total = 0;
for (i = 0; i < n; i++)
total += ar[i];
printf("The size of ar is %zd bytes.\n", sizeof ar);
return total;
}
该程序的输出如下:
The size of ar is 8 bytes.
The total number of marbles is 190.
The size of marbles is 40 bytes.
注意,marbles的大小是40字节。这没问题,因为marbles内含10个int类 型的值,每个值占4字节,所以整个marbles的大小是40字节。但是,ar才8字 节。这是因为ar并不是数组本身,它是一个指向 marbles 数组首元素的指 针。我们的系统中用 8 字节储存地址,所以指针变量的大小是 8字节(其他 系统中地址的大小可能不是8字节)。简而言之,在程序清单10.10中, marbles是一个数组, ar是一个指向marbles数组首元素的指针,利用C中数组 和指针的特殊关系,可以用数组表示法来表示指针ar。
8.2指针操作
指针求差:可以计算两个指针的差值。通常,求差的两个指针分别指向 同一个数组的不同元素,通过计算求出两元素之间的距离。差值的单位与数 组类型的单位相同。例如,程序清单10.13的输出中,ptr2 - ptr1得2,意思是 这两个指针所指向的两个元素相隔两个int,而不是2字节。 只要两个指针都 指向相同的数组(或者其中一个指针指向数组后面的第 1 个地址),C 都能 保证相减运算有效。如果指向两个不同数组的指针进行求差运算可能会得出 一个值,或者导致运行时错误。
作业截图
9.字符串和字符串函数
9.1 表示字符串和字符串函数I/O
// strings1.c
#include <stdio.h>
#define MSG "I am a symbolic string constant."
#define MAXLENGTH 81
int main(void)
{
char words[MAXLENGTH] = "I am a string in an array.";
const char * pt1 = "Something is pointing at me.";
puts("Here are some strings:");
puts(MSG);
puts(words);
puts(pt1);
words[8] = 'p';
puts(words);
return 0;
}
和printf()函数一样,puts()函数也属于stdio.h系列的输入/输出函数。但 是,与printf()不同的是,puts()函数只显示字符串,而且自动在显示的字符 串末尾加上换行符。
运算结果: Here are some strings:
I am an old-fashioned symbolic string constant.
I am a string in an array.
Something is pointing at me.
I am a spring in an array.
9.2在程序中定义字符串
1.双引号中的字符和编译器自动加入末尾的\0字 符,都作为字符串储存在内存中,所以"I am a symbolic stringconstant."、"I am a string in an array."、"Something is pointed at me."、"Here are some strings:"都是字符串字面量。
2.从ANSI C标准起,如果字符串字面量之间没有间隔,或者用空白字符 分隔,C会将其视为串联起来的字符串字面量。
例如: char greeting[50] = "Hello, and"" how are" " you" " today!";
与下面的代码等价: char greeting[50] = "Hello, and how are you today!";
3.如果要在字符串内部使用双引号,必须在双引号前面加上一个反斜杠 (\): printf(""Run, Spot, run!" exclaimed Dick.\n"); 输出如下: "Run, Spot, run!" exclaimed Dick.
4.字符串常量属于静态存储类别(static storage class),这说明如果在函 数中使用字符串常量,该字符串只会被储存一次,在整个程序的生命期内存 在,即使函数被调用多次。
5.用双引号括起来的内容被视为指向该字符串储存 位置的指针。这类似于把数组名作为指向该数组位置的指针。
例子:
/* strptr.c -- 把字符串看作指针 */
#include <stdio.h>
int main(void)
{
printf("%s, %p, %c\n", "We", "are", *"space farers");
return 0;
}
printf()根据%s 转换说明打印 We,根据%p 转换说明打印一个地址。因 此,如果"are"代表一个地址,printf()将打印该字符串首字符的地址(如果使 用ANSI之前的实现,可能要用%u或%lu代替%p)。最后,"space farers"表 示该字符串所指向地址上储存的值,应该是字符串"space farers"的首字 符。下面是该程序的输出: We, 0x100000f61, s
9.2.1字符串数组和初始化
1.const char * pt1 = "Something is pointing at me.";
该声明和下面的声明几乎相同: const char ar1[] = "Something is pointing at me.";
以上两个声明表明,pt1和ar1都是该字符串的地址。在这两种情况下, 带双引号的字符串本身决定了预留给字符串的存储空间。尽管如此,这两种 形式并不完全相同。
9.2.2数组和指针
1.通常在数组中,字符串都作为可执行文件的一部分储存在数据段中。当把 程序载入内存时,也载入了程序中的字符串。字符串储存在静态存储区 (static memory)中。但是,程序在开始运行时才会为该数组分配内存。此时,才将字符串拷贝到数组中。注意,此时字符串 有两个副本。一个是在静态内存中的字符串字面量,另一个是储存在ar1数 组中的字符串。此后,编译器便把数组名ar1识别为该数组首元素地址(&ar1[0])的别 名。这里关键要理解,在数组形式中,ar1是地址常量。不能更改ar1。可以进行类似 ar1+1这样的操作,标识数组的下一个元素。但是不允许进行++ar1这样的操 作。
2.对于指针数组(*pr1)来说,是可以运用递增运算符++pr1将指向数组中第二个元素。
总结
总的来说,普通数组是const数据,是无法修改的。而指针数组不是const数据,所以可以修改。 总之,初始化数组把静态存储区的字符串拷贝到数组中,而初始化指针 只把字符串的地址拷贝给指针.
https://gitee.com/gongjunfei666/source-code/blob/master/C primer11章数组指针
通过运行该代码,第一,pt和MSG的地址相同,而ar的地址不 同,这与我们前面讨论的内容一致。第二,虽然字符串字面量"I'm special"在程序的两个 printf()函数中出现了两次,但是编译器只使用了一个 存储位置,而且与MSG的地址相同。编译器可以把多次使用的相同字面量储存在一处或多处。另一个编译器可能在不同的位置储存3个"I'm special"。 第三,静态数据使用的内存与ar使用的动态内存不同。不仅值不同,特定编 译器甚至使用不同的位数表示两种内存。
总的来说,普通数组的内存分配有两个步骤,在程序运行前使用静态储存。在程序开始运行后,使用动态内存分配,导致地址不同。而指针数组是直接为数组分配固定位置,而且可以改变地址指向。
9.2.2.1 数组和指针的区别
1.初始化字符数组来储存字符串 char heart[]="I love mater",初始化指针指向字符串 const char *head="I love mike"
这二者的区别在于前者的heart是常量(不可修改的左值),而后者的head是变量(可修改的左值)
9.2.2.2 字符串数组
1.如果用常规的多维数组来表示多个字符串就会大大的增加内存空间,因为字符串中每个字符都需要空间。而使用指针数组,那每个字符串的地址就会被储存,这样就会大大降低内存使用率。
综上所述,如果要用数组表示一系列待显示的字符串,请使用指针数组,因为它比二维字符数组的效率高。但是,指针数组也有自身的缺点。指针数组中的指针指向的字符串字面量不能更改;而多维数组中的内容 可以更改。所以,如果要改变字符串或为字符串输入预留空间,不要使用指 向字符串字面量的指针。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 我与微信审核的“相爱相杀”看个人小程序副业
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 如何使用 Uni-app 实现视频聊天(源码,支持安卓、iOS)
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)