C语言中cord有几个成员变量,C语言知识点(上)

1.源码文件如何变成可执行文件(*)

需要以下4个步骤:

(1)预处理阶段:预处理器根据以#开头的指令,修改主要包括#include、#define和条件编译三个方面,修改源码内容,比如如果源码中有 #include 则预处理器会读取文件 stdio.h 文件中的内容,并将其直接插入到原来的源码文件中,通常另存为以 .i 为扩展名的文件。(gcc -E hello.c -o hello.i)

(2)编译阶段:编译器读取 .i 文件中的内容,并将其翻译为以 .s 为扩展名的汇编语言文件。(gcc -S hello.c -o hello.s)

(3)汇编阶段:汇编器将 .s 文件翻译成机器码,并保存为 .o为扩展名的文件。

(gcc -c hello.s -o hello.o)

(4)链接阶段:链接器将不同的 .o 文件合并到一起,组成最终的可执行文件;比如我们的程序里调用了 printf 函数,作为一个C标准函数,printf 单独存在于一个 printf.o 的文件中,那么链接器将会找到这个 printf.o 文件,将其中的内容合并到我们自己的 .o 文件中,生成可以被加载到内存中执行的文件。

C语言主要分为几个版本:Old Style C、C89、C99和C11。其中,C89、C99和C11是标准语言规范,现在广泛使用的是C99。

2.面向过程和面向对象的区别(*)

(1)面向过程就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。

(2)面向对象是把构成问题事务分解成各个对象,建立对象的目的不是为了完成一个步骤,而是为了描叙某个事物在整个解决问题的步骤中的行为。

比如:五子棋

面向过程:1.开始游戏,2、黑子先走,3、绘制画面,4、判断输赢,5、轮到白子,6、绘制画面,7、判断输赢,8、返回步骤2,9、输出最后结果。

面向对象:1.黑白双方,2、棋盘系统,负责绘制画面,3、规则系统,负责判定诸如犯规、输赢等。

特点

面向过程(蛋炒饭)

优点:性能比面向对象高,因为类调用时需要实例化,开销比较大,比较消耗资源;比如单片机、嵌入式开发、 Linux/Unix等一般采用面向过程开发,性能是最重要的因素。

缺点:没有面向对象易维护、易复用、易扩展

面向对象(盖浇饭)

优点:易维护、易复用、易扩展,由于面向对象有封装、继承、多态性的特性,可以设计出低耦合的系统,使系统 更加灵活、更加易于维护

缺点:性能比面向过程低

3.基本数据类型

(1)数值类型

数值类型分为整型(整数)和浮点型(小数)。按照表示数字范围的从大到小。

<1>整型

整数分为五类:字符型(char)、短整型(short)、整型(int)、长整型(long)和长长整型(long long)

<2>浮点型

浮点型分三类:单精度型(float)、双精度型(double)和长双精度型(long double)

(2)获取类型的大小

关键字sizeof:查看变量或者类型大小。

ecff50318dec

(3)字节

sizeof获得数据的单位是Byte(字节)。Byte(字节)是计量存储容量的一种计量单位,一个字节是8位二进制,可容纳256个数字。一个ASCII字符就是一个字节。

ecff50318dec

用B表示Byte(字节),用b表示bit(比特/位).

(4)输入输出格式化

ecff50318dec

double的输入占位符必须是%lf,输出占位符可以是%f。

(5)整数类型

<1>表示范围

ecff50318dec

类型的表示范围与类型大小存在如下关系:

ecff50318dec

n表示的是bit位,比如:char是一个字节,8位,int是4个字节,32个二进制位,由于存在负数,所以数的大小会减半。

<2>无符号整型

在一些特殊情况下,数据只用0和整数,不存在负数,这时可以使用无符号整型unsigned。无符号整型只是在原来的整型前加上关键字unsigned。因为没有负数,所以数值的表示范围扩大了一倍。

ecff50318dec

总之,类型的表示范围与类型大小存在如下关系:

ecff50318dec

<3>整数类型的选择

大多数情况下使用int。

如果int范围不够,使用long long。

避免使用long。

谨慎使用unsigned。

(5)浮点类型

<1>浮点数的范围

示例

输出

含义

1.0/0.0

inf

表示正无穷大

-1.0/0.0

-inf

表示负无穷大

0.0/0.0

nan

不存在

<2>浮点数的精度

ecff50318dec

注意:浮点类型没有无符号unsigned类型。

如何比较浮点数?使用最小误差。

float a = 10.2;

float b = 9;

float c = a - b;

if(fabs(c - 1.2) < 0.000001){

printf("%f == 1.2\n",c);//1.200000 == 1.2

}

在误差范围内认为相等。即绝对值差小于精度最小值。

float浮点数误差通常为1-6(6位有效数字)。

double浮点数误差通常为1-15(15位有效数字)。

<3>浮点类型选择

大多数情况下使用double。

尽量不要使用float。

过程运算可以使用long double。

既然浮点数这么不准确,为什么还需要?

浮点数通过损失精度,获取更大的表示范围。

