潘家园做网站公司,上海做网站公司qinmoo,90设计网官网首页,wordpress云图插件目录
1. C关键字
2. 命名空间
1#xff09;命名空间的引入和概述
2#xff09;命名空间的定义
3#xff09;std与命名空间的使用
4).相关特性
3. C输入输出
4. 缺省参数
1 #xff09;缺省参数概念
2#xff09;使用及分类
a.全缺省
b.部分缺省
5. 函数…目录
1. C关键字
2. 命名空间
1命名空间的引入和概述
2命名空间的定义
3std与命名空间的使用
4).相关特性
3. C输入输出
4. 缺省参数
1 缺省参数概念
2使用及分类
a.全缺省
b.部分缺省
5. 函数重载
1 函数重载概念
2分类
3原理
6. 引用
1引用概念和使用
2引用特性
3常引用
4使用场景
5)引用与指针
7. 内联函数
1概念
2特性
8. auto关键字(C11)
1概念和使用
2)特性
9. 基于范围的for循环(C11)
1 )范围for的语法
2) 范围for的使用条件
10. 指针空值---nullptr(C11)
1)NULL
2nullptr 1. C关键字
C总计63个关键字C语言32个关键字
asmdoifreturntrycontinueautodoubleinlineshorttypedefforbooldynamic_castintsignedtypeidpublicbreakelselongsizeoftypenamethrowcaseenummutablestaticunionwchar_tcatchexplicitnamespacestatic_castunsigneddefaultcharexportnewstructusingfriendclassexternoperatorswitchvirtualregisterconstfalseprivatetemplatevoidtrueconst_castfloatprotectedthisvolatilewhiledeletegotoreinterpret_cast2. 命名空间
1命名空间的引入和概述
在c中符号常量、变量、函数、结构、枚举、类和对象等名称将都存在于全局作用域中用户的命名与这些名称相同而冲突的可能性非常大。并且工程越大互相冲突性的可能性越大。另外使用标准类库时也可能导致命名冲突。为了避免这些情况C引入了关键字namespace命名空间/名字空间/名称空间来控制标识符的作用域从而达到目的。
例如在c里面
int a1;
void f1()
{int a2;//可以取名相同因为域不同
}
void f2()
{int a3;
}
2命名空间的定义
定义命名空间需要使用到namespace关键字后面加命名空间的名字然后接一对{}即可{} 中即为命名空间的成员。
//分别定义名字为AB的命名空间
namespace A {int a 10;void print(){cout A:print endl;}
}
namespace B {int a 20;void print(){cout A:print endl;}
}
void test1()
{cout A中a A::a endl;cout B中a B::a endl;
}
int main()
{test1();A::print();B::print();
} 编译器默认只在全局域里查找相关变量函数等不会进入特定的命名空间里查找。一个命名空间就定义了一个新的作用域命名空间中的所有内容都局限于该命名空间中。需要使用特定的命名空间里的变量等需要指定命名空间指定命名空间需要使用::。 ::是作用域限定符或者称作用域运算符scope operator 用法namespace::name 3std与命名空间的使用
std命名空间是C中标准库类型对象的命名空间。如需要使用cout等就有多种方法。
a. using namespce std; --代表把整体std展开此时应该避免与sd里的名称冲突。 编译器此时会去std里面查找相关内容。 b. 所有使用都用::指定。 c. 只把部分常用的展开已经展开的不需要用::。 4).相关特性
a. 命名空间只能全局范围内定义 b. 命名空间可以嵌套
namespace A
{int a 0;namespace B{int a 100;}
} c. 随时把新的成员加入已有的命名空间中
namespace A
{int a 0;}
namespace A
{int b 100;
}
int main()
{cout A::a endl A::b endl;
} 所以当项目里面有两个命名空间相同时会被合并。
d. 命名空间中的函数可以在“命名空间”外定义
namespace A
{int a 0;void A_fun();}
void A::A_fun()
{cout hello endl;
}
int main()
{A::A_fun();
} 3. C输入输出 #includeiostream
using namespace std;
int main()
{int d;cin d;cout d endl;
} 1在C里 如果需要使用输入输出时必须包含 iostream 头文件它包含了用于输入输出的对象 cout和cin是全局的流对象cin表示标准输入、cout表示标准输出。endl是特殊的C符号表示换行输出。 2 是流插入运算符是流提取运算符。 3使用C输入输出不需要手动控制格式自动识别变量类型。 4 实际上cout和cin分别是ostream和istream类型的对象和是运算符重载。 注早期标准库将所有功能在全局域中实现声明在.h后缀的头文件中使用时只需包含对应 头文件即可后来将其实现在std命名空间下为了和C头文件区分也为了正确使用命名空间 规定C头文件不带.h旧编译器(vc 6.0)中还支持iostream.h格式后续编译器已不支持因 此推荐使用iostreamstd的方式。 4. 缺省参数
1 缺省参数概念
声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时如果没有指定实参则采用该形参的缺省值否则使用指定的实参。
void fun1(int a 100)
{cout a的值 a endl;
}
int main()
{fun1();fun1(1);fun1(5);}2使用及分类
a.全缺省
void fun(int a 1, int b 2, int c 2)
{cout a b c endl;
}int main()
{fun();fun(12);fun(10,20);
} b.部分缺省
void fun(int a , int b 2, int c 2)
{cout a b c endl;
}
int main()
{fun(12);fun(10,20);fun(10, 20, 30);//fun(10, , 30);
} 注意
缺省参数不能在函数声明和定义中同时出现一般都在声明中给出。
缺省值必须是常量或者全局变量 5. 函数重载
1 函数重载概念
在开发中有时候我们可能需要实现几个功能类似的函数只是有些细节不同。例如希望交换两个变量的值两个变量有多种类型可以是 int、float、char、bool 等在C语言中只能分别设计出各个不同名的函数。
C允许在同一作用域中声明几个功能类似的同名函数这些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同常用来处理实现功能类似数据类型不同的问题。
void swap(int* a, int* b)
{int ret *a;*a *b;*b ret;
}
void swap(double* a, double* b)
{double ret *a;*a *b;*b ret;
}
2分类
a、参数类型不同
int Add(int left, int right)
{
return left right;
}
double Add(double left, double right)
{
return left right;
} b、参数个数不同
void f()
{
cout f() endl;
}
void f(int a)
{
cout f() endl;
}
c、参数类型顺序不同
void f(int a, char b)
{
cout f(int a,char b) endl;
}
void f(char b, int a)
{
cout f(char b, int a) endl;
}
注意函数的返回值不同时不能完成函数重载。
3原理
①为什么C语言不支持函数重载
程序在预编译阶段会经历预处理、编译、汇编和链接生成可执行程序的过程。
首先在汇编过程中编译器对每个文件都会收集全局符号并生成全局符号如果暂时找不到函数的地址例如只有函数的声明符号表里将会填写一个没有意义的值 生成了多个符号表后在链接过程中要对符号表进行合并。在合并的过程中如果函数出现了两次——函数有效的地址值就会作为Add函数最终的地址值。C语言中因为两个重名函数的地址都是有效值在重定位合并符号表的时候就会产生冲突和歧义。 ②C为什么支持函数重载
C会对写入符号表的同名函数进行名字修饰(name Mangling)这样函数重载的函数的名字都不一样有效地址也不一样这样合并符号表就不会发生冲突。 注不同编译器名字修饰的方法不同。 图为举例。
如果两个函数函数名和参数是一样的返回值不同是不构成重载的因为即使返回值不同修饰的函数不同解决了符号表合并时的函数名冲突但是真正调用时只有实参无法确定使用哪个函数。 6. 引用
1引用概念和使用
引用变量是一个别名把该引用初始化为某个变量x就可以使用该引用名称或变量名称x来读取或者修改x变量。此时两者是同一个东西。
使用方法引用类型必须和引用实体是同种类型的 类型 引用变量名(对象名) 引用实体 int a10; int a_othernamea; 2引用特性
a. 引用在定义时必须初始化 b. 一个变量可以有多个引用 c. 引用一旦引用一个实体再不能引用其他实体
void TestRef()
{
int a 10;
// int ra; // 该条语句编译时会出错
int ra a;
int rra ra;
printf(%p %p %p\n, a, ra, rra);
} 3常引用
指针和引用赋值/初始化时权限只能缩小或者平移但不能放大
const int a 10;
//int ra a; // 该语句编译时会出错a为常量,需要用const类型接收
const int ra a;// int b 10; // 该语句编译时会出错b为常量,需要用const类型接收
const int b 10;double d 12.34;
//int rd d; // 该语句编译时会出错类型不同const int rd d; //隐式类型转换/*
int i1;
double di;
//隐式类型转换
*/ 4使用场景
a. 做参数
void Swap(int left, int right)
{int temp left;left right;right temp;
}
b. 做返回值
1值返回
不管返回的值在栈堆静态区的任何地方值返回都会产生临时变量。 2引用返回
只要出了函数后该返回的值还存在就可以通过引用返回直接将返回值返回省去临时变量的传递。
int Add(int a, int b)
{
int c a b;//错误
return c;
}
int main()
{
int ret Add(1, 2);
Add(3, 4);
cout Add(1, 2) is : ret endl;
return 0;
}
当返回一个引用时要注意被引用的对象不能超出作用域。所以返回一个对局部变量的引用是不合法的但是可以返回一个对静态变量的引用。 传值、传引用效率比较 -以引用作为参数在传参期间函数会直接传递实参而传值需要多一份临时的拷贝因此用值作为参数效率是非常低下的尤其是当参数或者返回值类型非常大时效率就更低。
-返回引用实际返回的是一个指向返回值的隐式指针在内存中不会产生副本是直接将返回值拷贝给接收变量这样就避免产生临时变量相比返回普通类型的执行效率更高而且这个返回引用的函数也可以作为赋值运算符的左操作数 5)引用与指针
语法层面引用就是一个别名没有独立空间和其引用实体共用同一块空间 引用在底层实现上实际是有空间的因为引用是按照指针方式来实现的。
引用和指针的不同点: 1. 引用概念上定义一个变量的别名指针存储一个变量地址。 2. 引用在定义时必须初始化指针没有要求 3. 引用在初始化时引用一个实体后就不能再引用其他实体而指针可以在任何时候指向任何 一个同类型实体 4. 没有NULL引用但有NULL指针 5. 在sizeof中含义不同引用结果为引用类型的大小但指针始终是地址空间所占字节个数(32 位平台下占4个字节) 6. 引用自加即引用的实体增加1指针自加即指针向后偏移一个类型的大小 7. 有多级指针但是没有多级引用 8. 访问实体方式不同指针需要显式解引用引用编译器自己处理 9. 引用比指针使用起来相对更安全
7. 内联函数
在C语言里宏分为宏常量宏函数。然而宏具有许多不安全的问题且不易调试。c里引入了内联函数代替宏函数一般提倡使用const和enum替代宏常量。
1概念
以inline修饰的函数叫做内联函数。 我们知道一般情况下所有函数调用时都需要建立栈帧和传递参数。 如果在上述函数前增加inline关键字将其改成内联函数在编译期间编译器会把该函数的代码副本放置在每个调用该函数的地方用函数体替换函数的调用。不会产生建立栈帧和传递参数的消耗提升程序运行的效率。 2特性
a. inline是一种以空间目标文件的空间换时间的做法如果编译器将函数当成内联函数处理在编译阶段会 用函数体展开替换函数调用缺陷可能会使目标文件体积变大优势少了调用开销提高程序运行效率。 b. inline对于编译器而言只是一个建议不同编译器关于inline实现机制可能不同一般建 议将函数规模较小(即函数不是很长具体没有准确的说法取决于编译器内部实现)、不 是递归、且频繁调用的函数采用inline修饰否则编译器会忽略inline特性。 c.inline不建议声明和定义分离分离会导致编译时链接错误。因为inline函数会被展开没有函数地址不会进入符号表这样进行链接时会发生链接错误找不到函数定义。
// F.h
#include iostream
using namespace std;
inline void f(int i);
// F.cpp
#include F.h
void f(int i)
{
cout i endl;
}
// main.cpp
#include F.h
int main()
{
f(10);
return 0;
}
// 链接错误main.obj : error LNK2019: 无法解析的外部符号 void __cdecl
f(int) (?fYAXHZ)该符号在函数 _main 中被引用
d.在类定义中的定义的函数都是内联函数即使没有使用 inline 说明符。 8. auto关键字(C11)
1概念和使用
a.引入
随着程序越来越复杂程序中用到的类型也越来越复杂像 std::mapstd::string, std::string::iterator 这样的类型太长了特别容易写错。在C语言里可以通过typedef给类型取别名解决
typedef std::mapstd::string, std::string Map;
int main()
{Map m{ { apple, 苹果 },{ orange, 橙子 }, {pear,梨} };Map::iterator it m.begin();while (it ! m.end()){//....}return 0;
}
但是typedef还是存在一些问题 b.概念
为了解决这个问题并且在编程时常常需要把表达式的值赋值给变量这就要求在声明变量的时候清楚地知道表达式的类型。然而有时候要做到这点并非那么容易。因此C11给auto赋予了新的含义。auto不再是一个存储类型指示符而是作为一个新的类型指示符来指示编译器auto声明的变量必须由编译器在编译时期推导而得。
int a 10;
auto b a;
auto c a;
auto d fun();auto raa;
auto* paa;
auuto e;//error,报错 【注意】 使用auto定义变量时必须对其进行初始化在编译阶段编译器需要根据初始化表达式来推导auto 的实际类型。因此auto并非是一种“类型”的声明而是一个类型声明时的“占位符”编译器在编 译期会将auto替换为变量实际的类型。
2)特性
1. 用auto声明指针类型时用auto和auto*没有任何区别但用auto声明引用类型时则必须加 2. 当在同一行声明多个变量时这些变量必须是相同的类型否则编译器将会报错因为编译 器实际只对第一个类型进行推导然后用推导出来的类型定义其他变量。 c. auto不能作为函数的参数 // 此处代码编译失败auto不能作为形参类型因为编译器无法对a的实际类型进行推导
void TestAuto(auto a)
{}
d. auto不能直接用来声明数组 e.为了避免与C98中的auto发生混淆C11只保留了auto作为类型指示符的用法 f.auto在实际中最常见的优势用法就是跟C11提供的新式for循环和lambda表达式等进行配合使用。 9. 基于范围的for循环(C11)
1 )范围for的语法
在C98中如果要遍历一个数组可以按照以下方式进行
void TestFor()
{
int array[] { 1, 2, 3, 4, 5 };
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;
}
对于一个有范围的集合而言由程序员来说明循环的范围是多余的有时候还会容易犯错误。因 此C11中引入了基于范围的for循环。for循环后的括号由冒号“ ”分为两部分第一部分是范 围内用于迭代的变量第二部分则表示被迭代的范围。
void TestFor()
{
int array[] { 1, 2, 3, 4, 5 };
for(auto e : array)
e * 2;
for(auto e : array)
cout e ;
return 0;
}
void TestFor(int array[])
{
for(auto e : array)
cout e endl;
}
比
注意与普通循环类似可以用continue来结束本次循环也可以用break来跳出整个循环。 2) 范围for的使用条件
a. for循环迭代的范围必须是确定的 对于数组而言就是数组中第一个元素和最后一个元素的范围对于类而言应该提供 begin和end的方法begin和end就是for循环迭代的范围。 注意以下代码就有问题因为for的范围不确定
void TestFor(int array[])
{
for(auto e : array)
cout e endl;
}
//函数里数组名array已经退化成了指针
b. 迭代的对象要实现和的操作。 10. 指针空值---nullptr(C11)
1)NULL
在良好的C/C编程习惯中声明一个变量时最好给该变量一个合适的初始值否则可能会出现 不可预料的错误比如未初始化的指针。如果一个指针没有合法的指向我们基本都是按照如下 方式对其进行初始化 int* p1 NULL; int* p2 0; NULL实际是一个宏在传统的C头文件(stddef.h)中可以看到如下代码
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
可以看到NULL可能被定义为字面常量0或者被定义为无类型指针(void*)的常量。不论采取何 种定义在使用空值的指针时都不可避免的会遇到一些麻烦比如
void f(int)
{
coutf(int)endl;
}
void f(int*)
{
coutf(int*)endl;
}
int main()
{
f(0);
f(NULL);
f((int*)NULL);
return 0;
}
程序本意是想通过f(NULL)调用指针版本的f(int*)函数但是由于NULL被定义成0因此与程序的 初衷相悖。第二次调用时f会匹配到int参数那个。
2nullptr
c引入了nullptr完善NULL其实质是(void*)0 在C98中字面常量0既可以是一个整形数字也可以是无类型的指针(void*)常量但是编译器 默认情况下将其看成是一个整形常量如果要将其按照指针方式来使用必须对其进行强转(void *)0。 注意 1. 在使用nullptr表示指针空值时不需要包含头文件因为nullptr是C11作为新关键字引入 的。
2.在C11中sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。 3. 为了提高代码的健壮性在后续表示指针空值时建议最好使用nullptr。