C到C++入门基础知识
一:命名空间:namespace
(一):命名空间的定义
注:命名空间只能定义在全局,不能定义在函数内部。
(1)类似于C语言的结构体,C语言的命名空间定义为:
namespace name{ // name是命名空间的名字
//
}
(2)命名空间里成员可以是:变量/函数/自定义类型变量/类,等。
namespace xs {
int age;
char name[10];
int ID;
struct xs{
int height;
char* hobies;
};
void swap(int* num1,int*num2)
{
int tmp = *num1;
*num1 = *num2;
*num2 = tmp;
}
};
(3)命名空间可以嵌套定义:
namespace xs {
int age;
char name[10];
int ID;
struct xs{
int height;
char* hobies;
};
void swap(int* num1,int*num2)
{
int tmp = *num1;
*num1 = *num2;
*num2 = tmp;
}
// 嵌套一个老师的命名空间
namespace teacher {
char* subject;
int work_year;
char* name;
}
};
(4)在不同文件中定义同一个名字相同的命名空间会被编译器识别为同一个命名空间:
例如同一个项目中分别有两个.cpp文件,两个文件都有xs这命名空间,在编译的时候不会报错,编译器会将他们识别为同一个命名空间。
(5)C++的标准库的命名空间是std(standard).
(二):命名空间的使用
我们在命名空间中定义了变量,函数,类或者结构体等,如果我们没有解析命名空间,像C语言一样使用命名空间的对象和方法时会报错:
namespace xs {
int age;
char name[10];
int ID;
struct xs{
int height;
char* hobies;
};
void swap(int* num1,int*num2)
{
int tmp = *num1;
*num1 = *num2;
*num2 = tmp;
}
namespace teacher {
char* subject;
int work_year;
char* name;
}
};
int main()
{
int a = 1;
int b = 2;
swap(&a, &b);
return 0;
}
在使用命名空间域内定义的变量的时候有以下几种方法:
(1)指定命名空间访问:
::
符号被称为作用域解析运算符
#include <stdio.h>
namespace xs {
int age;
char name[10];
int ID;
struct xs{
int height;
char* hobies;
};
void swap(int* num1,int*num2)
{
int tmp = *num1;
*num1 = *num2;
*num2 = tmp;
}
namespace teacher {
char* subject;
int work_year;
char* name;
}
};
int main()
{
int a = 1;
int b = 2;
// 这里指定xs这个命名空间域
xs::swap(&a, &b);
printf("a = %d \nb = %d", a, b);
return 0;
}
(2)使用using关键字将命名空间中的某个成员展开
#include <stdio.h>
namespace xs {
int age;
char name[10];
int ID;
struct xs{
int height;
char* hobies;
};
void swap(int* num1,int*num2)
{
int tmp = *num1;
*num1 = *num2;
*num2 = tmp;
}
namespace teacher {
char* subject;
int work_year;
char* name;
}
};
// 展开xs中的swap函数
using xs::swap;
int main()
{
int a = 1;
int b = 2;
// 这里我们去掉xs::不会报错
swap(&a, &b);
printf("a = %d \nb = %d", a, b);
return 0;
}
(3)使用using关键字展开命名空间中的全部成员:这种方式容易产生命名冲突等问题
#include <stdio.h>
namespace xs {
int age = 10;
char name[10] = "小明";
int ID;
struct _xs{
int height;
char* hobies;
};
void swap(int* num1,int*num2)
{
int tmp = *num1;
*num1 = *num2;
*num2 = tmp;
}
namespace _teacher {
char* subject;
int work_year;
char* name;
}
};
// 展开命名空间中的全部成员,可以不用域解析::就可以随便访问命名空间内的所有成员了
using namespace xs;
int main()
{
printf("age:%d\n name:%s", age, name);
return 0;
}
(三):为什么要使用命名空间
(1).解决C语言命名冲突问题
(2).命名空间的本质是开出一个独立的域空间,C++中域有函数局部域,全局域,命名空间域,类域;域影响的是编译时语法查找⼀个变量/函数/类型出处(声明或定义)的逻辑,所以有了域隔离,名字冲突就解决了。局部域和全局域除了会影响编译查找逻辑,还会影响变量的⽣命周期,命名空间域和类域不影响变量⽣命周期。
二:输入输出:cout / cin
说明:cout和cin是c++的标准输入输出类的对象,他们是标准库<iostream>中定义的,用于窄字符(narrowcharacters(oftypechar))的输入输出。
(1)同C语言相比cout和cin不需要格式化,他们支持任意类型的输入输出,会自动将其他类型转换成字符串类型
(2)cout/cin/(换行endl)等都被定义于C++标准库<iostream>,因为C++标准库都放在std(standard)的命名空间中,所以使用的时候要解析命名空间。
(3)有些编译器<iostream>会间接包含printf和scanf函数,也就是说不包含<stdio.h>头文件也可以使用这两函数。
#include <iostream>
using namespace std;
int main()
{
int age;
char name[10];
cin >> age >> name;
// 这里没有包含<stdio.h>printf也可以正常使用
printf("age:%d\tname:%s\n", age, name);
cout << age << endl;
cout << name << endl;
return 0;
}
三:缺省参数
定义:在定义函数的时候给予参数一个默认的缺省值。
(1)半缺省,全缺省。
半缺省:部分形参给予默认值。C++规定半缺省参数必须从右往左依次连续缺省,不能间隔跳跃给缺省值。
全缺省:全部形参给予默认值。
(2)带缺省参数的函数调⽤,C++规定必须从左到右依次给实参,不能跳跃给实参。
(3)含缺省参数的函数调用的时候,没有传参使用默认值,传参使用传递值。
#include <iostream>
// 半缺省
int Add1(int b, int c, int a = 0)
{
return a + b + c;
}
// 全缺省
int Add2(int a = 0, int b = 0)
{
return a + b;
}
using namespace std;
int main()
{
int n1 = 2;
int n2 = 3;
cout << Add1(n1,n2) << endl;
// 全缺省的
cout << Add2() << endl;
return 0;
}
(4)函数声明和定义分离时,缺省参数不能在函数声明和定义中同时出现,规定必须函数声明给缺省值。
我们将.c文件的两个函数声明放到,h文件.c文件依然给缺省值
编译器报错:
所以应该将.c文件中的缺省参数去掉
四:内联函数:inline
使用inline关键字修饰的函数被调用时会在调用处将函数定义展开
(1)加上inline的函数,如果函数代码较多或者是递归,编译器会忽视不展开代码。
(2)使用inline的本质是替换函数调用,提高程序执行的效率(因为函数调用需要建立函数栈帧,开辟空间等消耗),可以平替C语言的宏。
(3)inline不建议声明和定义分离到两个⽂件,分离会导致链接错误。因为inline被展开,就没有函数地址,链接时会出现报错
使用inline,直接在函数的前面加个inline就可以了。
inline int add(int a, int b)
{
return a + b;
}
五:函数重载
函数重载:即同名函数,提现了类的多态行为。
满足函数重载的条件:
1.函数的形参不同
2.函数的参数个数不同
3.形参的类型不同
4.形参类型顺序不同
// 函数的形参不同以及形参个数不同
int add(int a, int b,int c = 1)
{
return a + b;
}
double add(double a, double b, int c = 1)
{
return a + b + c;
}
// 形参不同和形参类型顺序不同
void func(char c, int n)
{
cout << c << " " << n << endl;
printf("形参不同\n");
}
void func(int n, char c)
{
cout << c << " " << n << endl;
printf("形参类型顺序不同\n");
}
int main()
{
// 名字一样调用的是两个函数
func('a', 4);
func(4, 'a');
cout << add(1, 2) << endl;
cout << add(1.0, 2.0, 3) << endl;
return 0;
}
但是返回值不同不可以
六:引用
(一)引用的定义和特性
(1)引用就是给一个对象起别名,不开辟内存空间。
其基本格式为:类型& 引⽤别名 = 引⽤对象; (这里的&不是取地址)
#include <iostream>
using namespace std;
int main()
{
int a = 0;
int c = 5;
int& ra = a;
int& rc = c;
// 对引用进行++
ra++;
rc++;
cout << "ra++后a:" << a << endl;
cout << "rc++后c:" << c << endl;
cout << "ra的地址为:" << &ra << endl;
cout << "a的地址为:" << &a << endl;
cout << "rc的地址为:" << &rc << endl;
cout << "c的地址为:" << &c << endl;
return 0;
}
(2)引用定义的时候必须要初始化:看吧。
(3)⼀个变量可以有多个引⽤:跟一个人有多个绰号差不多
#include <iostream>
using namespace std;
int main()
{
int a = 0;
int c = 5;
int& ra = a;
int& rra = a;
int& rc = c;
return 0;
}
(4)引⽤⼀旦引⽤⼀个实体,再不能引⽤其他实体:
(5)引用可以引用引用;
int a = 0;
int& ra = a;
// 这样是可以的
int& rra = ra;
(二)引用的使用
(1)引⽤在实践中主要是于引⽤传参和引⽤做返回值中减少拷⻉提⾼效率和改变引⽤对象时同时改变被引⽤对象
例如在函数传参的时候
void fun(int& x)
{
x++;
}
int main()
{
int a = 0;
fun(a);
// 这里相当于将a取了一个别名,然后函数里面根据这个别名找到对应的对象进行运算
cout << a << endl;
return 0;
}
(三)const修饰引用
(1)const修饰引用可以使权限缩小,但是不能使权限放大,当然const修饰的应用对象是不能被修改的。
// 定义一个变量
const int a = 8;
// 权限放大,不允许,会报错的
int& ra = a;
// 权限平替,这样可以
const int& ra = a;
int b = 0;
// 权限缩小
int& rb = b;
// 不允许
rb++;
// 允许
b++;
// 权限缩小只是缩小引用的权限缩小,而不会影响引用的对象的权限。
(2)引用可以引用临时对象:
临时对象:在程序运行中产生的用于转换的值或者其他对象,例如:类型转换,函数传参(函数传参是临时拷贝,传地址就不会产生临时对象)。这期间就会产生临时对象。
临时对象具有常性:
void fun(int& x)
{
x++;
}
void fun2(int x)
{
int& rx = x;
x++;
cout << rx << endl; // 这输出3
}
int main()
{
int a = 0;
fun(a);
// 这里相当于将a取了一个别名,然后函数里面根据这个别名找到对应的对象进行运算
cout << a << endl;
const int n = 2;
int rn = n;
n++; // 这里不允许
// 这里int向double转换的时候产生了临时变量,权限被放大了。
const double& rm = n * 3;
// n是const修饰的变量,传递给函数,传递的是它的一份临时拷贝
fun2(n);
cout << rm << endl; // 这里输出6
return 0;
}
(四)指针和引用的区别
(1)指针需要开辟空间,指针变量存储的是空间的地址。引用不需要开空间,他是所引用对象的另一个别名。
(2)引用定义的时候就必须要初始化,指针可以不用
int& r;
// 指针不初始化没问题,编译器不会初始化,但是这样就成了野指针,不建议这样搞
int* ptr;
(3)指针容易出现空指针和野指针,引用不容易出现空引用,引用比指针更安全。
空引用:
(4)引⽤可以直接访问指向对象,指针需要解引⽤才是访问指向对象。
(5)引⽤在初始化时引⽤⼀个对象后,就不能再引⽤其他对象;⽽指针可以在不断地改变指向对象。
int main()
{
int a = 0;
int& b = a;
int d = 5;
b = d;
int* p = &a;
cout << p << endl;
p = &d;
cout << p << endl;
cout << "a和b的地址" << endl;
cout << &a << endl;
cout << &b << endl;
return 0;
}
(6)sizeof中含义不同,引⽤结果为引⽤类型的⼤⼩,但指针始终是地址空间所占字节个数(32位平台下占4个字节,64位下是8byte)
七:nullptr和NULL的区别
NULL在C++中被定义为了宏0,容易导致和整型的0混淆使用,而nullptr不会出现这种情况,C语言支持NULL不支持nullptr。C++都支持。
int main()
{
// 这里明着给int类型的a赋值指针但是没报错
int a = NULL;
printf("%d", a);
return 0;
}