(6)字符类型

字符类型是一个特殊类型,是整型的一种。使用单引号表示字符字面量,例如:字母'a'、数字'1'、空字符''、转义字符\n。

通常使用%c作为格式化占位符输入输出,有时也可以使用%d输出字符对应ASCII编码。(C语言中字符即数字。)

<1>ASCII编码

ASCII编码使用7 位二进制数(剩下的1位二进制为0)来表示所有的大写和小写字母,数字0 到9、标点符号, 以及在美式英语中使用的特殊控制字符。

ASCII表的特点:

1.字母在ASCII表中是顺序排列的。

2.大写字母和小写字母是分开排列的。

<2>运算

字符类型可以像整型一样参与运算。

1.一个字符加上一个数字得到ASCII表中对应新字符。

2.两个字符相减,得到这两个字符在表中的距离。

<3>转义字符/逃逸字符

在ASCII表中,除了常见的字符(如:大小写字母、数字等),还包含一些无法打印出来的控制字符或特殊字符。这些字符以反斜线\开头,后面接着一个字符,这种字符被称作转义字符/逃逸字符。

ecff50318dec

(7)布尔类型

在C99中新增bool类型表示逻辑值,它只有两种值(true和false)。使用前需要加上头文件stdbool.h。

(8)数据类型转换

<1>自动类型转换

当运算符左右两边操作数的类型不一致时,会自动转换成较大类型。

整型:char→short→int→long→long long

浮点型:int→float→double→long double

<2>强制类型转换

当把一个较大的类型转换成较小的类型,需要强制转换。

强制转换语法:

(转换后的类型)值

浮点数转整数采用的是截断方式。

printf("%d\n",(int)3.14);//3

整型转浮点型也需要强制转换。

printf("%f\n",(double)2);//2.000000

4.运算符

(1)算术运算符

+ 、-、*、 /(取整)、 %(取余)

(2)关系运算符

==、!=、>、 =、 <=

(3)逻辑运算符

&& 、 || 、 !

闰年判断:year%4==0&&year%100!=0 || year%400==0(year能被4整除 and 不能被100整除 or year能被400整除 )

(4)复合赋值运算符

+=、-=、*=、/=、%=

(5)自增、自减运算符

自增运算符与自减运算符优先级高于算术运算符。

<1>前缀自增/自减

++a、--a

<2>后缀自增/自减

ecff50318dec

(6)运算的优先级顺序

自增/自减的优先级大于算数运算符的优先级;

自增/自减的优先级大于解引用*的优先级

中括号[ ]的优先级大于解引用*的优先级

结构体中用点.的优先级大于取地址&的优先级

结构体中用点.的优先级大于接引用*

5.变量

初始化和赋值:

初始化时在生成变量时放入数值,赋值是在已经生成变量时放入数值。

6.控制语句

(1)条件判断

<1>if-else

代码块与if之间使用空格或者Tab缩进,不影响编译和执行,只是为了提高代码可读性。

if(condition1){

}else if(condition2){

}else{

}

<2>switch case

switch(表达式){

case 整型常量1:

/* 表达式等于整型常量1执行的代码 */

break; /* 可选的 */

case 整型常量2:

/* 表达式等于整型常量2执行的代码 */

break; /* 可选的 */

default : /* 可选的 */

/* 表达式不等于上面所有情况执行的代码 */

}

(2)循环

<1>while语句

while(条件){

/* 如果条件为真将重复执行的语句 */

}

<2>do-while

do {

/* 如果表达式为真将重复执行的语句 */

}while(条件);//注意while()后的分号;。

do-while循环是先循环后判断,循环体至少执行一次;while循环是先判断后循环,循环体可能一次也不执行。

<3>for

for (初始值;条件;递增或递减){

/* 如果条件为真将重复执行的语句 */

}

在while和for循环中,break是结束一个循环体;continue是结束单次循环。

(3)简化

<1>省略大括弧

如果if语句、while语句、for语句中只有一个执行语句,可以省略大括弧。

<2>三元运算符:?

如果if-else语句只有单个执行语句,可以使用三元运算符:?。

7.进制

(1)转换

<1>十进制转R进制

十进制转任何进制用短除法。从下往上来统计。

<2>R进制转十进制

加权和。

0x2A=2*16^1+A*16^0=32+10=42

代码:

2进制数1011转10进制:1

(1*2)+0)*2+1)*2+1)=11

int res=0;

res=res*2+每一位

(2)C语言中的进制

<1>进制常量表示

C语言不能直接表示二进制常量。八进制数字以0开头,十六进制数字以0x或0X开头。

<2>进制打印(输出)

进制的输出其实与字符输出是一样的,根据占位符的不同输出不同。

十进制:%d

八进制:%#o

十六进制:%#x

char a = 'a';

printf("%c\t%d\t%#o\t%#x\n",a,a,a);//a,97,0141,0x61

<3>进制输入

10进制:%d

8进制:%o

16进制:%x

scanf("%o",&n);//010

printf("%d\n",n);//8

