最近有很多读者朋友和朋友交朋友getsC语言的意思有疑问。有网友整理了相关内容,希望能回答你的疑惑。关于,这个网站已经为你找到了问题的答案,希望对你有所帮助。
文章目录
-
什么是C
-
C 的发展史
-
C 关键字
-
命名空间
-
命名空间的定义
-
1.命名空间的一般定义
-
2.命名空间可以嵌套
-
3.同一项目允许有多同名的命名空间,最后,编译器将在同一命名空间中合成。
-
使用命名空间
-
1.加命名空间名称和作用域限定符
-
2.使用usingnamespace 引入命名空间名称
-
3.使用using引入命名空间中的成员
-
C 输入输出
-
缺省参数
-
全缺省
-
半缺省参数
-
函数重载
-
函数重载的原理
-
extern“C”
-
引用
-
引用的特征
-
1.定义时引用必须初始化
-
2.一个变量可以引用多个参数
-
3.一旦引用实体,不能再引用其他实体
-
常引用
-
引用场景
-
1.引用作参数
-
二、引用作返回值
-
引用与指针的区别
-
内联函数
-
特性
-
c 哪些技术可以取代宏
-
auto关键字(C 11)
-
auto的使用细则
-
1.auto结合指针和引用使用
-
2.在同一行中定义多个变量
-
auto无法推导的场景
-
1.auto作为函数的参数
-
2.auto不能直接用来声明数组
-
基于范围的for循环(C 11)
-
范围for的语法
-
范围for的使用条件
-
1.for必须确定循环迭代的范围
-
2.实现迭代对象 和==的操作。
-
指针空值nullptr
-
C 98指针空值
-
C 11指针空值
什么是C
C语言是结构化和模块化的语言,适用于处理小规模程序。对于复杂的问题,大规模的程序需要高度
在抽象和建模中,C语言不合适。对于复杂的问题,大规模的程序需要高度
抽象和建模,C语言不合适。为了解决软件危机,计算机行业在20世纪80年代提出OOP(object
orientedprogramming:支持面向对象的程序设计语言应运而生。
1982年,BjarneStroustrup在C语言的基础上,博士介绍并扩展了面向对象的概念,发明了一种新的程序语言
言。为了表达语言与C语言的起源,命名为C 。因此:C 它是基于C语言的,可以用C语言生成的
言语的过程化程序设计,以抽象数据类型为特征的基于对象的程序设计,以及面向对象的程序设计
序设计。
C 的发展史
贝尔实验室,贝尔实验室本贾尼等人试图分析unix在核心中,试图模块化核心,然后在C语言的基础上扩展,增加类机制,完成可操作的预处理程序,称为Cwith classes。
随着时代的进步,语言的发展也在逐渐进步。让我们看看C 历史版:
C 关键字
C 总共有63个关键词:
画圈是C语言的关键词。这里要注意:false和true不是C语言的关键词。
命名空间
在C/C 变量、函数和类都很多,这些变量、函数和类的名称都会作用于整体作用域,可能会导致许多命名冲突。
使用命名空间的目的是本地化标识符和名称,避免命名冲突或名称污染,namespace关键词的出现是针对这个问题的。
命名空间的定义
需要定义命名空间namespace关键字,跟着命名空间的名字,然后连接一对{},{}就是命名
空间成员。
注:命名空间定义了一个新的功能域,命名空间中的所有内容都局限于命名空间
1.命名空间的一般定义
//1.变量可以定义为普通命名空间,函数也可以定义
namespacexjt
{
int printf=1;
int rand=2;
int Add(int a, int b)
{
return a b;
}
}
2.命名空间可以嵌套
//2.命名空间可以嵌套
namespacexjt
{
int printf=1;
int rand=2;
int Add(int a, int b)
{
return a b;
}
namespace xjt2
{
int a=0;
int Sub(int a, int b)
{
return a – b;
}
}
}
3. 同一项目允许有多同名的命名空间,编译器最终合成同一命名空间。
//3.在同一个项目中,允许有多个相同名称的命名空间,编译器最终间。
namespacexjt
{
int a=3;
int b=1;
}
会和上面的一起xjt合并命名空间
使用命名空间
让我们来看看这样一段代码
namespacexjt
{
int printf=1;
int rand=2;
int Add(int a, int b)
{
return a b;
}
}
#include
显然,直接打印printf这是不可能的,因为你这样调用它printf地址,所以会出现这样的结果,积极的调用方法是以下三种。
1.加命名空间名称和作用域限定符
符号:在C 称为作用域限定符,通过命名空间名称::命名空间成员,我们可以访问命名空间中应成员
2.使用using namespace 引入命名空间名称
但是这种方法有一些弊端,如果我们在命名空间中定义了一个名称printf如果变量,那么以后会namespacexjt如果引入命名空间,会造成命名污染。
第三种引入方法是为了解决这个问题。
第三种引入方法是为了解决这个问题。
3.使用using将命名空间的成员引入命名空间
这种方法可以防止命名的污染,因为它只引入了一部分。
C 输入和输出
新生儿会以自己独特的方式迎接这个崭新的世界,C 刚出来后,也算是一个新事物,那C++是否也应该向这个美好的世界来声问候呢?我们来看下C++是如何来实现问候的。
#include在C语言中有标准输入输出函数scanf和printf,而在C++中有cin标准输入和cout标准输出。在C语言中使用scanf和printf函数,需要包含头文件stdio.h。在C++中使用cin和cout,需要包含头文件iostream以及
std
标准命名空间
。
C++的输入输出方式与C语言更加方便,因为C++的输入输出不需要控制格式,例如:整型为%d,字符型为%c。
#include
注意:endl,这其中的l不是阿拉伯数字1,而是26个英文字母的l,它的作用相当于换行。
这里我们还要注意下cin的特点,他和C语言中的gets有些像,gets是遇到换行符停止,而cin是以遇到空格,tab或者换行符作为分隔符的,因此这儿输入helloworld会被空格符分隔开来。
这儿我输入的是helloworld,但因为输入时出现了空格,所以之后的内容并不会读入,因此arr中存的就是hello。
缺省参数
缺省参数是声明或定义函数时为函数的参数指定一个默认值。在调用该函数时,如果没有指定实参则采用该
默认值,否则使用指定的实参。
//缺省参数#include
全缺省
全缺省参数,即函数的全部形参都设置为缺省参数。
//全缺省#include
半缺省参数
voidfunc(int a, int b, int c=2)
{
cout << a << endl;
cout << b << endl;
cout << c << endl;
}
注意:?
1、半缺省参数必须从右往左依次给出,不能间隔着给。
//错误示例
voidfunc(int a, int b=2, int c)
{
cout << a << endl;
cout << b << endl;
cout << c << endl;
}
2、缺省参数不能在函数声明和定义中同时出现
//错误示例
//test.h
voidfunc(int a, int b, int c=3);
//test.c
voidfunc(int a, int b, int c=2)
{
cout << a << endl;
cout << b << endl;
cout << c << endl;
}
因为:如果声明与定义位置同时出现,恰巧两个位置提供的值不同,那编译器就无法确定到底该用那
个缺省值。
3、缺省值必须是常量或者全局变量。
//正确示例
intx=3;//全局变量
voidfunc(int a, int b=2, int c=x)
{
cout << a << endl;
cout << b << endl;
cout << c << endl;}
函数重载函数重载:是函数的一种特殊情况,C++允许在
同一作用域中声明几个功能类似的同名函数,这些同名函数的形参列表(参数个数或 类型 或 顺序
)
必须不同
,常用来处理实现功能类似数据类型不同的问题
#include
注意:若仅仅只有返回值不同,其他都相同,则不构成函数重载。
shortAdd(short left, short right)
{
return left+right;
}
intAdd(short left, short right)
{
return left+right; }
函数重载的原理
为什么C++支援函数重载,而C语言不可以了?
这里我们就要回顾一下以前的知识了,在运行到执行文件前,要经过:
预编译,编译,汇编,链接
这些阶段
其实问题就出在编译完之后的汇编阶段,因为在这里C++和C语言有着些许的不同,下面我们来看看:
采用C语言编译器编译之后
采用C++编译器编译之后
总结:
1.其实归根到底,还是因为C编译器和C++编译器对函数名的修饰不同。在gcc下的修饰规则是:【_Z+函数长度+函数名+类
型首字母】。2.这其实也告诉我们为什么函数的返回类型不同,不会构成函数重载,因为修饰规则并不会受返回值的影响。
extern “C”
有时候在C++工程中可能需要将某些函数按照C的风格来编译,在函数前加extern“C”,意思是告诉编译器,
将该函数按照C语言规则来编译。比如:tcmalloc是google用C++实现的一个项目,他提供tcmallc()和tcfree两个接口来使用,但如果是C项目就没办法使用,那么他就使用extern“C”来解决。
引用
引用不是新定义一个变量,而是给已存在变量取了一个别名
,编译器不会为引用变量开辟内存空间,它和它
引用的变量
共用同一块内存空间
。
类型&引用变量名(对象名)=引用实体;
#include
注意:
引用类型必须和引用实体是同种类型的
引用的特征
1.引用在定义时必须初始化
//正确示例
inta=10;
int&b=a;//引用在定义时必须初始化
//错误示例
inta=10;
int&b;//定义时未初始化b=a;
2.一个变量可以有多个引用
inta=10;
int&b=a;
int&c=a;int&d=a;
3.引用一旦引用了一个实体,就不能再引用其他实体
inta=10;
int& b=a;
int c=20; b=c;//你的想法:让b转而引用c
但实际的效果,确实将c的值赋值给b,又因为b是a的引用,所以a的值见解变成了20。
常引用
上面提到,引用类型必须和引用实体是同种类型的。但是仅仅是同种类型,还不能保证能够引用成功,这儿我们还要注意可否可以修改的问题。
voidTestConstRef()
{
const int a=10;
//int& ra=a; // 该语句编译时会出错,a为常量
const int& ra=a;
// int& b=10; // 该语句编译时会出错,b为常量
const int& b=10;
double d=12.34;
//int& rd=d; // 该语句编译时会出错,类型不同
const int& rd=d;
}
这里的a,b,d都是常量,常量是不可以被修改的,但是如果你用int&ra等这样来引用a的话,那么引用的这个a是可以被修改的,因此会出问题。
下面我们来看这么一段代码:
#include
这个引用对吗?想要弄明白这个问题,首先要明白隐士类型提升的问题,在这里int到double存在隐士类型的提升,而在提升的过程中系统会创建一个常量区来存放a类型提升后的结果。因此到这儿,这段代码一看就是错了,因为你隐士类型提升时a是存放在常量区中的,常量区是不可以被修改的,而你用double&ra去引用他,ra这个引用是可以被修改的。
加个const就可以解决这个问题。
#include
注意:将不可修改的量用可读可写的量来引用是不可以的,但是反过来是可以的,将可读可写的量用只可读的量来引用是可以的。
引用的使用场景
1.引用做参数
还记得C语言中的交换函数,学习C语言的时候经常用交换函数来说明传值和传址的区别。现在我们学习了引用,可以不用指针作为形参了。因为在这里a和b是传入实参的引用,我们将a和b的值交换,就相当于将传入的两个实参交换了。
//交换函数
voidSwap(int& a, int& b)
{
int tmp=a;
a=b;
b=tmp;}
2.引用做返回值
当然引用也能做返回值,但是要特别注意,我们返回的数据不能是函数内部创建的普通局部变量,因为在函数内部定义的普通的局部变量会随着函数调用的结束而被销毁。我们返回的数据必须是被static修饰或者是动态开辟的或者是全局变量等不会随着函数调用的结束而被销毁的数据。
不加static的后果
你是不是疑惑为什么打印的不是2而是7了?
这人就更奇怪了,为什么中间加了一句printf,就打印随机值了?下面我们来看看分析:
为什么会出现随机值,因为你在函数里定义的变量是临时变量,出了函数函数是会销毁的,这时它就随机指向内存中的一块空间了
。所以在引用做函数返回值时最好还是给在函数中定义的变量加上static。
这时你觉得你真的懂这段代码了吗?
#include
可能你会好奇了?为什么这儿是3了?下面来看看分析
其实你换种写法,这儿的结果就会换成7,原因也很简单,正是上面图片中说的原因
注意:如果函数返回时,出了函数作用域,返回对象还未还给系统,则可以使用引用返回;如果已经还给系统了,则必须使用传值返回。
这句话说的是下面这种例子:
int&Add(int a, int b)
{
int c=a+b; //出了函数作用域,c不在,回给了系统
return c;
}
int&Add(int a,int b)
{
static c=a+b; //出了函数作用域,c还在,可以用引用返回
return c;
}
大家是不是感觉这个传引用返回用起来很怪了,下面我们来分析一下它是如何返回的。
总结:传值的过程中会产生一个拷贝,而传引用的过程中不会,其实在做函数参数时也具有这个特点。
引用和指针的区别
在语法概念上引用就是一个别名,没有独立空间,和其引用实体共用同一块空间。
intmain()
{
int a=10;
int& ra=a;
cout<<"&a="<<&a<<endl;
cout<<"&ra="<<&ra<<endl;return 0; }
在底层实现上实际是有空间的,因为引用是按照指针方式来实现的。
intmain()
{
int a=10;
int& ra=a;
ra=20;
int* pa=&a;
*pa=20;
return 0; }
我们来看下引用和指针的汇编代码对比
引用和指针的区别
1、引用在定义时必须初始化,指针没有要求。
2、引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体。
3、没有NULL引用,但有NULL指针。
4、在sizeof中的含义不同:引用的结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4个字节)。
5、引用进行自增操作就相当于实体增加1,而指针进行自增操作是指针向后偏移一个类型的大小。
6、有多级指针,但是没有多级引用。
7、访问实体的方式不同,指针需要显示解引用,而引用是编译器自己处理。8、引用比指针使用起来相对更安全。
内联函数
概念:以inline修饰的函数叫做内联函数,编译时C++编译器会在
调用内联函数的地方展开
,没有函数压栈的开销,内联函数提升程序运行的效率。(看到在加粗部分时,小伙伴肯定会想,这和c语言中的宏是不是很像了?)
如果在上述函数前增加inline关键字将其改成内联函数,在编译期间编译器会用函数体替换函数的调用
- 特性
-
inline是一种以空间换时间的做法,省去调用函数额开销。所以代码很长/递归的函数不适宜
-
inline对于编译器而言只是一个建议,编译器会自动优化,如果定义为inline的函数体内代码比较长/递归等
使用作为内联函数。
等,编译器优化时会忽略掉内联。
inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会
找不到。
//F.h#include
c++有哪些技术可以代替宏
- C++有哪些技术替代宏?
常量定义换用const
函数定义换用内联函数
auto关键字(C++11)
在早期的C/C++中auto的含义是:使用auto修饰的变量是具有自动存储器的局部变量,但遗憾的是一直没有人去使用它。
在C++11中,标准委员会赋予了auto全新的含义:auto不再是一个存储类型指示符,而是作为一个新的类型指示符来指示编译器,auto声明的变量必须由编译器在编译时期推导而得。可能光看这一句话,你不一定能懂,下面我们举几个例子。
#include
注意:使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto的实际类
型。因此auto并非是一种“类型”的声明,而是一个类型声明时的“占位符”,编译器在编译期会将auto替换为变量实际的类型。
auto的使用细则
1.auto与指针和引用结合起来使用
用auto声明指针类型时,用auto和auto*没有任何区别,但用auto声明引用类型时则必须加&
#include
注意:用auto声明引用时必须加&,否则创建的只是与实体类型相同的普通变量,只不过将其换了个姓名而已。
2.在同一行定义多个变量
当在同一行声明多个变量时,这些变量必须是相同的类型,否则编译器将会报错,因为编译器实际只对
第一个类型进行推导,然后用推导出来的类型定义其他变量。
voidTestAuto()
{
auto a=1, b=2;
auto c=3, d=4.0; // 该行代码会编译失败,因为c和d的初始化表达式类型不同}
auto不能推导的场景
1.auto做为函数的参数
//此处代码编译失败,auto不能作为形参类型,因为编译器无法对a的实际类型进行推导
voidTestAuto(auto a){}
2.auto不能直接用来声明数组
voidTestAuto()
{
int a[]={1,2,3};
auto b[]={4,5,6};
}
为了避免与C++98中的auto发生混淆,C++11只保留了auto作为类型指示符的用法
auto在实际中最常见的优势用法就是跟以后会讲到的C++11提供的新式for循环,还有lambda表达式等进行配合使用。
基于范围的for循环(C++11)
范围for的语法
在C++98中如果要遍历一个数组,可以按照以下方式进行:
voidTestFor()
{
int array[]={ 1, 2, 3, 4, 5 };
//将数组所有元素乘以2
for (int i=0; i < sizeof(array) / sizeof(array[0]); ++i)
array[i] *=2;
for (int* p=array; p < array + sizeof(array)/ sizeof(array[0]);++p)cout << *p << endl; }
对于一个有范围的集合而言,由程序员来说明循环的范围是多余的,有时候还会容易犯错误。因此C++11中引入了基于范围的for循环。
for循环后的括号由冒号“:”分为两部分:第一部分是范围内用于迭代的变量,
第二部分则表示被迭代的范围。
注意不能写成auto,不然改变不了原数组
正确的写法
voidTestFor()
{
int array[]={ 1, 2, 3, 4, 5 };
//将数组中所有元素乘以2
for(auto& e : array)
e *=2;
for(auto e : array)
cout << e << " ";
return 0;
}
注意:与普通循环类似,可用continue来结束本次循环,也可以用break来跳出整个循环。
范围for的使用条件
1.for循环迭代的范围必须是确定的
对于数组而言,就是数组中第一个元素和最后一个元素的范围;对于类而言,应该提供begin和end的
方法,begin和end就是for循环迭代的范围。
注意:以下代码就有问题,因为for的范围不确定
voidTestFor(int array[])
{
for(auto& e : array) //这里的array其实不是数组,数组在传参时会退化成指针cout<< e <<endl; }
2. 迭代的对象要实现++和==的操作。
关于迭代器这个问题,以后会讲,现在大家了解一下就可以了。
指针空值nullptr
C++98中的指针空值
在良好的C/C++编程习惯中,在声明一个变量的同时最好给该变量一个合适的初始值,否则可能会出现不可预料的错误。比如未初始化的指针,如果一个指针没有合法的指向,我们基本都是按如下方式对其进行初始化:
int*p1=NULL;
int*p2=0;
NULL其实是一个宏,在传统的C头文件(stddef.h)中可以看到如下代码:
#ifndefNULL
#ifdef__cplusplus
#defineNULL 0
#else
#defineNULL ((void *)0)
#endif
#endif
可以看到,NULL可能被定义为字面常量0,或者被定义为无类型指针(void*)的常量。不论采取何种定义,在
使用空值的指针时,都不可避免的会遇到一些麻烦,比如:
#include
程序本意本意是想通过Fun(NULL)调用指针版本的Fun(int*p)函数,但是由于NULL被定义为0,Fun(NULL)最终调用的是Fun(intp)函数。
注:在C++98中字面常量0,既可以是一个整型数字,也可以是无类型的指针(void*)常量,但编译器默认情况下将其看成是一个整型常量,如果要将其按照指针方式来使用,必须对其进行强制转换。
C++11中的指针空值
对于C++98中的问题,C++11引入了关键字nullptr。在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为关键字引入的。在C++11中,sizeof(nullptr)与sizeof((void*)0)所占的字节数相同,大小都为4。
主题测试文章,只做测试使用。发布者:艾迪号,转转请注明出处:https://www.cqaedi.cn/fenxiang/40194.html