C++学习笔记(一)——C语言基础知识

一、易忘点

1.输出字符用putchar(ch),输出字符串用printf()。二者功能不可互相替代。

2.目前接触的基本都是静态数组,即声明方式为a[10]之类。与动态数组声明方式不同。静态数组可以修改数组的数据。

3.格式控制字符串说明:

  • %X:以十六进制数形式输出整数(%X跟%x是输出十六进制数字,%X是输出的十以上的字母大写,%x是输出十以上的字母小写)。
  • %c:单个字符
  • %d:十进制整数
  • %f :十进制浮点数
  • %o:八进制数
  • %s :字符串
  • %u:无符号十进制数(%o 八进制、%x 十六进制)(欸,和int的一样。其实就是一样的)
  • %%:输出百分号%
  • %lu:32位无符号整数(%lo 八进制、%lx 十六进制)
  • %llu:64位无符号整数
  • %ld:long类型的输入输出(%lo 八进制、%lx 十六进制)(欸,和32位无符号的一样。其实就是一样的)
  • %e:按指数的形式输出,比如 4.22e5
  • %g:输出实数,它根据数值的大小,自动选f格式或e格式(选择输出时占宽度较小的一种),且不输出无意义的0。即%g是根据结果自动选择科学记数法还是一般的小数记数法
  • %p:指针的值是一个表示地址空间中某个存储器单元的整数。printf函数族中对于%p一般以十六进制整数方式输出指针的值,附加前缀0x。

 

例如:

#include
int main()
{
    int x = 5342;
    int *p = NULL;
    p = &x;
    printf("If I know the name of the variable, I can get it's value by name: %d\n",x);
    printf("If I know the address of the variable is:%x, then I can also get it's value by address:%d\n",p,*p);
    return 0;
}

4.int和long区别

5.静态变量

全局变量都是静态变量。局部变量定义时如果前面加了“static”关键字,则该变量也成为静态变量。

静态变量的存放地址,在整个程序运行期间,都是固定不变的。

非静态变量(一定是局部变量)地址每次函数调用时都可能不同,在函数的一次执行期间不变。