scanf("%x",&n);//0xa

printf("%d\n",n);//10

<4>%i

%i 可以匹配八进制、十进制、十六进制表示的整数。

例如: 如果输入的数字有前缀 0(018),%i将会把它当作八进制数来处理,如果有前缀0x (0x54),它将以十六进制来处理。

scanf("%i",&n);//0xa

printf("%d\n",n);//10

8.文件操作

(1)文件输入输出

使用printf()和命令行重定向>实现文件输出;

使用scanf()和命令行重定向

<1>实例

hello.c

char name[256];

scanf("%s",name);

printf("Hello %s\n",name);

编译

gcc hllo.c -o hello

执行

echo zhangsan > namefile //把张三重定向到namefile文件中

./hello < namefile > output //把输入定向到./hello 中,然后把执行结果定向到output中。

(2)文件的打开和关闭fopen和fclose

头文件

<1>打开文件fopen

FILE *fopen(const char *pathname, const char *mode);

参数

No.

参数

作用

1

filename

需要打开的文件

2

mode

文件打开方式

返回值

No.

类型

说明

1

成功

返回值是指向这个文件流的文件指针

2

失败

NULL

打开方式

No.

打开方式

含义

1

r(read)

2

w(write)

写,不存在创建,存在清空写入(w),追加(a)

3

a(append)

追加

4

+(plus)

读或写,主要是配合r、w、a使用

5

t(text)

文本文件(默认)

6

b(binary)

二进制文件

<2>关闭文件fclose

int flcose(FILE* stream);

参数

stream文件指针。

返回值

如果成功释放,返回0; 否则返回EOF(-1).

(3)文本读写fprintf()和fscanf()

<1>函数原型

int printf(const char *format, ...);

int fprintf(FILE *stream, char *format, argument...);

int fscanf(FILE *stream, char *format, argument... );

fprintf()/fscanf()与printf()/scanf()使用非常相似,区别在于fprintf()/fscanf()第一个参数stream是文件描述符。

<2>用法示例

从文件中读出数据

int i ;

float f ;

char c;

char str[10];

fscanf(fp, "%d %f %c %s\n", &i, &f, &c, str); //字符串是不需要加&

将数据写入文件

int i = 10;

float f = 3.14;

char c = 'C';

char str[10] = "haha";

fprintf(fp, "%d %f %c %s\n", i, f, c, str);

如果不需要从文件里面写入字符串,那么就可以用逗号或者其他符号来分隔;如果文件里需要写入字符串,那么字符串与其他数据之间只能用空格和回车来分隔。

<3>实例

将多个学生信息写入文件并读出。

struct Student {

char name[32]; //姓名

int age; //年龄

float score; //成绩

};

int main() {

//打开文件

FILE* fp =fopen("./info.txt","r");

if(NULL==fp) {

perror("文件fp打开失败");

return 1;

}

//从文件中读取数据

int n;

fscanf(fp,"%d",&n);

struct Student student[n];

for(int i=0; i

fscanf(fp,"%s %d %f",student[i].name,&student[i].age,&student[i].score);

}

//将数据写入文件

FILE* fq=fopen("./res.txt","w");

if(NULL==fq) {

perror("fq打开失败");

return 1;

}

for(int i=0; i

fprintf(fq,"%s\t%d\t%f\n",student[i].name,student[i].age,student[i].score);

}

//关闭文件

fclose(fq);

fclose(fp);

}

(4)二进制读写:fread()和fwrite()

对于二进制文件的数据读取和写入是一对,只有以二进制的方式写入到文件才能读出来,不能从文本文件中读取数据。

<1>函数原型

size_t fread(void *ptr, size_t size, size_t count, FILE* stream);

size_t fwrite(void *ptr, size_t size, size_t count, FILE* stream);

参数

No.

参数

作用

1

ptr

一个指针,在fread()中是从文件里读入的数据存放的地址;在fwrite()中是写入到文件里的数据存放 的地址。

2

size

每次要读写的字节数

3

count

读写的次数

4

stream

文件指针

返回值

成功读取/写入的字节数。

<2>用法示例

从文件中读出字符串

char str[100];

fread(str, sizeof(str), 1, fp);//每次读取sizeof(str)个字节,读一次

或者:

fread(str,1,sizeof(str),fp);//每次读取1个字节,读取sizeof(str)次,和上面一样。

将字符串写入文件

char str[] = "Hello World";

fwrite(str, sizeof(str), 1, fp);

或者:

fwite(str,1,sizeof(str),fp);

<3>实例

往文件中写数据(结构体)

typedef struct {

char name[32]; //姓名

int age; //年龄

float score; //成绩

}Student;

int main(){

FILE* fp = fopen("./1.txt","wb");

if(NULL==fp){

perror("open failed");

return 1;

}

Student student[2];

strcpy(student[0].name,"张三");//结构体中的字符串赋值要使用strcpy

student[0].age=20;

student[0].score=92.8;

strcpy(student[1].name,"李四");

student[1].age=22;

student[1].score=76.2;

for(int i=0;i<2;++i){

fwrite(&student[i],sizeof(Student),1,fp);

}

fclose(fp);

}

