目录
1.常见集成开发环境(IDE/Integrated Development Environment )
C语言主体部分:
0.编译步骤与C的合法标识符:
0.1编译步骤
一个源程序到一个可执行程序的过程:预编译、编译、汇编、链接
1,预编译:主要处理源代码文件中的以“#”开头的预编译指令。处理规则见下
1.删除所有的#define,展开所有的宏定义。
2.处理所有的条件预编译指令,如“#if”、“#endif”、“#ifdef”、“#elif”和“#else”。
3.处理“#include”预编译指令,将文件内容替换到它的位置,这个过程是递归进行的,文件中包含其他文件。
4.删除所有的注释,“//”和“/**/”。
5.保留所有的#pragma 编译器指令,编译器需要用到他们,如:#pragma once 是为了防止有文件被重复引用。
6.添加行号和文件标识,便于编译时编译器产生调试用的行号信息,和编译时产生编译错误或警告是能够显示行号。
C语言的宏替换和文件包含的工作,不归入编译器的范围,而是交给独立的预处理器。
C语言中源代码文件的文件扩展名为.c,头文件的文件扩展名为.h,经预编译之后,生成xxx.i文件。
在C++,源代码文件的扩展名是.cpp或.cxx,头文件的文件扩展名为.hpp,经预编译之后,生成xxx.ii文件。
2,编译:把预编译之后生成的xxx.i或xxx.ii文件,进行一系列词法分析、语法分析、语义分析及优化后,生成相应的汇编代码文件。
3、汇编:将汇编代码转变成机器可以执行的指令(机器码文件)。
4、链接:各个模块之间相互引用的部分都处理好,使得各个模块之间能够正确地衔接。(就像拼图,凸起和凹槽的位置一定一一对应,否则…)
.链接的过程:地址和空间的分配、符号决议(也叫“符号绑定”,倾向于动态链接)和重定位
(自牛客网@李㐅㐅青 )
关于include:
在C语言源程序的开始处通常加上预处理命令 #include <stdio.h> 的原因是
stdio.h文件中包含标准输入输出函数的函数声明,通过引用此文件以便能正确使用printf,scanf等函数
inlcude 语句的实质是,在预编译阶段,把相应的头文件copy到当前行
关于<>和“”的区别和异同:
#inlcude <> 首先只搜索系统目录,不会搜索本地目录.比如你自己写一个头文件,你用#include <>会出错. 此方法会快一些
#inlude ""首先搜索本地目录,如果本地目录没有才会搜索系统目录.
可以把系统的文件 放到当前目录下 改成 "" 可以优先使用
(来自牛客网@小雨落梧桐)
0.2合法标识符
- 一个标识符以字母 A-Z 或 a-z 或下划线 _ 开始,后跟零个或多个字母、下划线和数字(0-9)
- 数字不可以开头
- 不允许出现标点字符,比如 @、$ 和 %
1.常见集成开发环境(IDE/Integrated Development Environment )
是用于提供程序开发环境的应用程序,一般包括代码编辑器、编译器、调试器和图形用户界面等工具。集成了代码编写功能、分析功能、编译功能、调试功能等一体化的开发软件服务套。所有具备这一特性的软件或者软件套(组)都可以叫集成开发环境。
1) Visual Studio系列及MFC
2)Qt
2.32个关键字
- 数据类型关键字(12个)
char,short,int,long,float,double,
unsigned,signed,struct(结构体),union(共用体),enum(枚举),void
- 控制语句关键字(12个)
if,else,switch,case,default,for,do,while,break,continue,goto,return
- 存储类关键字(5个)
auto(自动变量),extern(外部类型变量),register(寄存器类型变量),static(静态类型变量),const
- 其他关键字(3个)
sizeof,typedef,volatile
3.数据类型
3.1常量
在程序运行过程中,其值不能被改变,成为常量。
两种定义方式
方式1:使用关键字define
#define identifier value
//eg:
#define P 3.1415
//C中最稳定的定义常量的方法
方式2:使用关键字const
在C中不稳定,可以通过指针来修改,但是在C++中是稳定和安全的
const type variable = value;
//eg:
const int test_int = 10;
字符常量:
在 C 中,有一些特定的字符,当它们前面有反斜杠时,它们就具有特殊的含义,被用来表示如换行符(\n)或制表符(\t)等。下表列出了一些这样的转义序列码:
转义序列 | 含义 |
---|---|
\\ | \ 字符 |
\' | ' 字符 |
\" | " 字符 |
\? | ? 字符 |
\a | 警报铃声 |
\b | 退格键 |
\f | 换页符 |
\n | 换行符 |
\r | 回车 |
\t | 水平制表符 |
\v | 垂直制表符 |
\ooo | 一到三位的八进制数 |
\xhh . . . | 一个或多个数字的十六进制数 |
3.2变量
在程序运行过程中,其值可以改变,称为变量 。
bit(比特) 一个二进制代表一位,一个位只能表示0或1两种状态
Byte(字节)计算机存储的最小单位是字节
1 Byte = 8 bit
3.2.1整数类型及字符类型
下表列出了关于标准整数类型的存储大小和值范围的细节:
类型 | 存储大小 | 值范围 |
---|---|---|
char | 1 字节 | -128 到 127 或 0 到 255 |
unsigned char | 1 字节 | 0 到 255 |
signed char | 1 字节 | -128 到 127 |
int | 2 或 4 字节 | -32,768 到 32,767 或 -2,147,483,648 到 2,147,483,647 |
unsigned int | 2 或 4 字节 | 0 到 65,535 或 0 到 4,294,967,295 |
short | 2 字节 | -32,768 到 32,767 |
unsigned short | 2 字节 | 0 到 65,535 |
long | 4 字节 | -2,147,483,648 到 2,147,483,647 |
unsigned long | 4 字节 | 0 到 4,294,967,295 |
3.2.2浮点类型
下表列出了关于标准浮点类型的存储大小、值范围和精度的细节:
类型 | 存储大小 | 值范围 | 精度 |
---|---|---|---|
float | 4 字节 | 1.2E-38 到 3.4E+38 | 6 位小数 |
double | 8 字节 | 2.3E-308 到 1.7E+308 | 15 位小数 |
long double | 16 字节 | 3.4E-4932 到 1.1E+4932 | 19 位小数 |
注意,各种类型的存储大小与系统位数有关,但目前通用的以64位系统为主。
以下列出了32位系统与64位系统的存储大小的差别(windows 相同):
注意:指针类型存储的是所指向变量的地址,所以32位机器只需要32bit,而64位机器需要64bit。
常与变量捆绑的关键字sizeof
sizeof() | 返回变量的大小。 | sizeof(a) 将返回 4,其中 a 是整数。 |
C语言中的浮点数有两种形式,一种为十进制小数形式,一种为指数形式,
其一般形式为a E n,a为十进制数,n为十进制整数,都不可省略。
A中e3非法,因为只有e3没有尾数,其余两数都是合法的浮点数;
B中123是整数,不是浮点数,2e4.2阶码部分4.2是浮点数,不是整数,故是非法的,.e5尾数部分不能只有小数点,也是非法的;
C中的三个数均是合法的浮点数;
D中的.234和1e3也是合法的,只有-e3非法。
(来源牛客网@1185925亓乂)
3.3字符、字符串及字符串处理函数
关于字符和字符串(字符数组):
字符串是有字符组成的,但是多了一个字符串结束符,而在赋值的方式也不一样,字符是单引号,字符串是双引号。
字符使用普通char变量,而字符串使用char类型的数组。
在 C 语言中,字符串实际上是使用 null 字符 '\0' 终止的一维字符数组。因此,一个以 null 结尾的字符串,包含了组成字符串的字符。
下面的声明和初始化创建了一个 "Hello" 字符串。由于在数组的末尾存储了空字符,所以字符数组的大小比单词 "Hello" 的字符数多一个。
char greeting[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
注意,规定,字符串必须以'\0'作为结尾,所以,如果没有'\0',比如;
char greeting[6] = {'H', 'e', 'l', 'l', 'o'};
就不能算是字符串。
依据数组初始化规则,可以把上面的语句写成以下语句:
char greeting[] = "Hello";
char str1[12] = "Hello";
char str2[12] = "World";
printf("message: %s\n", str1 );
以下是 C/C++ 中定义的字符串的内存表示:
其中,‘\0’表示字符串结束符,很多字符串处理函数,对字符串的处理都是止步于字符串结束符'\0'。
关于字符串处理函数(17个):https://mp.csdn.net/postedit/99708133
关于内存分配:
char str1[] = "hello world";
char str2[] = "hello world";
char str3 = "hello world";
char str4 = "hello world";
为了节省空间,C/C++把常量字符串放到单独的一个内存区域。当几个指针赋值给相同的常量字符串时,它们实际上会指向相同的内存地址。但是用常量初始化数组时情况不同。
str1和str2是两个字符串数组,我们会为他们分配长度为12字节的空间,并把hello world的内容复制到数组中去。
这是两个初始地址不同的数组,因此str1和str2的值也不同。
(hello world在常量区,给数组初始化,要将常量区的内容拷贝一份出来,初始化多少个数组拷贝多少份)
str3和str4是两个字符串指针,hello world会从常量区拷贝到内存中,指针指向同一块内存。
(hello world在常量区,给指针初始化,要将常量区的内容拷贝一份放在内存中,初始化多少个指针都指向内存的同一个地址)
同样,sizeof和strlen也是有区别的,sizeof计算长度时会算上'\0';
char a[] = {'a','b','c','d','e','f','g','\0'};
qDebug()<<"sizeof(a):"<<sizeof(a);
qDebug()<<"strlen(a):"<<strlen(a);
定义char dog[]="wang\0miao";那么sizeof(dog)与strlen(dog)分别是多少:
sizeof返回数组所占的字节数,'wang' 'miao'共占8字节,显式'\0'占1字节,字符串末尾隐式'\0'占1字节,共10字节。
strlen返回字符串的长度,以遇到'\0'结束符为准,因此为4。(来源牛客网@L.K.)
常见的大小写ASCII值
大写:
A → 65,B →66,C → 67,D → 68,E →69,
F → 70,G →71,H → 72,I →73,J → 74,
K →75,L → 76,M → 77,N → 78,O → 79,
P → 80,Q → 81,R → 82,S → 83,T → 84,
U → 85,V → 86,W → 87,X → 88,Y → 89,Z → 90
小写:
a → 97,b → 98,c → 99,d → 100,e → 101,f → 102,
g → 103,h → 104,i → 105,j → 106,k → 107,l → 108,
m → 109,n → 110,o → 111,p→ 112,q → 113,r → 114,
s → 115,t → 116,u → 117,v → 118,w → 119,x → 120,y → 121,z → 122
题目:字符串必须以字符串结束符号结束
如下代码输出结果是什么?
#include<stdio.h>
char *myString()
{
char buffer[6] = {0};
char *s = "Hello World!";
for (int i = 0; i < sizeof(buffer) - 1; i++)
{
buffer[i] = *(s + i);
}
return buffer;
}
int main(int argc, char **argv)
{
printf("%s\n", myString());
return 0;
}
同样需要注意的是,char类型的范围是:-128---+127
题目:
char a=101;
int sum=200;
a+=27;sum+=a;
printf("%d\n",sum);
char类型的范围是-128---+127,当a+=27 ,之后a的值超出可表示范围会变为-128.
sum的值为72
同时还有一点要注意,打印字符串的首地址,就是打印整个字符串
题目:
结果:afternoon
a指向第一个元素的首地址,又因为a是指针数组,这就成了一个二维指针,存的全是字符串的地址
p等于a,那么p增1,就是移动到了afternoon的位置
解引用后,就是指向afternoon的首地址,打印出来的就是字符串
答案:
即便在定义中没有充分使用申请的空间,也不意味着 china 后面存储的是 beijing ,china后面剩余的5个字节依旧存在
3.3运算符优先级
下表将按运算符优先级从高到低列出各个运算符,具有较高优先级的运算符出现在表格的上面,具有较低优先级的运算符出现在表格的下面。在表达式中,较高优先级的运算符会优先被计算
https://www.runoob.com/cprogramming/c-operators.html
优先级:
单目运算符 >算术运算符 >移位 >比较 >按位 >逻辑 >三目运算符 >赋值运算符
题目1:
int *p[4] 与 int *(p[4] ) 等价
后缀运算符,++,--,[ ],->,.,是优先级最高的运算符
[]优先级比*高,p和[ ]结合,表示数组,且有4个元素,再和*结合,表示数组元素是指针类型。
题目2:
假定x的值为5,y的值为6,则表达式x++*--y的值为 x++*--y表达式为5*(6-1)=25
题目3:
已有声明"int x,a=3,b=2;",则执行赋值语句"x=a>b++?a++,b++"后,变量x、a、b的值分别为 3,4,3
x = 3>2?3,2;x选择a++,x=3;
b = 3;a=4;
题目4:
关系运算符优先级大于相等运算符
int main()
{
int a=1,b=2,m=0,n=0,k;
k=(n=b<a)&&(m=a) ;
printf("%d,%d\n",k,m);
return 0;
}
n = b < a;先进行关系运算符,也就是b<a,显然为假,那么n就等于0,逻辑运算符与一旦左侧为假,那么必然为假,不会在执行右侧,所以m还是等于0,最终输出
0,0
3.4函数
函数由四个部分组成
- 函数的声明
- 函数的原型
- 函数的返回值
- 函数的参数
函数原型的语法结构如下,意在表明,本函数进行的到底是怎样的操作,根据需求,规定相应的返回值。
return_type function_name( parameter list )
{
body of the function
}
函数的声明,C中,需要放置于C中main()函数,C++中,需要放置于类声明中。
return_type function_name( parameter list );
而声明及原型中的parameter list称为形式参数
在该函数被调用的过程中,被赋予的参数称为实际参数,如下:
void main()
{
.....
function_name( parameter list );
.....
return 0;
}
注意:参数之间用逗号隔开,并且需要声明参数的数据类型,比如:
void function_test(int list_frist,double list_second,float list_third)
{
........
.
.
.
........
}
注意:数组名作为函数参数时会自动退化为指针
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
double a[10];
qDebug()<<"实际中的数组名占用空间:"<<sizeof(a);
Test(a);
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::Test(double a[])
{
qDebug()<<"函数中的数组名占用空间:"<<sizeof(a);
}
结果:
在Qt编译时,编译器会出现警告:
所以想通过函数名作为参数,在函数中计算数组长度不能使用常规的方式,要么是最后一位有特征标志位,要么把数组长度当参数一起传递。
字符串不用担心这个问题,什么字符串不论字符串数组还是字符串指针最后一位都是特征标志位,字符串结束符 \0
3.4.1函数与作用域
int * MyFunc()
{
int a = 10;
return &a;
}
void main()
{
int * p = MyFunc();
pringtf("*p = %d\n",*p);
}
a的作用域在MyFunC();调用结束后,就被释放了,返回的指针所指向的内存,其中保存的是不是10都不重要了,这块内容随时有可能会被重新利用,会被其他内容代替,所以在调用函数时一定要考虑到作用域的问题。指针指向的内存空间是否是确定的。
以上程序的操作是非常危险的。
3.4.2函数传参的具体工作过程
void allocataSpace(char *pp)
{
pp = malloc(100);
memset(pp,0,100);
strcpy(pp,"hello world");
}
void test02()
{
char * p = NULL;
allocataSpace(p);
printf("p = %s\n",p);
}
结果是无法输出理想中的hello world。
首先,malloc在堆区分配空间,地址为0x####%%,将这个地址赋给allocataSpace函数中的变量pp,pp保存是传递过来参数的值,及p的值,NULL;
之后因为要拷贝字符串,字符串自动在常量区进行创建,之后将内容拷贝给堆区,并将堆区存有字符串的首地址给pp。。
整个函数过程完成,之后回到test02中,函数完成调用,栈区凡是和allocataSpace相关的变量全部销毁。
最终整个过程中,test02中的p没有任何变化,且丢失了堆区内存的首地址,内存泄漏。
关于函数,会在函数执行中,将参数和函数内定义的变量在栈区开辟一片内存。结束后销毁,所以在函数传参的时候务必要注意
最大的问题,就是实参和形参之间,没有联系! 单纯的按值传递,形参和实参类型一样,单纯的是赋值,单纯的把p指向的值传递出去。
关于以上问题,该如何修改?如下
下面使用到了二级指针的部分,因为传递来的是一级指针的地址,需要使用二级指针进行接收,就如同传递普通变量的地址,使用一级指针来接收一样,关于二级指针后文有详细介绍。
void allocataSpace(char **pp)
{
char * temp = malloc(100);
memset(temp,0,100);
strcpy(temp,"hello world");
*pp = temp;
}
void test()
{
char * p = NULL;
allocataSpace(&p);
printf("p = %s\n",p);
}
首先,allocataSpace函数中的变量pp保存的是test函数中p的地址。不同于第一个函数,保存的是NULL。
malloc在堆区分配空间,地址为0x####%%,将这个地址赋给allocataSpace函数中的变量temp;
之后因为要拷贝字符串,字符串自动在常量区进行创建,之后将内容拷贝给堆区,并将堆区存有字符串的首地址给temp。
最后取pp中地址所指向的内存,并将其换成指向堆区的地址。
整个函数过程完成,之后回到test02中,函数完成调用,栈区凡是和allocataSpace相关的变量全部销毁。
二者最大的区别在于,函数对栈区内内存的改写,到底最后哪儿些部分是实打实影响到调用该函数的部分。
从以上两个例子中,也不难看出函数运行过程中对参数及函数内定义的变量分配空间及销毁的过程。
3.4.3函数运行过程中内存的分配
int * getSpace()
{
int * p = malloc(sizeof(int) * 5);
if( NULL == p )
return 0;
for(int i = 0;i<5;i++)
{
p[i] = 100 + i;
}
return p;
}
p:在栈上被创建
但p所指向的地址,是在堆区上。
getSpace()被调用后,p被释放,但是p所指向的内存,依旧在堆区上。
void main()
{
int * ret = getSpace();
for( int i = 0;i<5;++i)
{
printf("%d",ret[i]);
}
free(ret);
ret = NULL;
}
所以返回堆区的首地址后,依旧可以访问堆区,因为堆区释放是程序员人工释放,函数结束调用后自动释放的只是在栈区的变量。
注意:
1,堆区一定要拿到数据段的首地址才能释放,否则无法释放。
2,只要是连续的内存空间,都可以使用指针名【】来进行访问。
3.4.4函数参数传递
传递参数分为两个部分
- 按值传递
- 按地址传递(自然包括引用)
那么并不是所有的数据类型都使用按地址传递效率最高
内置类型(int,float,double,char,....)按值传递方式的效率要高于按地址传递
编译器会采用效率最高的方式来实现参数传递
对于自定义类型,因为有可能调用构造函数和析构函数等问题,使用按值传递或者按地址传递,效率会高。