如果未明确初始化,则静态变量会被自动初始化成全0(每个bit都是0,局部非静态变量的值则随机。

注:静态变量不是指值不发生变化。

6.变量名、函数名、类型名统称为“标识符”。一个标识符能够起作用的范围 ,叫做该标识符的作用域。

7.所谓变量的“生存期”,指的是在此期间,变量占有内存空间,其占有的内 存空间只能归它使用,不会被用来存放别的东西。全局变量的生存期,从程序被装入内存开始,到整个程序结束;静态局部变量的生存期,从定义它语句第一次被执行开始,到整个程序结束为止。

8.%3d--可以指定宽度,不足的左边补空格 %-3d--左对齐 %03d---一种左边补0 的等宽格式,比如数字12,%03d出来就是: 012

9.单行注释从“//”开始直到行末为止;多行注释用“/*”和“*/”包围起来。

10.在C99 中,double的输出必须用%f,而输入需要用%lf,但是在C89和C++中都不必如此——输入输出可以都用%lf。

11.想输出\n,可以printf("\\n"),想输出%d,可以printf("%%d")。

12.int型最小值:-2147483648;最大值:2147483647

13.double是浮点数,它的小数点的位置是“浮动”的,所以很难说double类型能精确到小数点后面几位。通常这个关于精度的问题都是通过它能表示的有效数字(十进制)的位数来表示的。遵循IEEE标准的8字节(64位)的double能表示的有效数字的位数是:15 ~ 16

14.函数floor(x)返回不超过x的最大整数。假设在经过大量计算后,由于误差的影响,整数1变成了0.9999999999,floor的结果会是0而不是1。为了减小误差的影响,一般改成四舍五入,即floor(x+0.5)(2)。如果难以理 解,可以想象成在数轴上把一个单位区间往左移动0.5个单位的距离。floor(x)等于1的区间 为[1,2),而floor(x+0.5)等于1的区间为[0.5,1.5)。

15.long long (其范围是-2^63~2^63-1) 在Linux下的输入输出格式符为%lld,但Windows平台中有时 为%I64d。

16.科学计数法用e表示时中间不能加空格,如“1e-6”,而不能是“1e  -  6”!

17.x = i ++;    //先让x变成i的值,再让i加1;  x = ++i;    //先让i加1, 再让x变成i的值

18.在C语言中,double型变量输入用%lf,输出用%f。

19.函数的声明可以在main()函数里面,而定义部分可以放在main()函数后面。

20.+-*/%都是双目运算符。双目操作符等号两边操作数类型要相同。

21.区分一下常量与静态变量。5,9这都是常量,静态变量

22.'\\'指的是'\','\/'指的是'/'。

23.

  • 其实,sscanf就是比scanf的括号最前面多了一个要输入的字符串,sprintf只是比printf括号最前面多了一个要输出的字符串,其他的其实没什么变化。
  • fprintf 和 fscanf 也是这样,只是前面多了一个文件指针,后面的还是一个格式字符串加一个输出表。

一*、胡晓雁的考点总结:

1.常量与变量

  • 常量:在程序运行过程中,其值不能被改变;
  • 变量:在程序运行过程中,其值可以被改变。
  • 常量:5 和 9是整型常量(整数)
  • float x:定义单精度浮点型变量
  • double area, length:定义双精度浮点型变量
  • C语言中的变量代表保存数据的存储单元
  • C89规定,在任何执行语句之前,在块的开头声明所有局部变量。 在C99以及C++中则没有这个限制,即在首次使用之前,可在块的任何位置都可以声明变量。
  • 变量必须先定义,后使用。应该先赋值,后引用。一个变量名只能定义一次

2.算术运算、赋值运算、关系运算:

  • 单目运算符: +,-,++,--,!,~,sizeof
  • sizeof用法:sizeof(a);sizeof(int);sizeof(double)
  • 双目算术运算符(五个):+ - * / %
  • 双目运算符两侧操作数的类型要相同
  • 赋值运算符=的左边必须是一个变量
  • 关系运算符 x < y, x > y, x < = y, x >= y x != y
  • 关系表达式:用关系运算符将2个表达式连接起来的式子。

3.软件测试:精心设计一批测试用例 [输入数据,预期输出结果] ,然后分别用这些测试用例运行程序,看程序的实际运行结果与预 期输出结果是否一致。

4.格式化输入:float型:%f;double型:%lf

5.

  • 库函数:C语言处理系统提供事先编好的函数,供用户在编程时调用。如:scanf(), printf(), exp() 。在相应的系统文件(头文件)中定义一些必需的信息。
  • #include命令:用户调用库函数时,将相应的头文件包含到源程序中。

6.常用数学库函数:

  • 平方根函数 sqrt(x)
  • 绝对值函数 fabs(x):fabs( -3.56) 的值为3.56
  • 幂函数 pow(x, n) 
  • 指数函数 exp(x) :
  • 以 e为底的对数函数 log(x)
  • 以10为底的对数函数 log10(x)

7.

  • 字符输入函数getchar():输入一个字符
    char ch; ch = getchar( );
  • 字符输出函数putchar():输出一个字符
    putchar(输出参数);
    输出参数可以是字符常量或字符变量

8.分支结构一般分为二分支和多分支两种结构。

9.

  • 统计一个整数的位数,别忘先把这个数变成非负数。用do...while可以考虑到0的情况。
  • 计算阶乘的时候,可以用double,别忘最后输出的时候用"%.f"就行。
  • 不返回运算结果的函数定义用void,不能省略。否则函数类型被默认定义为int。
  • 函数名的实参表可以含:常量、变量、表达式。

10.变量的作用域与生命周期:

  • 局部变量作用域:本函数内部;复合语句内部;
  • 全局变量作用域:从定义处到源文件结束(包括各函数)
  • 若局部变量与全局变量 同名,局部变量优先。明天一定会有题,用局部变量去覆盖全局变量。
  • 自动变量(auto): 普通的局部变量。如int x, y 写全就是 auto int x, y;char c1 写全即为 auto char c1。函数调用时,定义变量,分配存储单元。函数调用结束,收回存储单元。
  • 动态存储:自动变量;静态存储:全局变量、静态局部变量。用户的储存空间分为静态储存区和动态储存区。
  • 静态局部变量:作用范围等同于局部变量,生命周期等同于全局变量。

10.数据存储:

  • short [int] 与 unsigned short [int] 16位
  • long [int] 与 unsigned long [int] 32位
  • int 与 unsigned [int] 16或32位
  • 单精度浮点型 float 32位 (PS:实型就是浮点型)
  • 双精度浮点型 double 64位
  • char 8位
  • 实型常量的类型都是double

11.整数的表示三种表现形式:

  • 十进制整数:正、负号,0-9,首位不是0
  • 八进制整数:正、负号,0-7,首位是0
  • 16进制整数:正、负号,0-9,a-f,A-F, 前缀是0x,0X

12.整数后的字母后缀

  • 123L long
  • 123U unsigned
  • 123LU unsigned long

13.实数的表示

  • 浮点表示法:0.123  123.4  12.  .12
  • 科学计数法:6.026E-27  1.2e+30  1E-5

14.转义字符

  • 反斜杠后跟一个字符或数字是字符常量,代表一个字符,所有字符都可以用转义字符表示。
  • \n 换行;\r 回车;\\ 反斜杠'\'
  • \ddd:八进制数ddd所代表的字符。
  • \xhh:十六进制数hh所代表的字符。

15.自动类型转换

  • 非赋值运算:从取值范围小的向取值范围大的转换。
  • 赋值运算:将赋值运算符右侧表达式的类型自动转换成赋值号左侧变量的类型
  • (double)(int)x:先把x强制转换为int型变量,然后再强制转化为double变量。

16.易忽略的优先级:

  • 移位运算(<<与>>)优先级低于算数运算(+-*/%),高于关系运算(>, >=, <, <=)
  • 赋值运算优先级极低,低于三目运算符(?:)
  • 关系运算符(>, >=, <, <=)高于 == 与 !=
  • 逗号运算符的优先级最低,左结合
  • (ch = getchar()) != '\n' 与 ch = getchar() != '\n' 不等价,一定要注意优先级。

17.数组

  • 数组长度:在C语言中,一定要是常量表达式。
  • 数组下标:整型表达式
  • 静态数组的初始化:static int b[5] = {1, 2, 3, 4, 5};静态存储的数组如果没有初始化,所有元素自动赋0
  • 动态数组的初始化:动态存储的数组如果没有初始化,所有元素为随机值;auto int c[5];
  • 针对部分元素的初始化(新发现):如 int a[10] = {1, 2}; 不论数组是静态还是动态的,不论数组是局部变量还是全局变量,没有被赋初值的后面剩余的元素都会被初始化为0;
  • 如果对全部元素都赋初值,可以省略数组长度:int a[] = {1, 2};
  • 二维数组的初始化(分行赋初值):int a[3][3] = {{1,2,3}, {4,5,6}, {7,8,9}};int b[3][3] = {{1,2,3},{},{1}};
  • 静态二维数组初始化(分行赋初值):static int b[4][3] = {{1,2,3}, { }, {4,5}};
  • 顺序赋初值:int a[3][3] = {1,2,3,4,5,6,7,8,9};static int b[4][3] = {1,2,3,0,0,0,4,5};
  • 省略行长度:对全部元素都赋了初值,如:int a[ ][3]={1,2,3,4,5,6,7,8,9};或是分行赋初值时,在初值表中列出了全部行 static int b[ ][3]={{1,2,3},{},{4,5},{}}

18.字符串数组

  • 0代表字符'\0',也就是ASCII码为0的字符
  • 字符串常量:用一对双引号括起来的字符序列及一个字符串结束符 '\0'。字符串的有效长度:有效字符的个数。
  • 数组长度 >= 字符串的有效长度 + 1
  • auto char s[80]= "Happy"; 字符串遇 '\0' 结束。第一个 '\0' 前面的所有字符和 '\0' 一起构成了字符串 "Happy”
  • 字符数组初始化:static char s[6] = "Happy";
  • printf("%s\n", "string"+1):说明字符串的名字也是地址。
  • 数组名是常量,不能对它赋值。char sa[10];  sa = “Hello”; 非法
  • 但是,char sa[10] = "Hello",就是合法的。
  • char *color[5] = {"red", "blue", "yellow", "green", "purple"};这样定义一个二维字符串数组。

19.排序

(1)选择排序法

for(int i = 0; i < n - 1; i++){
	int idx = i;
	for(int j = i + 1; j < n; j++){
		if(a[j] < a[idx]) idx = j;
	}
	int t = a[i]; a[i] = a[idx]; a[idx] = t;
}

(2)冒泡排序

for(int i = 1; i < N; i++){
	for(int j = 0; j < N - i; j++){
		if(a[j] > a[j + 1]){
			int t = a[j]; a[j] = a[j + 1]; a[j + 1] = t;
		}
	}
}

(3)冒泡法对二维字符数组排序:

for(int i = 1; i < N; i++){
	for(int j = 0; j < N - i; j++){
		if(strcmp(a[j], a[j + 1]) > 0){
			char* tmp = a[j];
			a[j] = a[j + 1];
			a[j + 1] = tmp;
		}
	}
}

20.指针

  • 直接访问:通过变量名访问
  • 间接访问:通过另一个变量访问把变量的地址放到另一变量中,使用时先找到后者,再从中取出前者的地址。
  • &*p 与 &a 相同,是地址。*&a 与 a 相同,是变量
  • (*p)++ 等价于 a++ 将 p 所指向的变量值加1
  • *p++ 等价于 *(p++) 先取 *p,然后 p 自加,此时p不再指向a
  • printf("%d\n", p2 - p1):指针p2和p1之间元素的个数
  • printf("%d\n", (int)p2 - (int)p1):指针p2和p1之间的字节数(强制转化为int的值,及转化为指针的地址值)。
  • 指针相加、相乘和相除,或指针加上和减去一个浮点数都是非法的。
  • p < q:两个相同类型指针可以用关系运算符比较大小
  • p + 1:指向下一个存储单元。

21.C语言中,在main()函数外声明函数和函数内声明函数有何区别?

  • 声明在函数外,声明后面的函数(无论main还是其他函数)都可以调用
  • 声明在函数内部,只能在本函数内,声明后面的区域可以调用

22.字符串相关函数:

  • scanf("%s", str):输入参数:字符数组名,不加地址符 遇回车或空格输入结束,并自动将输入的一串字符和 ‘\0’ 送入数组中
  • gets(str):遇回车输入结束,自动将输入的一串字符和 ‘\0’ 送入数 组中
  • puts、gets、putchar、getchar 都定义在 stdio.h 这个头文件中。
  • puts(str):输出参数可以是字符数组名或字符串常量,输出遇 '\0' 结束
  • 字符串连接:strcat(str1, str2),连接两个字符串str1和str2, 并将结果放入str1中

23.动态存储分配函数malloc()

  • void *malloc(unsigned size)
  • 若申请内存空间不成功,则返回NULL(值为0)
  • 返回值类型:(void *)
  • 每次动态分配都要检查是否成功,考虑例外情况处理
if ((p = (int *)malloc(n*sizeof(int))) == NULL) {
    printf(“Not able to allocate memory. \n”);
    exit(1);
}

 24.计数动态存储分配函数calloc ()

  • void *calloc( unsigned n, unsigned size)
  • 在内存的动态存储区中分配n个连续空间,每一存储空间的 长度为size,并且分配后还把存储块里全部初始化为0
  • 若申请成功,则返回一个指向被分配内存空间的起始地 址的指针
  • 若申请内存空间不成功,则返回NULL

25.分配调整函数realloc

  • void *realloc(void *ptr, unsigned size)
  • 更改以前的存储分配,ptr必须是以前通过动态存储分配得到的指针,参数size为现在需要的空间大小
  • 如果调整失败,返回NULL,同时原来ptr指向存储块的内容不变。
  • 如果调整成功,返回一片能存放大小为size的区块,并 保证该块的内容与原块的一致。如果size小于原块的大 小,则内容为原块前size范围内的数据;如果新块更大, 则原有数据存在新块的前一部分。
  • 如果分配成功,原存储块的内容就可能改变了,因此不允许再通过ptr去使用它。

26.指向函数的指针

  • 每个函数都占用一段内存单元, 有一个入口地址(起始地址)
  • 函数指针:一个指针变量,接收 函数的入口地址,让它指向函数
  • 类型名 (*变量名)( ); 如:int (*funptr)( );
  • funpt指向一个返回值类型为int的函数
  • 赋值 funptr = fun;
  • int z = (*func)(3,4);这样就可以使用了。

27.

  • C语言真值为1 假值为0但在实际判断中,凡是非0的都按真处理
  • 从C99标准开始有了布尔型:类型名字为“_Bool”
  • C99为了让C和C++兼容,增加了一个头文件stdbool.h。里面定义了bool、true、false,让我们可以像C++一样的定义布尔类型
  • scanf在接受输入时会有一个缓冲区,用户的键盘输入会存储在这个缓冲区中,随后scanf从这个缓冲区获取输入的数据。
  • 当scanf取完所需要的数据,则会把缓冲区没有被取走的数据留到下一次scanf读取。
  • 当调用scanf时,缓冲区中还有数据,scanf直接从缓冲区取得数据,则不会等待用户的键盘输入

二、程序设计概论

1.python是一种解释性语言,C语言是一种编译型语言;python是一种面向对象的语言,C语言是一种面向过程的语言;Python有C语言编写完成,执行效率不如C语言;C语言支持指针直接操作内存,运行效率极高。

2.计算机能够直接识别和接受的二进制代码称机器指令;在汇编语言中,用助记符代替机器指令的操作码,用地址符号或标号代替指令或操作数的地址。

3.汇编语言与机器语言一一对应,每一条机器指令都有与之对应的汇编指令。汇编语言可以通过编译得到机器语言,机器语言可以通过反汇编得到汇编语言。

4.Java吸收了C++的各种优点,放弃了多继承、指针等概念,其最主要的特点是采用平台无关的字节码,实现一次编译,到处运行

5.
编译执行:高级语言的代码经过编译链接后,形成的可执行文件,就可以在机器上反复执行。
解释执行:高级语言的代码不提前进行编译,而是在运行时由解释器一行一行代码翻译成机器代码再执行。
编译执行和解释执行的直观区别:是用编译器翻译后的代码去执行,还是直接拿着源代码去执行。

编译执行的优点是程序执行效率高,可用于操作系统、大型应用程序、数据库系统等开发。
解释执行的优点是跨平台能力强,开发效率高,可用于交互式界面。

6.
结构化程序设计方法采用自顶向下、逐步求精的设计方法,将复杂程序划分为若干个相互独立的模块。模块是子程序,各个模块通过“顺序、选择、循环”的控制结构进行连接。模块只有一个入口、一个出口。

7.
编辑程序后,用该语言的编译程序对其进行编译,以生成二进制代码表示的目标程序(.
obj 或 .o),与编程环境提供的库函数进行链接(Link)形成可执行的程序(.exe)。
把源代码翻译成目标代码的过程称为编译  compile
把多个目标文件合并起来,生成可执行文件的过程称为链接  link(就是把你的代码文件,用的库函数中代码所在文件等等合成一个文件,就是.exe文件)。
高级语言是源代码(source code);汇编语言是汇编代码(assemble code);机器语言是目标代码(object code)

8.调试:在程序中查找错误并修改错误的过程。调试的方法:设置断点,单步跟踪。

9.编程环境:主要包括程序的编辑器(Editor)、编译器(Compiler)、调试器(Debuger)

10.基本数据类型:int, float, float, char
构造数据类型:数组,结构,联合,枚举,指针,空类型。

11.流程控制分为语句级控制(三种基本控制结构)和单位级控制(子程序,如函数)

12.编译程序只能指出语法错误,而不是语义错误(逻辑错误)。语法错误是compile error,链接错误是link error,逻辑错误是bug.

13.codeblocks, visual studio是编辑器,visual C++, gcc是编译器,GDB是调试器。

 

 

三、可能会考到的代码

1.swap三种编写方式

#include
void swap1(int x, int y), swap2(int *px,int *py),swap3(int *px,int *py);
int main()
{
    int a=1,b=2;
    int *pa=&a, *pb=&b;
    
    swap1(a,b);
    printf("After calling swap1:a=%d, b=%d\n",a,b);
       
    a=1;
    b=2;
    swap2(pa,pb);
    printf("After calling swap2:a=%d, b=%d\n",a,b);
    
    a=1;
    b=2;
    swap3(pa,pb);
    printf("After calling swap3:a=%d, b=%d\n",a,b);
    
    return 0;
}
void swap1(int x, int y)
{
    int t;
    t=x;
    x=y;
    y=t;
}
void swap2(int *px, int *py)
{
    int t;
    t=*px;
    *px=*py;
    *py=t;
}
void swap3(int *px, int *py)
{
    int *pt;
    pt = px;
    px = py;
    py = pt;
}

2.计时器

可以使用time.h和clock()函数获得程序运行时间。常数 CLOCKS_PER_SEC和操作系统相关,请不要直接使用clock()的返回值,而应总是除以 CLOCKS_PER_SEC。

阶乘之和

#include
#include
int main()
{
    const int MOD = 1000000;
    int n, S = 0;
    scanf("%d", &n);
    for(int i = 1; i <= n; i++)
    {
        int factorial = 1;
        for(int j = 1; j <= i; j++)
            factorial = (factorial * j % MOD);
        S = (S + factorial) % MOD;
    }
    printf("%d\n", S);
    printf("Time used = %.2f\n", (double)clock() / CLOCKS_PER_SEC);
    return 0;
}

3.神奇的程序

#include
int main()
{
    int a = 10;
    double b = 9.9 + 0.1;
    int c = (a == b);
    printf("%d",c);
    return 0;
}
//输出结果是1.

4.汉诺塔

#include
void hanio(int n,char a,char b,char c);
int main()
{
    int n;
    printf("input the number of disk:");
    scanf("%d",&n);
    printf("the steps for %d disk are:\n",n);
    hanio(n,'a','b','c');
    return 0;
}
void hanio(int n,char a,char b,char c)
{
    if(n==1)
        printf("%c-->%c\n",a,b);
    else{
        hanio(n-1,a,c,b);
        printf("%c-->%c\n",a,b);
        hanio(n-1,c,b,a);
    }
}
 

 

四、指针与数组

1.指针常见使用规则

(1)下列语句可以对指针变量p赋值:

(第二条0即为空指针NULL,第四条p指向地址为1732的int型变量)

p = &i;
p = 0;
p = NULL;
p = (int*)1732

 (2)*p = *p+1; ++*p; (*p)++,都是将指针p所指的变量值加1。但表达式*p++等价于*(p++),先取*p的值作为表达式的值,在将指针p的值加1,之后p不在指向变量a。
(3)假设a是一个数组,p是一个指针。

  • 以下两条语句等价:
p=a;
p=&a[0];
  • 以下两条语句也是等价:
p=a+1;
p=&a[1]
  • 以下三种方法可以对数组元素求和:
/*法一*/
sum = 0;
for(p=a; p<=&a[99]; p++)
    sum += *p;

/*法二*/
sum = 0;
for(int i=0; i<100; i++)
    sum += *(a+i);

/*法三*/
p = a;
sum = 0;
for(int i=0; i<100; i++)
    sum += p[i];

(4).用指针定义字符串(以下两语句不完全一样)

char sa[] = "This is a string.";
char *sp = "This is a string.";

注)char const *ptr:此种写法和const char *等价,大家可以自行实验验证。