从文件中读数据(结构体)

int main() {

//打开文件

FILE* fp =fopen("./1.txt","rb");

if(NULL==fp) {

perror("文件fp打开失败");

return 1;

}

Student student;//由于不知道读取的个数,所以使用while循环

while(fread(&student,sizeof(Student),1,fp)){//读到数据就显示,否则就退出

printf("%s\t%d\t%f\n",student.name,student.age,student.score);

}

fclose(fp);

}

(1)写操作fwrite()后必须关闭流fclose()。

(2)不关闭流的情况下,每次读或写数据后,文件指针都会指向下一个待写或者读数据位置的指针。

(3)sizeof和strlen的区别,否则就会读不到数据(在读数据时,使用strlen(buf))或者读到一半数据(写数据时,使用sizeof(msg)=8)。

(4)对于二进制文件的写入和读取是同时存在的,不能从普通文件中读取数据。

(5)其他类型数据的读取:https://www.cnblogs.com/xudong-bupt/p/3478297.html

<4>二级制文件和文本文件

比较

文本

二进制

优势

便于人类读写,跨平台

文件较小,机器读写比较快

劣势

文件较大,机器读写比较慢

不便人类读写,不跨平台

配置

Unix用文件

Windows用注册表

说明:

(1)Unix喜欢用文本文件来做数据存储和程序配置。

(2)windows喜欢用二进制文件。

(3)数据量较多使用数据库

(4)多媒体使用二进制

(5)通常使用第三方库读写文件,很少直接读写二进制文件。

(5)文件定位:ftell()和fseek()

<1>函数原型

// 获取位置

long ftell(FILE* stream);

// 设置位置

int fseek(FILE* stream,long offset,int whence);

参数

No.

参数

含义

1

stream

文件指针

2

offset

偏移量,基于起始点偏移了offset个字节

3

whence

起始点

No.

whence

数值

含义

1

SEEK_SET

0

从头开始

2

SEEK_CUR

1

从当前开始

3

SEEK_END

2

从结束开始

返回值

ftell()返回文件指针当前位置,基于文件开头的偏移字节数。

<2>示例:

fseek(stream, 0, SEEK_END);

// 将文件指针指向文件结尾,并偏移了 0 个字节,也就是直接将文件指针指向文件结尾

fseek(stream, -10, SEEK_CUR);

// 将文件指针指向当前位置,并偏移了 -10 个字节,也就是将文件指针往前移动10个字节

应用

获取文件大小。

char path[1024];

scanf("%s",path);

FILE* fp = fopen(path,"r");

if(fp){

fseek(fp,0,SEEK_END);

long size = ftell(fp);

printf("%ldB\n",size);

}

(6)文件结尾判断feof()

<1>函数原型

int feof(FILE* stream);

返回值

一旦文件指针指向文件结尾,就返回一个真值;否则返回非真值。

(7)返回开头rewind()

<1>函数原型

void rewind(FILE* stream);

<2>举例

FILE *fp = fopen("./text.txt", "r+");

fseek(fp, 0, SEEK_END); // 将文件指针指向文件结尾

long len = ftell(fp); // 获取文件指针位置,得到文件的大小(Byte)

rewind(fp); // 将文件指针重新指向文件开头

(8) 清空数据流fflush()

<1>函数原型

void fflush(FILE* stream);

<2>使用

在使用多个输出函数连续进行多次输出时,有可能发现输出错误。因为下一个数据再上一个数据还没输出完毕,还在输出缓冲区中时,下一个printf就把另一个数据加入输出缓冲区,结果冲掉了原来的数据,出现输出错误。 在 prinf();后加上fflush(stdout); 强制马上输出,避免错误。

(9)文件重名名rename

int rename(const char *old_filename, const char *new_filename);

(10)文件删除remove

int remove(char * filename);

(11)putc和getc

<1>函数原型

int getc ( FILE * stream );//从stream中获取一个字符

int putc ( int character, FILE * stream );//将字符写入到stream中

<2>实现cp命令

int main(int argc,char* argv[]){

if(3!=argc){

perror("argc error");

return 1;

}

FILE* srcfp=fopen(argv[1],"rb");

if(NULL==srcfp){

perror("srcfile error");

return 1;

}

FILE* dstfp=fopen(argv[2],"wb");

if(NULL==dstfp){

perror("dstfile error");

return 1;

}

while(!feof(srcfp)){//到达文件末尾返回true,否则返回flase

putc(getc(srcfp),dstfp);

}

fclose(srcfp);

fclose(dstfp);

}

9.宏定义

(1) 宏定义是什么?

宏是用来表示一段代码的标识符。

宏也是标识符,也要满足标识符的规则。但通常习惯使用大写字母和下划线命名。

(2)宏定义怎么用?

<1>宏定义通常有三种用法:

当作常量使用。

当作函数使用。

编译预处理。

<2>宏定义常量

预定义宏

