今天跟兄弟闲聊的时候,他抱怨说,Python是C++写的,Java是C++写的...而C++呢,C写的。就好像在说,你看,别墅是砖磊的,楼是砖磊的,而砖自己呢,粘土做的,有点low的感觉。不过这也侧面说明了C++的地位。C++以其较好的兼容性和高执行效率,迄今仍是主流编程语言之一,在某些场景不可替代(某些重要系统考虑速度的时候)。这使得C++在以下领域不可或缺。
C++的应用领域
1)游戏(魔兽世界听过吧)
2)科学计算(https://www.cnblogs.com/xiexiaokui/archive/2010/05/20/1740353.html)
3)网络通信(ACE框架)
4)操作系统和设备驱动(看看Windows自己)
5)其它(嵌入式、编译器、脚本引擎...)
C和C++区别
C和C++到底有什么区别呢?简单说就是
1)都是编译型语言
2)都是强类型语言,但是C++更强
3)C++去除了C中不好特性
4)C++中增加很多好的特性(面向对象语法),比C语言更适合大型软件开发。
我记得在我学STL的时候,我的老师很不屑的说,别的(编程语言)能做的,C++都能做,而C++都能做的,别的做不了。于是用文言文形式的注释,以简练优美的语言,深入浅出的讲解方式,凝练而精致的代码,向我们描述STL的强大和优雅。从那时候起我就渴望,以后一定要做一个优秀的C++程序员,不为别的,就为了想写什么就能写什么!也许这条路很长,也许生活会拖慢进取的脚步,但是只要在路上,总会有所成就!本课程将总结C++Primer重点内容,对C++语法做一个梳理。
第一个C++程序
#include <iostream>
//#include <stdio.h>//C风格
#include <cstdio>//C++风格,语义和上面等价
int main(void)
{
printf("hello world!\n");
std::cout << "hello world!" << std::endl;
int i;
float f;
std::cin >> i >> f;
printf("i=%d,f=%g\n",i,f);
std::cout << "i=" << i << ",f=" << f << std::endl;
return 0;
}
1 linux下编译方式:
1)gcc xx.cpp -lstdc++
2)g++ xx.cpp //g++自动链接标准C++库
2 文件扩展名:C++中源文件一般以.cpp结尾,但也有其他
1)xx.cpp //推荐使用
2)xx.cc
3)xx.C
4)xx.cxx
3 头文件
#include <iostream>
#include <stdio.h> ==> #include <cstdio>
标准输入和输出
、
1)cin对象表示标准输入//类似scanf
eg:
int a;
//scanf("%d",&a);
cin >> a;//从标准输入设备提取一个整形数放到变量a中
">>":提取运算符
eg:
int a;
double d;
//scanf("%d%lf",&a,&d);
cin >> a >> d;
2)cout对象表示标准输出//类似printf
eg:
int a = 100;
//printf("%d\n",a);
cout << a << endl;//把数据a插入到标准输出设备
"<<":插入运算符
eg:
int a = 100;
int d = 3.14;
//printf("%d,%lf\n",a,d);//100,3.14
cout << a << ',' << d << endl;//100,3.14
一 命名空间(或者叫名字空间)
一个命名空间的定义包含两部分:首先是关键字namespace,随后是命名空间的名字。在命名空间名字后面是一系列由花括号括起来的声明和定义。只要能出现在全局作用域中的声明就能置于命名空间内,主要包括:类、变量(及其初始化操作)、函数(及其定义)、模板和其它命名空间。命名空间结束后无须分号,这一点与块类似。和其它名字一样,命名空间的名字也必须在定义它的作用域内保持唯一。命名空间既可以定义在全局作用域内,也可以定义在其它命名空间中,但是不能定义在函数或类的内部。
1 名字空间的作用
1)避免名字冲突
2)划分逻辑单元
2 定义名字空间
namespace 名字空间名{
名字空间成员1;
名字空间成员2;
...
}
eg:
namespace ns1{
int x;//全局变量
void func(){} //全局函数
struct A{}; //自定义类型
namespace ns2; //名字空间
}
注意:1 命名空间可以是不连续的,命名空间可以定义在几个不同的部分。拿函数来说,函数定义一次,多次调用,这ok。而命名空间可以在不同地方多次定义(里面的成员名称不能相同)。2 通常来说#include应该放在命名空间前,如果放在名字空间里,那这个头文件里的变量、函数等都是该命名空间里的了,使用时需要 这样YourNameSpace::YourMember
3 名字空间使用
1)通过作用域限定符“::”
名字空间名::访问问的成员;
eg:
int main(){
//访问ns1名字空间中x变量
x = 100;//error,名字空间里面成员不能直接访问
ns1::a = 100;//ok
func();//error
ns1::func();//ok
}
eg:
//std是标准C++库中已经定义好的名字空间,称为标准名字空间,所有C++库中全局函数、全局变量都在该名字空间中。
std::cout << "hello world" << std::endl;
下面是代码示范:
#include <iostream>
//定义名字空间
namespace ns1{
void func(void){
std::cout << "ns1名字空间中的func函数"
<< std::endl;
}
}
int main(void)
{
ns1::func();//调用ns1中的函数
return 0;
}
2)名字空间指令
using namespace 名字空间名;
在该条指令以后的代码中,指定名字空间中的成员都可见,访问其中成员可以省略作用域限定。 它的有效范围从using声明的地方开始,一直到using声明所在的作用域结束为止。在此过程中,外层作用域的同名实体将被隐藏。
using namespace std;//全局作用域
int main(void){
cout << "hello world!" << endl;//前面不引用std
//如果这句using namespace std;不写,则需要这样
std::cout << "hello world!" << std::endl;
}
3)名字空间声明
using 名字空间名::名字空间成员;
将名字空间中的特定成员引入当前作用域,在该作用域中访问这个成员可以省略作用域限定。
eg:
namespace ns{
int i1;
int i2;
}
int main(){
using ns::i1;
i1 = 100;//ok
i2 = 200;//error
ns::i2 = 200;//error,未声明
}
4 无名名字空间(unnamed namespace)
不属于任何名字空间的全局变量或函数,将被编译器自动放入无名名字空间中,引用格式如下。
::无名名字空间成员;
#include <iostream>
using namespace std;
namespace A
{
int a = 100;
namespace B //嵌套一个命名空间B
{
int a =20;
}
}
int a = 200;//定义一个全局变量,不属于任何名字空间,自动在无名名字空间::里
int main(int argc, char *argv[])
{
cout <<"A::a ="<< A::a << endl;
cout <<"A::B::a ="<<A::B::a << endl;
cout <<"a ="<<a << endl;
cout <<"::a ="<<::a << endl;
int a = 30;
cout <<"a ="<<a << endl;
cout <<"::a ="<<::a << endl;
return 0;
}
结果:
A::a =100
A::B::a =20
a =200 //全局变量a
::a =200
a =30 //局部变量a
::a =200
注意:一个未命名的命名空间可以在某个给定的文件内不连续,但是不能跨越多个文件
5 名字空间嵌套//了解
namespace china{
namespace beijing{
char* name = "金亮";
}
namespace chengdu{
char* name = "一虎";
}
}
cout << china::beijing::name << endl;//金亮
cout << china::chengdu::name << endl;//一虎
拓展:
6 内联命名空间:C++11新标准引入了一种新的嵌套命名空间,称为内联命名空间(inline namespace)。和普通的嵌套命名空间不同,内联命名空间中的名字可以被外层命名空间直接使用。定义内联命名空间的方式是在关键字namespace前添加关键字inline。关键字inline必须出现在命名空间第一次定义的地方,后续再打开命名空间的时候可以写inline,也可以不写。
#include <iostream>
using namespace std;
inline namespace A
{
inline void f(){
std::cout << __FUNCTION__ << __LINE__ << std::endl;
};
}
int main(int argc, char *argv[])
{
f();
}
结果:
f6
拓展:模板特例化
模板特例化必须定义在原始模板所属的命名空间中。和其它命名空间名字类似,只要我们在命名空间中声明了特例化,就能在命名空间外部定义它了。
#include <iostream>
using namespace std;
namespace ns{
//原始类模板
template<typename T1,typename T2>
class One{
public:
void fun(T1 v1,T2 v2){
std::cout << "v1 = " << v1 << std::endl;
std::cout << "v2 = " << v2 << std::endl;
}
};
//全特化类模板
template<>
class One<int,double>
{
public:
void fun(int v1,double v2){
std::cout << "v1(int) = " << v1 << std::endl;
std::cout << "v2(double) = " << v2 << std::endl;
}
};
}//end ns
int main(int argc, char *argv[])
{
using ns::One;//先用using指令声明One
One<int,double> one;
one.fun(123,12.3);
return 0;
}
结果:
v1(int) = 123
v2(double) = 12.3
二 C++中的类型
1 结构体
1)定义结构体变量时,可以省略struct关键字。
2)结构体中里面可以定义函数,称为成员函数,在成员函数中可以访问成员变量。
2 联合体(了解)
1)定义联合体变量时,可以省略union关键字
2)支持匿名联合
3 枚举
1)定义枚举变量时,可以enum关键字
2)C++枚举看做一种独立数据类型,而C中枚举本质就是整形数。
eg:
enum COLOR{RED,GREEN,BLUE};
COLOR c;
c = 100;//C中ok,C++中error
c = RED;//ok
4 字符串
4.1 回顾C中字符串
1)双引号常量字符串
"hello world"
2)字符串指针:char *
3)字符数组:char arr[..];
eg:
char arr[6] = "hello";
strcpy(arr,"jiangguiliang");//内存越界,危险
char* p = "world";
strcpy(p,arr);//段错误
p = "jiangguiliang";//ok
arr = "jiangguiliang";//error
4.2 C++兼容C中字符串,同时增加string类型,专门表示字符串
1)定义字符串
#include <string>
string s;//定义一个空字符串
string s = "hello";
string s = string("hello");//和上面等价
string s("hello");//和上面等价
cout << s << endl;//cout可以直接输出string类型的字符串
2)字符串的基本操作
+ += 字符串连接
eg:
string s1 = "hello";
string s2 = "world";
s1 += s2;//s1 = s1 + s2
cout << s1 << endl;//"helloworld"
= 字符串的拷贝
eg:
string s1 = "hello";
string s2 = "world";
s1 = s2;
cout << s1 << endl;//world
< <= > >= 字符串的比较大小
== != 比较字符串是否相同
eg:
string s1 = "hello";
string s2 = "world";
if(s1>s2){
cout << "s1 > s2" << endl
}
else{
cout << "s1 <= s2" << endl;//ok
}
可以用 [ ] 获取字符串中指定下标的字符
eg:
string s = "hello";
s[0] = 'h';
3)string类型中常用的成员函数
size()/length(); //获取字符串的长度
c_str();//将string转换为char*风格的字符串
eg:
string s = "this is a test";
s.size();//得到字符串实际长度
char *ps = s.c_str();
5 布尔类型
5.1 bool类型是C++中基本数据类型,专门表示逻辑值:
true表示逻辑真,false表示逻辑假
5.2 bool类型在内存占一个的内存:1表示true,0表示false
5.3 bool类型的变量可以接收任意类型的表达式的值,其值非0(NULL)则为true,零为false
6 操作符别名(了解)
&& --》 and
|| --》 or
{ --》 <%
} --》 %>
....
三 C++中的函数
函数是一组一起执行一个任务的语句,以{}开始结束。每个 C 程序都至少有一个函数,即主函数 main() ,所有简单的程序都可以定义其他额外的函数。声明格式如下:
函数类型名称 函数名称(形式参数列表)
{
语句1;
语句2;
...
return 返回值;
}
函数声明告诉编译器函数的名称、返回类型和参数。函数定义提供了函数的实际主体,也就是语句。在调用函数时,格式如下:
函数名称(实际参数列表);
以上是标准C语言的函数,C++中在此基础上,发展了一些新的特性,如函数重载,缺省函数,哑元参数,内联函数,下面一一说明:
1 函数重载:在相同的作用域中,定义同名不同的参函数,这样的函数构成重载关系。(函数重载和返回类型无关)调用重载关系的函数时,编译器将根据实参和形参的匹配程序,自动选择最优的匹配版本。其本质是C++编译器通过对函数进行换名,将参数类型信息整合到新的名字中,解决函数重载与名字冲突的矛盾(可理解为,参数类型也是函数名的一部分)。
g++4.8的选择顺序是: 完全匹配>常量转换>升级转换>降级转换>省略号匹配
#include <iostream>
using namespace std;
//char->int:升级转换
void bar(int i){
cout << "bar(1)" << endl;
}
//char->const char:常量转换
void bar(const char c){
cout << "bar(2)" << endl;
}
//short->char:降级转换
void func(char c){
cout << "func(1)" << endl;
}
//short->int:升级转换
void func(int i){
cout << "func(2)" << endl;
}
//short->long long:过分的升级转换
void func(long long l){
cout << "func(3)" << endl;
}
//省略号匹配,最差
void hum(int i,...){
cout << "hum(1)" << endl;
}
//double->int:降级转换
void hum(int i,int j){
cout << "hum(2)" << endl;
}
int main(void)
{
char c = 'A';
bar(c);//调用第二个bar()
short s = 10;
func(s);//调用第二个func()
hum(10,3.14);
}
有些面试题问extern "C" 声明的作用,就可以说,让C++编译器知道这是C风格的函数名,那么C程序就可以顺利调用它了。
extern "C" void func();
2 函数的缺省参数(默认实参)
1)可以为函数的参数指定缺省值,调用该函数时,如果不给实参,就取缺省值作为相应的形参的值。
eg:
void mysend(
int soctfd,void* buf,int size,int flag=0);
2)如果函数的声明和定义分开,缺省参数写在声明部分,而定义部分不写。
3)缺省参数必须靠右,如果一个参数有缺省值,那么这个参数的右侧所有参数都必须带有缺省值。
void func(int a=10,int b){}//error
void func(int b,int a=10){}//ok
func(100);
#include <iostream>
using namespace std;
//注意函数重载引发的歧义错误
//void foo(int a){}
//函数声明
void foo(int a,int b = 200 ,int c = 100);
int main(void)
{
foo(10);
foo(10,20,30);
return 0;
}
//函数定义
void foo(int a,int b/*= 200*/,int c/* = 100*/){
cout << a << ',' << b << ',' << c << endl;
}
3 函数的哑元参数//了解
1)定义:只有类型而没有形参变量名的形参称为参数。
eg:
void func(int/*哑元*/){}
int main(){
func(10);
}
2)使用场景
-->为了兼容旧的代码
eg:
算法函数:
void math_func(int a,int b){..}
使用者:
int main(){
math_func(10,20);
...
math_func(30,40);
}
升级算法函数:
void math_func(int a,int = 0){..}
使用者:
int main(){
math_func(10,20);
...
math_func(30,40);
}
4 内联函数(inline)
1)定义
使用inline关键字修饰的函数,表示这个函数就是内联函数,编译器会对内联函数做内联优化,避免函数调用的开销。内联函数和宏的区别在于:宏是由预处理器对宏进行替代,而内联函数是通过编译器控制来实现的。
inline 返回类型 函数名(形参表){...} //内联函数
2)适用场景
--》多次调用小而简单的函数适合内联
--》调用次数极少获取大而复杂的函数不适合内联
--》递归函数不适合内联
*拓展:
a 任何在类的声明部分定义的函数都会被自动的认为是内联函数。
b 在内联函数中如果有复杂操作将不被内联。如:循环和递归调用。
c 将简单短小的函数定义为内联函数将会提高效率。
d 关键字inline必须与函数定义体放在一起才能使函数称为内联函数,仅将inline放在函数声明前面不起作用。
inline void Foo(int x, int y); // inline仅与函数声明放在一起
void Foo(int x, int y)
{
//.....
}
//而如下风格的函数Foo则成为内联函数:
void Foo(int x, int y);
inline void Foo(int x, int y) // inline与函数定义体放在一起
{
//......
}