2.scanf() 和 gets() 都可用来输入字符串;printf() 和 puts() 只能输出字符串。

scanf("%s", s);
gets(s);

printf("%s", s);
puts(s);

注:gets()是有返回值的,输入成功返回首地址,不成功返回NULL。puts()输出时自动把'\0'转化为'\n',输出成功返回'\n',否则返回EOF。

3.void指针:

(1)void * p:可以用任何类型的指针对 void 指针进行赋值或初始化。
(2)因 sizeof(void) 没有定义,所以对于 void * 类型的指针p,*p  无定义,++p, --p, p += n, p+n,p-n 等均无定义。

4.数组变量是特殊的指针:

  • 数组变量本身表达地址,所以无需用 & 取地址:int a[10];   int* p = a;
  • 但是数组的单元表达的是变量,需要用 & 取地址:a == &a[0];
  • [ ] 运算符可以对数组做,也可以对指针做: p[0] 与 a[0] 是等价的。
  • * 运算符可以对指针做,也可以对数组做: *a = 25;
  • 数组变量可以看做是 const 的指针,所以不能被赋值。int a[] 与 int * const a 等价;

5.指针是const(只适用C99)

  • int * const q = &i:表示该指针不能再指向其他变量(比如 q++ 就是非法操作),但是可以通过指针修改指针所指变量的值。注:这个* 在const 前面,表示指针是 const。
  • const int * p = &i 或 int const * p = &i:可以直接修改变量本身( i = 25),也可以让该指针指向其他变量( p = & j),但是不可以通过该指针修改所指的变量(如 *p = 25 是非法操作)。注:这个* 在 后面,表示所指为 const。