ANSI C标准有定义好的宏定义,称为预定义宏。这些宏定义以双下划线__开头结尾。

ecff50318dec

printf("%s:%d",__FILE__,__LINE__);//源文件名:当前语句所在的行号

printf("%s:%s",__DATE__,__TIME__);//月 日 年 时间

自定义宏

除了使用标准定义的宏,可以使用#define指令用来定义一个宏。

(1)语法

#define 标识符 值

#define PI 3.1415926

(2)说明

注意没有结尾的分号,因为不是C的语句。

名字必须是一个单词,值可以是各种东西。

在C语言的编译器开始之前,编译预处理程序会把程序中的名字换成值,是完全的文本替换。

如果一个宏的值有其他宏的名字,也会被替换

#define PI_2 2*PI

如果一个宏的值超过一行,最后一行之前行末需要加\

#define PI_2 2 \

* \

PI

宏的值后面出现的注释不会被当做宏的值的一部分。

#define PI_2 2*PI // 二倍的PI

<3>带参数的宏

宏可以带参数,使用上有些像函数。这种宏称为带参数的宏。

语法

#define 标识符(参数...) 代码

示例:

#define square(x) ((x)*(x))

#define cube(x) ((x)*(x)*(x))

错误示范

#define square(x) x*x

square(10);//100

square(10+1);//10+1*10+1=21

说明

上面因为缺少括号导致错误,称为宏定义边际效应,所以带参数的宏需要在以下两个位置加上括号:

(1)参数出现的每个地方都要加括号。

(2)整个值要加括号

参数的宏也可以有多个参数

#define MIN(a,b) ((a)

swap函数:

#define SWAP(m,n) {\

int t=m;\

m=n;\

n=t;\

}

尽量避免使用宏定义。

<4>编译预处理

有时我们会使用没有值的宏,这种宏用于条件编译的,#ifdef #ifndef用于检查宏是否被定义过。控制代码的编译。

#define TEST

#ifdef TEST

printf("Test\n");//如果前面定义了:#define TEST,则执行这个,否则执行后面的。

#else

printf("No Test\n");

#endif

(3)宏展开

宏的本质是指编译前(编译预处理阶段),用定义中的值或者代码完全替换宏的标识符。

只替换条件编译中的宏。使用下面指令来查看宏的展开。

gcc -E hello.c -o hello.i

(4)编译预处理指令

以#开头的都是编译预处理指令。除了宏定义,还有文件包含#include和条件编译指令#if、#ifdef #ifndef、#else、#elif、#endif,一般写在.h文件中。

文件包含#include,把文件内容包含到代码中。

条件编译指令,根据编译条件,选择编译或者编译某段代码。

格式

#ifndef __HELLO_H

#define __HELLO_H

函数声明(函数原型)

#endif //__HELLO_H

10.头文件

(1)经验

编写小的程序可以把代码写在一个文件中,当编写大程序中,需要把代码分在多个文件中。

多个源代码文件

(1)main()里面代码太长适当分成几个函数。

(2)一个源代码文件太长适当分成几个文件。

(3)两个独立的源代码文件不能编译成可执行文件。

(2)头文件概念

<1>#include指令

把函数原型放到一个头文件.h中,在需要调用这个函数的源代码文件.c时,使用#include指令包含这个头文件,使编译器在编译的时候知道函数的原型。

<2>头文件作用

头文件主要用于编译器的编译阶段,告诉编译器在代码中使用了这么一个函数。只是告诉编译器函数的原型,保证调用时参数类型和个数正确。

(3)头文件的使用

在使用和定义函数的地方都要#include头文件,#include指令不一定要放在.c文件的最前面,但是通常习惯这样做。

#include指令分类

#include指令有两种形式:

(1)#include <>:编译器到指定目录查找,主要用于查找标准库的头文件。

(2)#include "":编译器先到当前目录查找(.c文件所在目录),如果没有再到指定目录查找。

#include指令是一个编译预处理指令,和宏一样,在编译之前就处理了。它会把指定的文件原封不动的插入到它所在的地方。

(4)头文件怎么写

头文件通常用来存放所有对外公开的函数的原型和全局变量的声明。

通常任何.c文件都有对应同名的.h文件

<1>声明

常见的声明

函数声明

变量声明

结构体声明

宏声明

枚举声明

类型声明

通常声明只能可以放在头文件中,否则,编译器连接会出现重名函数错误。

重复声明

在一个编译单元中,不允许重复声明,尤其是结构体声明。为了防止头文件不被多次#include导致重复声明。

定义与声明

声明是不产生代码的语句。定义是产生代码的语句。

int i;// 变量的定义,在.c文件中

extern int i; // 变量的声明,在.h文件中

<2>标准头文件结构

避免头文件多次包含,必须使用标准头文件结构。

#ifndef _文件名_H__

#define _文件名_H__

// 声明

#endif

使用条件编译和宏,保证头文件在一个编译单元中只会#include一次。

#pragma once指令也起到相同作用,但是并不是所有编译器支持。

分析

ecff50318dec

