《C Primer Plus》 学习笔记系列之(一)


第一章

编程步骤,不是直接就写代码:
1、定义程序目标
2、设计该程序
3、编写程序
养成在编写代码时先进行规划的习惯。使用古老而可敬的笔记技术来略记下程序的目标,并勾勒出设计概貌,

最终会节约一定的时间。


第二章c语言概述

操作系统和c库通常使用以一个或两个下划线开始的名字,所以在你的程序中最好避免这种用法。

第四章字符串的格式化输入/输出

sizeof()

c没有为字符串专门定义变量类型。'x'和”x”是不一样的。

预定义常量:
#define PI 3.1415926
#define ADDRESS “chongqing”

printf(“Here's a way to print a \
long string!”);

在两个转换说明之间放一个空白字符,可以确保即使一个数字溢出了自己的字段,它也不会闯入下一个数字一起输出。
printf(“%9d %9d %9d\n”, val1, val2, val3);

如果在语句中要嵌入一个数字,那么指定一个和期望的数字宽度同样小或者更小的字段宽度通常会比较方便。
printf(“Count Jack ran %.2f miles in 3 hours.\n”);

stype.h 里面有很多字符系列函数

尽量避免使用goto语句。

第八章字符输入/输出和输入确认

while ((ch = getchar()) != '#')
{
      putchar(ch);
}

将若干个字符作为一个块传输比逐个发送这些字符耗费的时间少。

缓冲输入。
非缓冲输入。
缓冲分为两类:完全缓冲(fullybuffered)I/O和行缓冲(linebuffered)I/O。对完全缓冲输入来说,缓冲区满时被清空(内容被发送至目的地)。这种类型的缓冲通常出现在文件输入中。缓冲区的大小取决于系统,但512字节和4096字节是常见的值。对行缓冲I/O来说,遇到一个换行字符时将清空缓冲区。键盘输入是标准的行缓冲,因此按下回车键将清空缓冲区。

从概念上说,c程序处理一个流而不是直接处理文件。流(sream)是一个理想化的数据流,实际输入或输出映射到这个数据流。于是打开文件的过程就成为将流与文件相关联,并通过流进行读写的过程。c对待输入和输出设备与其对待存储设备上的普通文件相同。键盘输入由一个被称为stdin的流表示,而到屏幕(或电传打字机、或其他输出设备)上的输出由一个被称为stdout的流表示。

getchar()和scanf()函数在文件结尾时都返回EOF,通常EOF在stdio.h中定义。

#include<stdio.h>
void main()
{
     char ch;
     while ((ch = getchar()) != EOF)
     {
           putchar(ch);
     }
}


编译运行这个程序,得到可执行文件a.out。
创建一文本文档,words
you are so beautiful
terrific
excellent
when I was young

输入重定向:
$ ./a.out < words
<该运算符把words文件与stdin流关联起来,将该文件的内容引导至a.out程序。改程序a.out所知道的就是向他传送了一个字符流,所以将这些字符读出并一次打印一个字符,知道遇到文件结尾。由于c将文件和I/O设备置于相同的地位,所以现在这个文件就是I/O设备。

输出重定向:
$./a.out > mywords
c程序将输入视为一个外来字节的流。getchar()函数将每个字节解释为一个字符编码。scanf()函数以同样的方式看待输入。通常,许多程序使用getchar()来逐个字符地读取输入。通常,系统使用行缓冲输入(line-bufferedinput),这意味着输入的内容在你按下回车键时被传输给程序。按下回车键的同时还将传输一个编程时需要注意的换行字符。ANSIC把缓冲输入作为标准。

第九章函数

#include<stdio.h>
int imax();
int main()
{
      printf("The maximum of %d and %d is %d.\n", 3, 5,imax(3));
      printf("The maximum of %d and %d is %d.\n", 3, 5,imax(3.0, 5.0));
      return 0;
}
int imax(n, m)
int n, m;
{
    if (n > m)
    {
        return n;
    }
    else
    {
        return m;
    }
}
在我的32位linux中运行结果:
The maximum of 3 and 5 is 3.
The maximum of 3 and 5 is 1074266112.
调用函数首先把参数放在一个称为堆栈的临时存储区域里,然后被调函数从堆栈中读取这些参数。调用函数根据调用过程中的实际参数确定需要传递的数值类型,但是被掉函数是根据形式参数进行数据读取的。因此,函数调用imax(3)把一个整数放在堆栈中。当imax开始执行时,他会从堆栈中读取两个整数。而实际上只有一个需要的的数值被存储在堆栈中,所以第二个读出的数据就是恰好在堆栈中的其他数值。
第二次使用函数imax()时,传递的是floar类型的数值。这时两个double类型的数值就被放在堆栈中(作为参数传递时,float类型的数据会转换成double类型)。而在我们的系统中,这意味着两个64位的数值,即共128位的数据存储在堆栈中。因为这个系统中的int类型是32位,所以当imax()从堆栈中读取两个int类型的数值时,他会读出堆栈中的64位的数据,把这些数据对应于两个整数,其中较大的一个就是1074266112.。

所以函数的声明最好是:
int imax(int, int); or
int imax(int n, int m);

将一个十进制的数转换为二进制:
#include<stdio.h>
void to_binary(unsigned long number);
int main()
{
      unsigned long number;
      printf("Please enter a integer(q to quit):\n");
      while (scanf("%ul", &number) == 1)
      {
          printf("Binary equivalent:\n");
          to_binary(number);
          putchar("\n");
          printf("Please enter a integer(q to quit):\n");
     }
     return 0;

}

void to_binary(unsigned long number)
{
     int r;
     r = number % 2; //取模,偶数的二进制的最后一位是0,奇数的二进制的最后一位是1
     if (r >= 2)
     {
         to_binary(number / 2);
     }
    putchar('0' + r);  //转化为一个字符输出
}

把函数原型和常量定义放在一个头文件中是一个很好的习惯。

while ((status = scanf(“%d”, &code)) != 1 || (code < 1|| code > 5))
{
     //只有code正确输入了,才检查code的值
     if (status != 1)
     {
          scanf(“%*s”); //跳至下一个空白字符
          printf(“Please enter an integer from 1 to 5.\n”);
     }

}

第十章数组和指针

float a[n]; //c99之前不允许这样声明数组
#include<stdio.h>
#define MONTHS 12
int main()
{
     int days[MONTHS]; //但允许这样声明
     return 0;
}

指针提供了一种用来使用地址的符号方法。
数组名同时也是数组首元素的地址。
例如:inta[5];
a == &a[0];

对指针加一,等价于对指针的值加上它所指向的对象的字节大小。
int date[10];
int * dates;
dates + 2 == &date[2];
*(dates + 2) == date[2];

total += *start++; *和++具有相同的优先级,先把指针指向的数据加到total上,然后指针加1
total += (*start)++; 会先使用start指向的数据,然后该数据加1

ar[i]和*(ar+i)是等价的。
ar+4等价于&ar[4].

int *pt;
*pt = 5; //pt没有初始化,因此pt的值是随机的,不知道5会存储到什么位置。

const的使用:
1、double rates[5] = {34.45, 56.89, 12.10, 13.04, 12.04};
const double * pd = rates; //const在前不允许改变数组中的每一个值。
*pd = 33.05; //不合法
pd[2] = 22.01; //不合法
pd = &rates[2]; //合法

2、double * const pc = rates; //const在后面pc指向数组开始处,使用const来声明并初始化指针,以保证指针不会指向别处。
pc = &rates[2]; //不允许
*pc = 32.08; //允许

3、可以使用两个const来创建指针,这个指针既不可以更改所指向的地址,也不可以修改所指向的数据:

const double * const pc = rates;

指针和多维数组:
int zippo[4][2], 
 1、zippo是数组首元素的地址,zippo == zippo[0] == &zippo[0][0];
 2、zippo+1和zippo[0] + 1结果不一样
 3、*(zippo[0]) == zippo[0][0], 而*zippo代表其首元素zippo[0]的值,即&zippo[0][0]的值。
    所以**zippo == *&zippo[0][0]。也就是zippo是地址的地址,需要两次取值才可以得到通常的值。
    zippo[1][2] = *(*(zippo+1)+2)
    int是4字节的长的,zippo[0]指向4字节长的数据对象,对zipppo[0]加1导致他的值增加4。数组名zippo是包含两个int数的数组的地址,因此
    他指向8字节长的数据对象,故对zippo加1导致他的值z增加8.

那如何来声明指向二维数组的指针,
首先,指向int的指针只能和zippo[0]兼容,因为他们都指向一个单个int值。而zippo是其首元素的地址,而该元素有时包含两个int值的数组。
因此pz必须指向一个包含两个int值的数组,而不是一个单个int值。正确的代码是:
 int (*pz)[2];  //pz指向一个包含2个int值的数组
使用圆括号的原因是[]的优先级高于*, 如果是int * pz[2], 表示pz是两个指针组成的数组。
zippo[m][n] = *(*(zippo+m)+n)

int * pt;        //pt指向一个int数值
int (*pa)[3];    //pa指向由3个int值构成的数组
int ar1[2][3];    //ar1指向由3个int值构成的数组
int ar2[3][2];    //ar2指向由2个int值构成的数组的指针,简单的说就是指向int[2]的指针,
int **p2;
如下赋值:
pt = &ar1[0][0];  //都指向int
pt = ar1[0];      //都指向int
pt = ar1;    //非法
pa = ar1;    //都指向int[3]
pa = ar2;    //非法
p2 = &pt;    //都指向int *
*p2 = ar1[0];   //都指向int
p2= ar2;      //非法

int * p1;
const int * p2;
const int ** pp2;
p1 = p2;       //非法,把const指针赋值给非const指针
p2 = p1;    //合法,把非const指针赋值给const指针,赋值的前提是只有一层间接运算。
pp2 = &p1;  //非法,把非const指针赋值给const指针,

二维数组在函数中的形参:
现有这样的一个二维数组,
int junk[3][4] = {
{3, 4, 5, 9},
{6, 7, 8, 10},
{11, 12, 13, 14}
};
分别计算其行的和、列的和、总和。
#include<stdio.h>
#define ROWS 3
#define COLS 4
void sum_rows(int ar[][COLS], int rows);   //二维数组的三种原型声明方式
void sum_cols(int [][COLS], int);
void sum2d(int (*ar)[COLS], int rows);
int main()
{
    int junk[3][4] = {
            {3, 4, 5, 9},
            {6, 7, 8, 10},
            {11, 12, 13, 14}
    };
    sum_rows(junk, ROWS);
    sum_cols(junk, ROWS);
    sum2d(junk, ROWS);
    return 0;
}

//计算行的和
void sum_rows(int ar[][COLS], int rows)
{
    int i, j, tol;
    for (i = 0; i < rows; i++)
    {
        tol = 0;
        for (j = 0; j < COLS; j++)
        {
            tol += ar[i][j];
        }
        printf("Row %d: sum = %d\n", i, tol);
    }
}

//计算列的和
void sum_cols(int ar[][COLS], int rows)
{
    int i, j, tol;
    for (i = 0; i < COLS; i++)
    {
        tol = 0;
        for (j = 0; j < rows; j++)
        {
            tol += ar[j][i];
        }
        printf("Col %d: sum = %d\n", i, tol);
    }
}

//计算总和
void sum2d(int ar[][COLS], int rows)
{
    int i, j, sum = 0;
    for (i = 0; i < rows; i++)
    {
        for (j = 0; j < COLS; j++)
        {
            sum += ar[i][j];
        }
    }
    printf("Sum is %d\n", sum);
}
int sum2(int ar[3][4], int row);  //合法声明,但3将被忽略


  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值