6.动态分配内存

(1)int *a = (int *)malloc(n * sizeof( int ));

  • 向 malloc 申请的空间大小是以字节为单位的。
  • 返回的类型是void*,需要类型转换为自己需要的类型。

(2)free(a)

  • 把申请得来的空间还给系统。
  • 只能还申请来的空间的首地址。

7.发现:

指针存的值就是指针所指向的值的地址,因此指针之间相互赋值,并非是让另外一个指针指向该指针,而是让两个指针储存的值是相同的,就是说让一个指针和另外一个指针指向相同的地方。

#include
using namespace std;
int main(){
	int a = 10;
	int *p1 = &a;
	int *p2 = p1;
	int **p3 = &p2;
	cout << p1 << endl << p2 << endl << p3 << endl << *p3 << endl;
	return 0;
}
/*
0x6dfed4
0x6dfed4
0x6dfed0
0x6dfed4
*/

五、字符串函数

1.void * memset(void * dest,int ch,int n):(头文件string.h)

将从dest开始的n个字节,都设置成ch。返回值是dest。ch只有最低的字节起作用。

char szName[200] = ""; 
memset( szName,'a',10);

int a[100]; 
memset(a,0,sizeof(a));

9.void * memcpy(void * dest, void * src, int n)  (头文件string.h)