在cord.cpp 中第一次遇到coordin.h的文件的时候,它的内容会被读取,找到#ifndef COORDIN_H_,并且会给COORDIN_H_设定一个值,之后再次看到coordin.h头文件时,COORDIN_H_就已经被定义了,coordin.h的内容就不会再次被读取了。

误区:

(1)#include不是用来引入库的,是告诉编译器函数声明,

(2)头文件只有函数原型,函数实现在.a(Unix)或者.lib(Windows)中。

(3)现代的C语言编译器会默认引入所有的标准库。

11. 变量的作用域和生存周期

(1)作用域

<1>作用域是什么?

在什么范围内可以访问这个变量。

<2>作用域怎么用?

局部变量的作用域在变量定义的大括号以内。

(2)生存周期

<1>生存周期是什么?

变量什么时候出现到什么时候灭亡。对于局部变量,生存期与作用域一致。

<2>使用

不要返回局部变量的地址(注意是地址,不是值),返回局部变量的值是可以的。

认识

string& test_str(){

string str = "test";

return str;

}

int main(){

string& str_ref = test_str();

cout << str_ref << endl;

return 0;

}

A.编译警告

B.返回局部变量的引用,运行时出现未知错误

C.正常编译且运行

D.把代码里的&都去掉之后,程序可以正常运行

分析:

ABD。

(1)在C语言中,局部变量是分配在栈空间上的, 当函数调用结束后,由编译器释放.

(2)通过调用test_str得到了他的局部变量的内存地址, 然而在main函数中调用函数时,这个内存地址被”破坏”了,类似于野指针。在c语言中,一种典型的错误就是将一个指向局部变量的指针作为函数的返回值

(3) 如果返回指针(变量地址),应该返回堆区或者全局区的地址(全局变量或者静态变量)

(3)同名隐藏

在相同作用域中,同名变量会报错;在不同的作用域中,内部变量会隐藏外部变量,

int main() {

int n = 1;

{

printf("n = %d\n",n);//1

int n=10;

printf("n = %d\n",n);//10

n = 20;

}

printf("n = %d\n",n);//1

}

12.变量分类

(1)本地变量/局部变量

<1>概念

在大括号内定义的变量就是本地变量/局部变量。

<2>特点

1.本地变量是定义在代码块内的,可以定义在函数的块内,可以定义在语句的块内(for),可以定义在一个随意的大括弧里面。

2.程序进入块前,内部变量不存在,离开时失效。

3.块外定义的变量,块内仍然有效。(反过来不行)

函数的每次运行,都会产生一个独立的变量空间,在这个空间中的变量,是函数这次运行独有的。

1.定义在函数内部的变量就是本地变量;2.参数也是本地变量

<3>初始化

1.本地变量不会默认初始化

2.参数在进入函数时被初始化。

本地变量/局部变量的生存期和作用域都是在大括号内。

(2)全局变量

<1>定义

定义在函数外面的变量称为全局变量。

int n;//全局变量

int main(){

int m;//局部变量

}

<2>特点

全局变量有全局的生存周期和作用域。

1.不属于任何函数。

2.所有函数内部都可以使用。

<3>初始化

没有初始化的全局变量会自动初始化为0。

只能用编译时刻已知的值初始化全局变量。(只能用常量来初始化全局变量)

初始化发生在main()前。

<4>同名隐藏

如果函数内部存在与全局变量同名的变量,则全局变量被隐藏。

全局变量有全局的生存周期和作用域。

(3)局部静态变量

<1>定义

在本地变量定义时加上static变成静态本地变量。(只初始化1次)

<2>特点

当函数离开时,静态局部变量会继续存在并保存其值。

int inc(){

static int n = 1;

n = n + 1;

return n;

}

int main(){

printf("%d\n",inc());//2

printf("%d\n",inc());//3

printf("%d\n",inc());//4

}

(1)静态本地变量的初始化在第一次进入函数时执行,以后进入函数会保持离开的值。

(2)静态本地变量是特殊的全局变量,具有全局生存周期和局部作用域。

(4)全局静态变量

<1>定义

在全局变量前加上关键字static。

<2>全局变量与全局静态变量区别

1.若程序由一个源文件构成时,全局变量与全局静态变量没有区别。

2.若程序由多个源文件构成时:

非静态的全局变量的作用域是整个源程序,非静态的全局变量在各个源文件中都是有效的,而静态全局变量则限制了其作用域, 即只在定义该变量的源文件内有效, 在同一源程序的其它源文件中不能使用它。

3.非静态全局变量具有外部链接的静态,可以在所有源文件里调用,除了本文件,其他文件可以通过extern的方式引用。

extern int a,b;//在.h文件中全局变量的声明

(5)变量的作用域和生命期总结

作用域

变量或函数在运行时候的有效作用范围 。

生命期

变量或函数在运行时候的没被销毁回收的存活时间。

ecff50318dec

(6)static关键字小结

static在C语言里面既可以修饰变量,也可以修饰函数。

<1>static变量

