c语言是结构化和模块化的语言,用于处理规模较小的程序。当问题需要高度抽象和建模时,c语言不适合。c 是基于c语言产生的,既可以进行c语言过程化程序设计,又可以以抽象数据类型为特点的基于对象的程序设计,还可以进行面向对象的程序设计。
namespace关键字:使用命名空间的目的是对标识符的名称进行本地化, 以避免命名冲突或名字污染。
如果像下面这种情况,在c语言中是解决不了的:
代码语言:javascript复制#include <stdio.h>
#include <stdlib.h>
int rand = 10;
int main()
{
printf("%dn", rand);
return 0;
}
编译后后报错:error C2365: “rand”: 重定义;以前的定义是“函数”
命名空间定义
命名方法:使用到namespace关键字,后面跟命名空间的名字,然后接一对{}即可,{} 中即为命名空间的成员。
我们有不同的命名空间定义方法:
1、正常的命名空间定义
命名空间中可以定义变量/函数/类型
代码语言:javascript复制namespace pearl
{
int rand=10;
int Add(int left, int right)
{
return left right;
}
Struct Node
{
Struct Node* next;
int val;
};
}
2、命名空间可以嵌套
//test.cpp
代码语言:javascript复制namespace pearl1
{
namespace pearl2
{
int Add(int a,int b)
{
return a b;
}
}
}
3、同一个工程中可以出现命名相同的空间,并且编译器最终会合成在同一个命名空间当中
//test.h
代码语言:javascript复制namespace pearl1
{
int Sub(int a,int b)
{
return a-b;
}
}
注意:一个命名空间就定义了一个作用域,命名空间所有的内容都局限在该命名空间当中
命名空间的使用
比如我们在命名空间当中定义了一个变量,我们该如何把它调用出来呢?
代码语言:javascript复制namespace pearl
{
int a=1;
int b=0;
int rand=10;
int Add(int left, int right)
{
return left right;
}
Struct Node
{
Struct Node* next;
int val;
};
}
int main()
{
printf("%d",a);
return 0;
}
// 编译报错:error C2065: “a”: 未声明的标识符
命名空间的使用有三种方法:
1、加空间名称以及域作用限定符
代码语言:javascript复制int main()
{
printf("%dn", pearl1::a);
return 0;
}
2、使用using将某个空间成员引入
代码语言:javascript复制using pearl::b;
int main()
{
printf("%dn", b);
return 0;
}
3、使用using namespace命名空间名称引入
代码语言:javascript复制using namespce pearl;
int main()
{
printf("%dn", pearl::a);
printf("%dn", b);
Add(10, 20);
return 0;
}
这里a和b都能够被正常打印出来。
c 输入和输出
代码语言:javascript复制#include<iostream>
// std是C 标准库的命名空间名,C 将标准库的定义实现都放到这个命名空间中
using namespace std;
int main()
{
cout<<"Hello world!!!"<<endl;
return 0;
}
注意:
使用cout(标准输出对象(控制台))和cin(标准输入对象(键盘))时,必须包含<iostream>标准头文件以及按照命名空间使用方法使用std。
cout和cin是全局的流对象,endl是特殊的C 符号,表示换行输出,他们都包含在包含< iostream >头文件中。
是流插入运算符,>>是流提取运算符。并且在c 中的输入和输出可以自动识别变量的类型。
std命名空间的使用惯例
在日常练习中,建议直接using namespace std即可。
如果在大型的开发项目当中,直接展开,会出现比较多的问题,像std::cout这样使用时指定命名空间 using std::cout展开常用的库对象/类型等方式就可以了。
缺省参数
概念
是在C 声明或定义函数时,为函数的参数指定一个默认值。在调用该函数时,如果没有为某个参数指定实参(即没有显式地传递一个值给这个参数),则该函数将自动采用该参数的默认值。如果调用时提供了实参,则使用指定的实参值。
分类
全缺省参数
代码语言:javascript复制void Func(int a = 10, int b = 20, int c = 30)
{
cout<<"a = "<<a<<endl;
cout<<"b = "<<b<<endl;
cout<<"c = "<<c<<endl;
}
半缺省参数
代码语言:javascript复制void Func(int a, int b = 10, int c = 20)
{
cout<<"a = "<<a<<endl;
cout<<"b = "<<b<<endl;
cout<<"c = "<<c<<endl;
}
注意事项
1半缺省参数必须从右往左给,不能跳着给。
2缺省参数不能在定义和声明中同时出现。
如果声明与定义位置同时出现缺省参数,恰巧两个位置提供的值不同,那编译器就无法确定到底用哪个缺省值。
3缺省值必须是常量或者全局变量
函数重载
概念
在同一作用域,使用相同名称且功能类似的同名函数,这些同名函数的形参列表不同,来处理不同数据类型的问题。
1、参数类型不同
代码语言:javascript复制#include<iostream>
using namespace std;
// 1、参数类型不同
int Add(int left, int right)
{
cout << "int Add(int left, int right)" << endl;
return left right;
}
double Add(int left, int right)
{
cout << "double Add(int left, int right)" << endl;
return left right;
}
2、参数个数不同
代码语言:javascript复制void Add()
{
cout << "f()" << endl;
}
void Add(int a)
{
cout << "f(int a)" << endl;
}
3、参数顺序不同
代码语言:javascript复制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;
}
那么为什么c语言支持函数重载,而c 不支持呢?
review:一个程序需要运行起来,需要经过预处理,编译,汇编,链接这四个过程。
实际的项目通常由多个源文件和多个头文件组成,在编译后链接前,当addB.obj中调用的函数地址只在addA.obj(在addA.cpp中定义)中存在,我们就会进行链接。链接器在看到addB.obj调用obj时,就会到addA.obj中的符号表找到Add的地址,然后链接到一起。
每个编译器都有自己的命名规则,那么链接器会在哪儿寻找Add函数呢?
c语言
在Linux下,采用gcc编译后,函数名的修饰不发生改变。
c
在Linux下,采用g 编译后,函数名的修饰发生改变。 会在编译过程中将函数的参数类型信息(以及其他可能需要的信息,如模板参数等)添加到函数的名字中。
引用
定义
引用不是定义一个新的变量,而是原有的变量有了新的别名,并不需要为这个别名开辟一个新的空间,它和引用的变量公用一块存储空间。
规则 :引用类型&引用变量名=引用实体名;
代码语言:javascript复制int &ra=a;
引用变量必须和引用实体是同种类型的。
引用特性
1引用前必须初始化
2一个实体可以被多次引用
3一个引用变量名只能使用一次,不能再引用其他实体
常引用
下面我将会逐个讲解下面代码错误的地方,以及如何修正
代码语言:javascript复制void TestConstRef()
{
const int a = 10;
int& ra = a;
const int& ra = a;
int& b = 10;
const int& b = 10;
double d = 12.34;
int& rd = d;
const int& rd = d;
}
1 const int a = 10; int& ra = a; 错误 ,不能将非常量引用绑定到常量对象上。
const int& ra = a; 正确
2 int& b = 10;
错误,不能将非常量引用绑定到字面量上
const int& b = 10; 正确
3 double d = 12.34; int& rd = d; 错误,类型不同
const int&rd=d; 正确
权限可以缩小,但不能被放大。
用法
1作为参数
代码语言:javascript复制void swap(int&left,int&right)
{
temp=left;
left=right;
right=temp;
}
2作为函数的返回值
代码语言:javascript复制int &count()
{
static int n=0;
n ;
return n;
}
代码语言:javascript复制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;
}
Add函数运行结束之后,栈帧就被销毁了,c变量就没有意义了。但是空间被回收指的是空间不能使用,仍然可以通过引用找到这个值。
在调用函数结束后,如果返回对象还在(未还给系统),可以使用引用返回,如果返回对象已经还给系统,就要使用传值返回。
传值传址效率比较
采用传值返回时,返回的并不是值本身,而是该实参或者返回变量的一份临时拷贝,因此效率非常低下。
在语法层面上,引用是不占空间的。
但是在底层逻辑层面,引用需要占一块空间。
代码语言:javascript复制int main()
{
int a=10;
int &ra=a;
ra=20;
int *p=&a;
*p=20;
return 0;
}
引用和指针的区别
1 引用必须要初始化,指针可以不用初始化
2 引用在引用时只能引用一个实体,而多个指针可以指向同一个实体
3sizeof的结果不同,引用中计算的是引用内容的大小,而指针是地址空间的大小
4引用中 1是引用的实体加一,而指针是偏移一个类型大小
5指针需要显示解引用,指针只需要自己处理
6引用比指针更加安全
内联函数
概念
以inline修饰的函数叫作内联函数,在gcc编译器下,只有内联函数才会展开
auto关键字
编译器会通过变量初始化的表达式来推断变量的类型。(编译器在编译时期推导而得)它并不是一种类型的声明,而是一种类型的占位符。编译器在编译时期会替换成实际的类型。