将地址src开始的n个字节,拷贝到地址dest。返回值是dest。

10.字符串处理函数(#include

(1)字符串赋值函数:strcpy(s1, s2)

(2)字符串连接函数:strcat(s1, s2)

(3)字符串比较函数:strcmp(s1, s2), 若str1=str2,则返回零;若str1str2,则返回正数。

(4)字符串长度函数:strlen(s1)    (返回 '\0' 之前的字符个数)

 

11.字符串操作库函数:(#include

(1)char * strchr(const char * str,int c):寻找字符c在字符串str中第一次出现的位置。如果找到,就返回指向该位置的char*指 针;如果str中不包含字符c,则返回NULL。 

(2)char * strstr(const char * str, const char * subStr):寻找子串subStr在str中第一次出现的位置。如果找到,就返回指向该位置的指针;如 果str不包含字符串subStr,则返回NULL。

(3)int stricmp(const char * s1,const char * s2):大小写无关的字符串比较。如果s1小于s2则返回负数;如果s1等于s2,返回0;s1大 于s2,返回正数。不同编译器编译出来的程序,执行stricmp的结果就可能不同。 

(4)int strncmp(const char * s1,const char * s2,int n):比较s1前n个字符组成的子串和s2前n个字符组成的子串的大小。若长度不足n,则取整个串作为子串。返回值和strcmp类似。 

(5)char * strncpy(char * dest, const char * src,int n):拷贝src的前n个字符到dest。如果src长度大于或等于n,该函数不会自动往dest中写 入‘\0’;若src长度不足n,则拷贝src的全部内容以及结尾的‘\0’到dest。

(6)char * strtok(char * str, const char * delim):连续调用该函数若干次,可以做到:从str中逐个抽取出被字符串delim中的字符分隔开的若干个子串。 

(7)int atoi(char *s); 将字符串s里的内容转换成一个整型数返回。比如,如果字符串s的内容是“1234”,那 么函数返回值就是1234。如果s格式不是一个整数,比如是"a12",那么返回0。 

(8)double atof(char *s); 将字符串s中的内容转换成实数返回。比如,"12.34"就会转换成12.34。如果s的格式 不是一个实数 ,则返回0。 

(9)char *itoa(int value, char *string, int radix):将整型值value以radix进制表示法写入 string。

 

六、结构

1.基本知识 

(1)例两个同类型的结构变量,可以互相赋值。但是结构变量之间不能用 “==”、“!=”、“<”、“>”、“<=”、“>=”进行比较运算。
(2)声明一个结构三种形式:

struct point
{
    int x;
    int y;
};

struct point p1, p2;

 

struct
{
    int x;
    int y;
} p1, p2;

 

struct point
{
    int x;
    int y;
} p1, p2;

(3)声明在函数内部的结构类型只能在函数内部使用,和局部变量相类似。若想让结构被多个函数使用,就要把结构声明在函数外部。
(4)赋值(代码中的p1, p2展现了两种赋值方式)

#include
struct point
{
    int x,y;
};
int main()
{
    struct point p1, p2;
    p1 = (struct point){1, 2};
    p2 = {3, 5};
    printf("%d %d %d %d", p1.x, p1.y, p2.x, p2.y);
}

(5)结构指针(以下三个语句等效,s1为结构变量,p为指向s1首地址(第一个成员)的指针)

s1.num=101;
(*p).num=101;
p->num=101;

(6)和数组不同,结构变量名字并不是结构变量的地址,必须使用&运算符。

#include
struct point
{
    int x,y;
};
int main()
{
    struct point p1, p2;
    p1 = {1, 2};
    struct point *p = &p1;
    printf("%d %d\n", p1.x, p1.y);
    printf("%d %d\n", (*p).x, (*p).y);
    printf("%d %d\n", p->x, p->y);
    return 0;
}

2. 联合(union)

(1)用法和 struct 一模一样

#include
union AnElt {
	int i;
	char c;
}elt1, elt2;
int main() {
	int a = sizeof(union AnElt);
	int b = sizeof(AnElt);
	int c = sizeof(elt1);
	printf("%d %d %d\n", a, b, c);
	return 0;
}
/*
Output:
4 4 4
*/

(2)sizeof (union ...) = sizeof( 每个成员 ) 的最大值。
(3)成员变量共享同一内存空间。 
(4)英特尔CPU是小端,内部储存是低位在前,高位在后。

#include
typedef union{
	int i;
	char ch[sizeof(int)];

} CHI;
int main() {
	CHI chi;
	chi.i = 1234;
	for (int i = 0; i < sizeof(int); i++) {
		printf("%02hhX", chi.ch[i]);
	}
	printf("\n");
	return 0;
}
/*
1234表示为16进制:
0x000004D2
Output:
D2040000
*/

七、宏定义与多个源代码文件

1.宏

(1)编译预处理指令

  • #开头的是编译预处理指令
  • 它们不是C语言的成分,但是C语言程序离不开它们。
  • #define用来定义一个宏。

(2)#define

  • 注意没有结尾的分号,也不要随意加上分号(不然可能会因为程序设计不当引起错误),因为不是C的语句。
  • 在C语言的编译器开始编译之前,编译预处理程序 (cpp) 会把程序中的名字换成值,并且是完全的文本替换。

(3)宏

  • 如果一个宏的值中有其他的宏的名字,也是会被替换的。
  • 如果一个宏的值超过一行,最后一行之前的行末需要加\。
  • 宏的值后面出现的注释不会被当作宏的值的一部分。
#include
#define PI 3.1415926
#define FORMAT "%f\n"
#define PI2 2 * PI
#define PRT printf("%f ", PI);\
			printf("%f\n", PI2)
int main() {
	printf(FORMAT, PI2 * 3.0);
	PRT;
	return 0;
}
/*
Output:
18.849556
3.141593 6.283185
*/

(4)没有值的宏

  • #define _DEBUG
  • 这类宏是用于条件编译的,后面有其他的编译预处理指令来检查这类宏是否已经被定义过了。

(5)预定义的宏

  • __LINE__:表示当前行号的整数
  • __FILE__:文件的完整路径和文件名字符串
  • __DATE__:包含当前日期的字符串
  • __TIME__:包含当前时间的字符串
  • __STDC__:如果编译器遵循ANSI C标准,它就是个非零值
#include
int main() {
	printf("%s: %d\n", __FILE__, __LINE__);
	printf("%s, %s\n", __DATE__, __TIME__);
	return 0;
}
/*
Output:
E:\computer\program\2020.04\2020.04.17\源.cpp: 3
Apr 22 2020, 10:20:17
*/

(6)带参数的宏:

  • 可以带多个参数
  • #define MIN(a, b) ((a) > (b) ? (b) : (a))
  • 也可以组合(嵌套使用其他宏)
  • 原则:整个值要带括号;参数出现的每个地方都要括号。
  • 在大型程序的代码中使用的非常普遍;在 # 和 ## 这两个运算符的帮助下,可以“产生函数”;存在中西方差异;部分宏可以被 inline 函数取代。

2.多个源代码文件与头文件

(1)先在一个.h(头文件)中放入函数的原型

//max.h

int max(int a, int b);

(2)然后在 max.cpp 中 #include 这个头文件,并定义函数 

//max.cpp

#include "max.h"
int max(int a, int b) {
	return a > b ? a : b;
}

(3)最后看看 main.cpp 怎么写:

#include
#include "max.h"

int main() {
	int a = 5;
	int b = 6;
	printf("%d", max(a, b));
	return 0;
}

(4)#include 有两种形式来指出要插入的文件:

  • "" 要求编译器首先在当前目录(.cpp 所在目录)寻找这个文件,如果没有,到编译器指定的目录中去找。
  • <> 让编译器只在指定的目录中去找。
  • 编译器自己知道自己的标准库头文件在哪里。环境变量和编译器命令行参数也可以指定寻找头文件的目录。

(5)其他注意事项:

  • #include 是一个编译预处理指令,和宏一样,在编译之前就处理了。它把那个文件的全部文本内容原封不动的插入到它所在的那个地方。所以也不是一定要在.c文件最前面 #include。
  • vs 中新建头文件或是源文件不要点击左上角的那个新建,而是要在右边解决方案资源管理器中新建文件。同样,重命名也是在右边那个解决方案资源管理器中进行。
  • #include 不是用来引入库的。stdio.h 里只有 printf 的原型,printf 的代码在另外的地方,某个.lib( Windows ) 或 .a( Unix ) 中。但是,现在C语言编译器默认会引入所有的标准库。#include  只是为了让编译器知道 printf 函数的原型,保证你调用时给出的参数值是正确的类型。

(6)头文件

  • 在使用和定义这个函数的地方都应该 #include 这个头文件。
  • 一般的做法就是任何.c都有对应的同名的.h,把所有的对外公开的函数的原型和全局变量的声明都放进去。

(7)不对外公开的函数

  • 在函数前面加上 static 就使得它成为只能在所在的编译单元中被使用的函数。
  • 在全局变量前面加上 static 就使得它成为只能在所在编译单元中被使用的全局变量

3.变量的声明

(1)变量的声明

  • int i:变量的定义
  • extern int i:变量的声明

(2)声明是不产生代码的东西,定义是产生代码的东西。常见的声明有:

  • 函数原型
  • 变量声明
  • 结构声明
  • 宏声明
  • 枚举声明
  • 类型声明
  • inline 函数

(3)只有声明可以放在头文件中,否则会造成一个项目中多个编译单元中有重名的实体(不过,某些编译器允许几个编译单元中存在同名的函数,或者用 weak 修饰符来强调这种存在)。
(4)同一个编译单元中,同名的结构不能重复声明。如果多个头文件里有同名的结构声明,很难保证这个结构不被声明多次。这时候需要“标准头文件结构”。
(5)运用条件编译和宏,保证这头文件在一个编译单元中只会被 #include 一次。#pragma once 也能起到相同的作用,但不是所有编译器都支持。

//max.h

#ifndef _gALL_
#define _gALL_

extern int gALL;

#endif 

#ifndef _Node_
#define _Node_
struct Node {
	int n;
};

#endif
// min.h

#ifndef _Node_
#define _Node_

#include "max.h"

struct Node {
	int n;
};
#endif
//max.cpp

int gALL = 10;
//main.cpp

#include
#include "min.h"

int main() {
	
	printf("%d\n", gALL);
	return 0;
}

4.#pragma once用法总结

(1)标准头文件结构

  • 为了避免同一个头文件被包含(include)多次,C/C++中有两种宏实现方式:一种是#ifndef方式;另一种是#pragma once方式。
  • 在能够支持这两种方式的编译器上,二者并没有太大的区别。但两者仍然有一些细微的区别。

(2)#ifndef:

  • #ifndef的方式受C/C++语言标准支持。它不仅可以保证同一个文件不会被包含多次,也能保证内容完全相同的两个文件(或者代码片段)不会被不小心同时包含。
  • 由于编译器每次都需要打开头文件才能判定是否有重复定义,因此在编译大型项目时,ifndef会使得编译时间相对较长,因此一些编译器逐渐开始支持#pragma once的方式。
  • 会出现宏名冲突引发的奇怪问题

(3)#pragma once:

  • #pragma once 一般由编译器提供保证:同一个文件不会被包含多次。注意这里所说的“同一个文件”是指物理上的一个文件,而不是指内容相同的两个文件。
  • 你无法对一个头文件中的一段代码作pragma once声明,而只能针对文件。
  • 不必再担心宏名冲突了,当然也就不会出现宏名冲突引发的奇怪问题。

八、文件处理

1.翁恺讲义

(1)打开文件标准代码

  • FILE* fopen(const char * restrict path, const char * restrict mode);
  • int fclose(FILE *stream);
  • fscanf(FILE*, ...)
  • fprintf(FILE*, ...)
#include
int main() {
	FILE* fp = fopen("12.in", "r");
	if (fp) {
		int num;
		fscanf(fp, "%d", &num);
		printf("%d\n", num);
		fclose(fp);
	}
	else {
		printf("无法打开文件\n");
	}
	return 0;
}

(2)fopen

r 打开只读
r+ 打开读写,从文件头开始
w 打开只写。如果不存在则创建,如果存在则清空
w+ 打开读写,如果不存在则新建,如果存在则清空
a 打开追加。如果不存在则新建,如果存在则从文件尾开始
...x 只新建,如果文件已存在则不能打开

(2)文本 vs 二进制

  • 文本的优势是方便人类读写,且跨平台
  • 文本的缺点是程序输入输出要经过格式化,开销大
  • 二进制的缺点是人类读写困难,而且不跨平台(int大小不一致,大小端的问题)
  • 二进制的有点是程序读写快。

(4)程序为什么要文件:

  • 配置:Unix用文本,Windows用注册表
  • 数据:稍微有点量的数据都放数据库里了。
  • 媒体:这个只能是二进制的。
  • 现实是,程序通过第三方库来读写文件,很少直接读写二进制文件了。

(5)二进制读写

  • size_t fread(void *restrict ptr, size_t size, size_t nitems, FILE *restrict stream);
  • size_t fwrite(const void *restrict ptr, size_t size, size_t nitems, FILE *restrict stream);
  • 注意FILE指针是最后⼀一个参数,返回的是成功读写的字节数
  • nitem:因为二进制文件的读写一般都是通过一个结构变量的操作来进行的。于是nitem就是用来说明这次读写几个结构变量!
//student.h

#pragma once
const int STR_LEN = 20;
typedef struct _student {
	char name[STR_LEN];
	int gender;
	int age;
}Student;
//main.cpp

#include
#include "student.h"
void getList(Student aStu[], int number) {
	char format[STR_LEN];
	sprintf(format, "%%%ds", STR_LEN - 1);
	//"%19s"
	for (int i = 0; i < number; i++) {
		printf("第%d个学生:\n", i);
		printf("name, gender, age:\n");
		scanf(format, &aStu[i].name);
		scanf("%d", &aStu[i].gender);
		scanf("%d", &aStu[i].age);
	}
}
int save(Student aStu[], int number) {
	int ret = -1;
	FILE* fp = fopen("student.data", "w");
	if (fp) {
		ret = fwrite(aStu, sizeof(Student), number, fp);
		fclose(fp);
	}
	return ret == number;
}
int main() {
	int number = 0;
	printf("输入学生的数量:");
	scanf("%d", &number);
	Student aStu[20];
	getList(aStu, number);
	if (save(aStu, number)) {
		printf("保存成功\n");
	}
	else {
		printf("保存失败\n");
	}
	return 0;
}

(6)在文件中定位

  • long ftell(FILE *stream);
  • int fseek(FILE *stream, long offset, int whence);
  • SEEK_SET:从头开始
  • SEEK_CUR:从当前位置开始
  • SEEK_END:从尾开始(倒过来)
//student.h

#pragma once
const int STR_LEN = 20;
typedef struct _student {
	char name[STR_LEN];
	int gender;
	int age;
}Student;
//main.cpp

#include
#include "student.h"
void read(FILE *fp, int index) {
	fseek(fp, index * sizeof(Student), SEEK_SET);
	Student stu;
	if (fread(&stu, sizeof(Student), 1, fp) == 1) {
		printf("%s %d %d", stu.name, stu.gender, stu.age);
	}
}
int main() {
	FILE* fp = fopen("student.data", "r");
	if (fp) {
		fseek(fp, 0L, SEEK_END);
		long size = ftell(fp);
		int number = size / sizeof(Student);
		int index = 0;
		printf("有%d个数据,你要看第几个", number);
		scanf("%d", &index);
		read(fp, index - 1);
		fclose(fp);
		return 0;
	}
}

(7)二进制文件的可移植性

  • 二进制文件不具有可移植性。在 int 为32位的机器上写成的数据文件无法直接在 int 为64位的机器上正确读出。
  • 解决方案之一是放弃使用int,而是用typedef具有明确大小的类型。
  • 更好的方案是用文本。

2.课本

(1)定义文件类型指针:

  • FILE * fp:FILE 是文件类型定义符,fp 是文件类型的指针变量。
  • fp -> curp:指示文件缓冲区种数据存取的位置。

(2)文件处理步骤:

  • 定义文件指针
  • 打开文件:文件指针指向磁盘文件缓冲区
  • 文件处理:文件读写操作
  • 关闭文件

(3)打开文件:

  • fopen("文件名", "文件打开方式");
  • 执行成功,函数将返回包含文件缓冲区等信息的FILE结构地址。否则,返回NULL。
  • 文件路径若包含绝对完整路径,定位子目录用的斜杠'\'需要改为双斜杠'\\'。
r 以只读方式打开文件,该文件必须存在。 rb 打开二进制文件进行只读
w 打开只写文件,若文件存在则长度清为0,即该文件内容消失,若不存在则创建该文件。 wb 以只写方式打开或新建一个二进制文件,只允许写数据。
a 以附加的方式打开只写文件。若文件不存在,则会建立该文件,如果文件存在,写入的数据会被加到文件尾,即文件原先的内容会被保留(EOF符保留)。 ab 打开二进制文件进行写/追加
r+ 以读/写方式打开文件,该文件必须存在。 rb+ 以读/写方式打开一个二进制文件,只允许读/写数据。
w+ 打开可读/写文件,若文件存在则文件长度清为零,即该文件内容会消失。若文件不存在则建立该文件。 wb+ 以读/写方式打开或建立一个二进制文件,允许读和写。
a+ 以附加方式打开可读/写的文件。若文件不存在,则会建立该文件,如果文件存在,则写入的数据会被加到文件尾后,即文件原先的内容会被保留(原来的EOF符不保留)。 ab+ 以读/写方式打开一个二进制文件,允许读或在文件末追加数据。

(4)关闭文件:

  • fclose(文件指针)
  • exit(0)是系统标准函数,作用是关闭所有打开的文件并结束程序的执行。参数0表示程序正常结束,非0参数表示不正常的程序结束。
  • 通过文件关闭,能强制把缓冲区的数据写入磁盘扇区。
  • 关闭文件应该这样子:
if(fclose(fp)){
    printf("Can not close the file!\n");
    exit(0);
}

(5)字符方式文件读写:

  • ch = fgetc(fp):成功返回ch,失败返回EOF(并非常规的ASCII码,而是值为-1的常量)。且这个函数读到有效字符的时候,会向后移动指针。
  • fputc(ch, fp):有返回值,若写文件成功返回ch,失败返回EOF。

(6)复制文件内容

while(!feof(fp1)){
    ch = fgetc(fp1);
    if(ch != EOF) fputc(ch, fp2);
}
  • feof() 来检测fp1所指示文件的位置是否到了文件末尾。到了文件末尾返回1,否则返回0。

(7)字符串方式读写函数

  • 向文件中写入字符串:fputs(s, fp):s是要写入的字符串,fp是文件指针。字符串s的结束符'\0'不写入文件。函数执行成功返回所写的最后一个字符,否则返回EOF。
  • 从文件中读入内容到字符串:fgets(s, n, fp):s是字符串,n是指定读入的字符的个数,fp是文件地址,最多读取n-1个字符,并自动在末尾加上'\0'。函数执行成功,返回读取的字符串,否则返回空指针。

(8)格式化方式读写文件:

  • fscanf(文件指针,格式字符串,输入表)
  • fprintf(文件指针,格式字符串,输入表);

(9)其他:

  • rewind(FILE* fp):定位文件读写位置的指针,使其指向读写文件的首地址。
  • fseek(fp, offset, from):fp是文件指针,offset是移动偏移量(long类型,使用常量时加后缀'L',且offset可正可负,单位是字节数)。from表示从哪个位置开始计算偏移量,有三个取值:文件首部,当前位置,文件尾部,分别为0, 1, 2,也可以是这三个常量:SEEK_SET, SEEK_CUP, SEEK_END。
  • ftell(文件指针):获取相对文件开头位置偏移量(字节数)。函数出错时,返回-1L。
  • ferror(文件指针):读写错误检查
  • clearerr(文件指针):出错标记清除函数。

你可能感兴趣的:(C++学习笔记)