静态局部变量:在函数中定义的,生命周期是整个源程序,但是作用域和局部变量没区别。只能在定义这个变量的函数范围内使用,而且只在第一次进入这个函数时候被初始化,之后的初始化会跳过,并保留原来的值。退出这个函数后,尽管这个变量还在,但是已经不能使用了。

静态全局变量:全局变量本身就是静态存储的,但是静态全局变量和非静态全局变量又有区别:

1.全局变量:变量的作用域是整个源程序,其他源文件也可以使用,生命周期整个源程序。

2.静态全局变量:变量的作用域范围被限制在当前文件内,其他源文件不可使用,生命周期整个源程序。

静态变量的生命周期是整个源程序,而且只能被初始化一次,之后的初始化会被忽略。(如果不初始化,数值数据将被默认初始化为0, 字符型数据默认初始化为NULL)。

<2>static函数(内部函数)

只能被当前文件内的其他函数调用,不能被其他文件内的函数调用,主要是区别非静态函数(外部函数)。

1.在函数前面加上static就使它成为只能所在编译文件中使用的函数。

2.在全局变量前加上static使它成为只能所在编译文件中使用的全局变量

(7)实践经验

<1>不要返回本地变量的指针

返回本地变量的地址是危险的。

返回全局变量或静态本地变量的地址是安全的。

返回函数内的动态内存是安全的,但注意要记得释放。

最好的做法是返回传入的指针。(常用)

<2>慎用静态变量

不要使用全局变量在函数间传递参数和结果。

尽量避免使用全局变量。

使用全局变量和静态本地变量的函数是不可重入的,是线程不安全的。

13.内存

(1) 结构体字节对齐

在C语言里,结构体所占的内存是连续的,但是各个成员之间的地址不一定是连续的。所以就出现了"字节对齐"。

字节对齐默认原则

结构体变量的大小,一定是其最大的数据类型的大小的整数倍,如果某个数据类型大小不够,就填充字节。

结构体变量的地址,一定和其第一个成员的地址是相同的。

struct Box{

int height;

char a[10];

double width;

char type;

};

int main(void) {

struct Box box;

printf("box = %p\n", &box);

printf("box.height = %p\n", &box.height);

printf("box.a = %p\n", box.a);

printf("box.width = %p\n", &box.width);

printf("box.type = %p\n", &box.type);

printf("box = %ld\n", sizeof(box));

}

ecff50318dec

(2)内存四区

ecff50318dec

image.png

ecff50318dec

<1>栈区(stack)

由编译器自动分配和释放,主要是存放函数参数的值,局部变量的值。

比如:int a; int *p; 这儿的a和p都存放在栈中

<2>堆区(heap)

由程序员自己申请分配和释放,需要malloc()、calloc()、realloc()函数来申请,用free()函数来释放如果不释放,可能出现指针悬空/野指针。

函数不能返回指向栈区的指针,但是可以返回指向堆区的指针。

<3> 数据区(data)

** 数据区在程序结束后由操作系统释放**

存放常量。

包含字符串常量和其他常量。 char *p = "I love u"; 指针p指向的这块内存属于常量区。

存放全局变量和静态变量。

初始化的全局变量和静态变量在一块区域(data段);未初始化的全局变量和未初始化的静态变量在相邻的另一块区域,称作BSS(Block Started by Symbol:以符号开始的块);

<4>代码区(code)

用于存放编译后的可执行代码,二进制码,机器码。

int a; //申请栈区内存

a = 4; //指向的代码,放在代码区。

(3)堆和栈的区别(重要)

No.

比较方面

1

管理方式

由系统自动管理,以执行函数为单位

由程序员手动控制

2

空间大小

空间大小编译时确定(参数+局部变量)

具有全局性,总体无大小限制。

3

分配方式

函数执行,系统自动分配;函数结束,系统立即自动回收。

使用new/malloc()手动申请;使用delete/free()手动释放

4

优点

使用方便,不需要关心内存申请释放。

可以跨函数使用。(可以返回指向堆区的指针)

5

缺点

只能在函数内部使用。

容易造成内存泄露。

ecff50318dec

(4)显示目标文件区段大小

size命令:

ecff50318dec

dec与hex是前面三个区域的和,dec是十进制,hex是十六进制。

各区段的含义

No.

区段

名称

含义

1

text

代码段(code segment/text segment)

存放程序执行代码的内存区域。该区域的大小在运行前已确定,且通常属于只读。可能包含一些只读的常数变量,例如字符串常量等。

2

data

数据段(data segment)

存放程序中已初始化的全局变量的内存区域。数据段属于静态内存分配。

3

bss

BSS段(bss segment)

存放程序中未初始化的全局变量的内存区域。BSS是英文Block Started by Symbol的简称。BSS段属于静态内存分配。

没有显示的区段

No.

区段

含义

1

栈(stack)

存放程序临时创建的局部变量,也就是函数括弧{}中定义的变量(不包括static声明的变量)。在函数被调用时,参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。

2

堆(heap)

存放程序动态分配的内存段,大小并不固定,可动态扩张或缩减。当调用malloc()等函数分配内存时,堆被扩张;当调用free()等函数释放内存时,堆被缩减。

ecff50318dec

14.二进制

(1)位运算

位运算说穿了,就是直接对整数在内存中的二进制位进行操作.

<1>按位运算

No.

操作符

功能

1

&

按位与

2

|

按位或

3

~

按位取反

4

^

按位异或(相异为真)

<2>运算规则

ecff50318dec

<3>按位与

1.让某一位或某些位为0(清零)

int n = 0xFFFF;

n = n & 0x0010;//0x10

2.取一个数中某些指定位:

比如a=23,我想取a的二进制的后面4位数,那么可以找一个后4位是1其余位是0的数b,即b=0x0f(十六进制,转换为二进制为00001111),a&b就得到了a的后四位。

a:00010111

b:00001111

a&b:00000111

3.保留指定位:

比如a=23(用8bit表示),我想保留其二进制的第4和第6位(最左边为第1位),其余位置0。那么可以找一个第4和第6位是1其余位是0的数b与a进行按位与运算.

a:00010111

b:00010100

a&b:00010100

4.应用:

判断某一位是否为1;

设置一个只有某一位是1的数,其余为是0,然后和判断的数进行位与。

判断一个数是否是偶数;

与1进行位与,如果其二进制的最末尾是0表示偶数,为1表示奇数。

结论:任何二进制位与0能实现置0;与1保持原值不变.

<4>按位或

1.让某一位或某些位为1,其余位不变。

int n = 0x0000;

n = n | 0x0010;

2.拼接两个二进制数。

int a = 0xab00;

int b = 0x0012;

int c = a|b;//0xab12

<5>按位取反

1,得到全部为1的数字~0

int n = ~0;// 等同于0xFFFF

2.使数字的部分清零x& ~7。

int n = 0xFFFF;

n = n & ~7;

<6>按位异或

1.两个相等数异或结果为0。

int n = 0x1234;

n = n^n;

2.对同一个变量两次异或相同值,变回原值。

int a = 0x1234;

int b = 0x1357;

a = a^b;//0x163

a = a^b;//0x1234

3.0和任何数字(或字符)异或都为任何数(或字符)

int n=21;

n = n^0;//21

4.应用:

把一个数据的某些位翻转,即1变为0,0变为1

如要把a的奇数位翻转,可以对a和b进行“按位异或”运算,其中b的奇数位置为1,偶数位置为0。

交换两个值,不用临时变量(***)

x ^= y;

y ^= x;

x ^= y;

加密解密

加密程序(a^b),解密程序是加密程序的逆过程,这里的加密和解密程序是完全相同的,原因是(a^b)^b=a。

5.例题

[136.只出现一次的数字]:

思路:每个数字全部异或,相同的会为0,直到最后一个数字。

[389.找不同]:

思路:字符也可以异或,相同字符异或为0.

逻辑运算与按位运算

1.逻辑运算结果只有0和1两种值,按位运算有多种值。

2.逻辑运算相当于把所有的非零值都变成1,再按位运算。

(2)移位运算

在移动过程中相当于操作二进制数

No.

操作符

功能

1

<<

左移

2

>>

右移

<1>左移

i<

左移一位相当于乘以2,两位乘以4。

1<<1;//2

1<<2;//4

1<<3;//8

<2>右移

i>>j表示i中所有位向右移动j个位置,对于unsigned类型,左边填入0;对于signed类型,左边填入符号位。

右移一位相当于除以2,再取整。

14>>1;//7

14>>2;//3

14>>3;//1

14>>4;//0

1.应用:

循环移位的实现:

将一个无符号整数x的各位进行循环左移n位的运算,即把移出的高位填补在空出的低位处。

b=(a<>(16-n)) ;

求x的绝对值

y = x >> 31 ;//二进制最高位

return (x^y)-y ; //or: (x+y)^y

<3>位移运算与乘除运算

ecff50318dec

<4>综合

求一个无符号数的二进制中1的个数。

//就是判断最低位是否为1(最右边)

int numof1(int n){

int count=0;

while(n){

if(n & 1){

++count;

}

n = n >> 1;

}

return count;

}

(3)位域

<1>定义

位域是又称作位段,是把一个字节中的二进位划分为几个不同的区域。

<2>作用

节省空间,有些信息不需要占用一个完整的字节。

<3>使用

1.定义位域

定义位域与结构定义相仿。

struct 位域结构名{

类型 位域名:位域长度;

};

为了保证位域的可以移植性,成员类型通常为unsigned int和int,C99可以使用bool。

示例:

struct Byte{

unsigned int b1:1;

unsigned int b2:1;

unsigned int b3:1;

unsigned int b4:1;

unsigned int b5:1;

unsigned int b6:1;

unsigned int b7:1;

unsigned int b8:1;

};

2.位域变量

定义和使用位域变量与结构体相同。每个域有一个域名,允许在程序中按域名进行操作。

struct Byte a;

3.位域大小

整个结构体的总大小为最宽基本类型成员大小的整数倍。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值